crewly 1.4.68 → 1.4.70

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 (60) hide show
  1. package/dist/backend/backend/src/constants.d.ts +1 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +1 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  6. package/dist/backend/backend/src/controllers/chat/chat.controller.js +11 -2
  7. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  8. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts.map +1 -1
  9. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js +4 -1
  10. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js.map +1 -1
  11. package/dist/backend/backend/src/controllers/project/project.controller.d.ts.map +1 -1
  12. package/dist/backend/backend/src/controllers/project/project.controller.js +9 -3
  13. package/dist/backend/backend/src/controllers/project/project.controller.js.map +1 -1
  14. package/dist/backend/backend/src/middleware/require-auth.middleware.d.ts +17 -9
  15. package/dist/backend/backend/src/middleware/require-auth.middleware.d.ts.map +1 -1
  16. package/dist/backend/backend/src/middleware/require-auth.middleware.js +60 -10
  17. package/dist/backend/backend/src/middleware/require-auth.middleware.js.map +1 -1
  18. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  19. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  20. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  21. package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts +13 -4
  22. package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
  23. package/dist/backend/backend/src/services/agent/claude-runtime.service.js +37 -7
  24. package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -1
  25. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +6 -3
  27. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
  28. package/dist/backend/backend/src/services/messaging/adapters/wechat-messenger.adapter.d.ts +47 -0
  29. package/dist/backend/backend/src/services/messaging/adapters/wechat-messenger.adapter.d.ts.map +1 -0
  30. package/dist/backend/backend/src/services/messaging/adapters/wechat-messenger.adapter.js +69 -0
  31. package/dist/backend/backend/src/services/messaging/adapters/wechat-messenger.adapter.js.map +1 -0
  32. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts +1 -1
  33. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +3 -1
  36. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
  37. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  38. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +6 -1
  39. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  40. package/dist/backend/backend/src/services/wechat/wechat-bridge.service.d.ts +69 -0
  41. package/dist/backend/backend/src/services/wechat/wechat-bridge.service.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/wechat/wechat-bridge.service.js +133 -0
  43. package/dist/backend/backend/src/services/wechat/wechat-bridge.service.js.map +1 -0
  44. package/dist/backend/backend/src/services/wechat/wechat.controller.d.ts +51 -0
  45. package/dist/backend/backend/src/services/wechat/wechat.controller.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/wechat/wechat.controller.js +104 -0
  47. package/dist/backend/backend/src/services/wechat/wechat.controller.js.map +1 -0
  48. package/dist/backend/backend/src/services/wechat/wechat.service.d.ts +171 -0
  49. package/dist/backend/backend/src/services/wechat/wechat.service.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/wechat/wechat.service.js +411 -0
  51. package/dist/backend/backend/src/services/wechat/wechat.service.js.map +1 -0
  52. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts +25 -2
  53. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/workflow/cron-task.service.js +143 -17
  55. package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
  56. package/dist/cli/backend/src/constants.d.ts +1 -0
  57. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  58. package/dist/cli/backend/src/constants.js +1 -0
  59. package/dist/cli/backend/src/constants.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,171 @@
1
+ /**
2
+ * WeChat iLink Bot Service
3
+ *
4
+ * Integrates with the Tencent iLink Bot API for WeChat messaging.
5
+ * Supports QR code login, message polling, and sending replies.
6
+ *
7
+ * API Base: https://ilinkai.weixin.qq.com
8
+ * Auth: Bearer token with AuthorizationType: ilink_bot_token header
9
+ *
10
+ * @module services/wechat/wechat.service
11
+ */
12
+ import { EventEmitter } from 'events';
13
+ /** Persisted WeChat config. */
14
+ export interface WeChatConfig {
15
+ botToken: string;
16
+ connectedAt: string;
17
+ botName?: string;
18
+ }
19
+ /** QR code response from iLink API. */
20
+ export interface QRCodeResponse {
21
+ qrcode_url: string;
22
+ qrcode_ticket: string;
23
+ }
24
+ /** QR code status from iLink API. */
25
+ export interface QRCodeStatus {
26
+ status: 'waiting' | 'scanned' | 'confirmed' | 'expired';
27
+ bot_token?: string;
28
+ bot_name?: string;
29
+ }
30
+ /** Incoming WeChat message from getupdates. */
31
+ export interface WeChatMessage {
32
+ msg_id: string;
33
+ from_user: string;
34
+ from_user_name: string;
35
+ content: string;
36
+ msg_type: string;
37
+ context_token: string;
38
+ timestamp: number;
39
+ }
40
+ /** Service connection state. */
41
+ export type WeChatConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
42
+ /**
43
+ * WeChat iLink Bot Service.
44
+ *
45
+ * Singleton service that manages the WeChat bot connection lifecycle:
46
+ * - QR code generation for login
47
+ * - Long-polling for incoming messages
48
+ * - Sending replies
49
+ * - Persisting credentials to disk
50
+ *
51
+ * Events:
52
+ * - `message` — Fired for each incoming WeChat message
53
+ * - `connected` — Fired when QR code scan is confirmed
54
+ * - `disconnected` — Fired when service disconnects
55
+ * - `error` — Fired on unrecoverable errors
56
+ */
57
+ export declare class WeChatService extends EventEmitter {
58
+ private static instance;
59
+ private readonly logger;
60
+ private state;
61
+ private botToken;
62
+ private botName;
63
+ private pollCursor;
64
+ private pollTimer;
65
+ private isPolling;
66
+ private constructor();
67
+ /**
68
+ * Get the singleton instance.
69
+ *
70
+ * @returns WeChatService instance
71
+ */
72
+ static getInstance(): WeChatService;
73
+ /**
74
+ * Reset the singleton (for testing).
75
+ */
76
+ static resetInstance(): void;
77
+ /**
78
+ * Get a QR code for WeChat login.
79
+ *
80
+ * Calls the iLink API to generate a QR code that the user scans
81
+ * with their WeChat app to authorize the bot.
82
+ *
83
+ * @returns QR code URL and ticket for status polling
84
+ * @throws Error if API call fails
85
+ */
86
+ getQRCode(): Promise<QRCodeResponse>;
87
+ /**
88
+ * Poll the QR code status until the user scans and confirms.
89
+ *
90
+ * @param ticket - QR code ticket from getQRCode()
91
+ * @returns Resolved when confirmed, rejected on timeout/error
92
+ */
93
+ pollQRCodeStatus(ticket: string): Promise<{
94
+ botToken: string;
95
+ botName?: string;
96
+ }>;
97
+ /**
98
+ * Send a message via WeChat.
99
+ *
100
+ * @param content - Message text
101
+ * @param contextToken - Context token from the received message (maintains thread)
102
+ * @throws Error if not connected or API call fails
103
+ */
104
+ sendMessage(content: string, contextToken: string): Promise<void>;
105
+ /**
106
+ * Get the current connection state.
107
+ *
108
+ * @returns Connection status object
109
+ */
110
+ getStatus(): {
111
+ state: WeChatConnectionState;
112
+ botName: string | null;
113
+ connectedAt: string | null;
114
+ };
115
+ /**
116
+ * Check if the service is connected.
117
+ *
118
+ * @returns True if connected with a valid bot token
119
+ */
120
+ isConnected(): boolean;
121
+ /**
122
+ * Disconnect and stop message polling.
123
+ */
124
+ disconnect(): void;
125
+ /**
126
+ * Try to restore connection from persisted config.
127
+ *
128
+ * @returns True if config was loaded and connection restored
129
+ */
130
+ tryRestore(): Promise<boolean>;
131
+ /**
132
+ * Start long-polling for incoming messages.
133
+ */
134
+ private startMessagePolling;
135
+ /**
136
+ * Stop message polling.
137
+ */
138
+ private stopMessagePolling;
139
+ /**
140
+ * Long-poll for new messages from the iLink API.
141
+ * Automatically reschedules on completion or error.
142
+ */
143
+ private pollMessages;
144
+ /**
145
+ * Persist bot token to disk.
146
+ */
147
+ private persistConfig;
148
+ /**
149
+ * Load persisted config from disk.
150
+ *
151
+ * @returns Config or null if not found
152
+ */
153
+ private loadConfig;
154
+ /**
155
+ * Remove persisted config from disk.
156
+ */
157
+ removeConfig(): Promise<void>;
158
+ /**
159
+ * Build authorization headers for iLink API.
160
+ *
161
+ * @returns Headers with Bearer token and AuthorizationType
162
+ */
163
+ private authHeaders;
164
+ }
165
+ /**
166
+ * Get the WeChat service singleton.
167
+ *
168
+ * @returns WeChatService instance
169
+ */
170
+ export declare function getWeChatService(): WeChatService;
171
+ //# sourceMappingURL=wechat.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wechat.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wechat/wechat.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAqCtC,+BAA+B;AAC/B,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gCAAgC;AAChC,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;AAM1F;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO;IAKP;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,aAAa;IAOnC;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAW5B;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC;IA0B1C;;;;;OAKG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA4DvF;;;;;;OAMG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBvE;;;;OAIG;IACH,SAAS,IAAI;QAAE,KAAK,EAAE,qBAAqB,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAQjG;;;;OAIG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,UAAU,IAAI,IAAI;IAUlB;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAqBpC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAM3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;OAGG;YACW,YAAY;IAqE1B;;OAEG;YACW,aAAa;IAiB3B;;;;OAIG;YACW,UAAU;IASxB;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAanC;;;;OAIG;IACH,OAAO,CAAC,WAAW;CAOpB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD"}
@@ -0,0 +1,411 @@
1
+ /**
2
+ * WeChat iLink Bot Service
3
+ *
4
+ * Integrates with the Tencent iLink Bot API for WeChat messaging.
5
+ * Supports QR code login, message polling, and sending replies.
6
+ *
7
+ * API Base: https://ilinkai.weixin.qq.com
8
+ * Auth: Bearer token with AuthorizationType: ilink_bot_token header
9
+ *
10
+ * @module services/wechat/wechat.service
11
+ */
12
+ import { EventEmitter } from 'events';
13
+ import { readFile, writeFile, mkdir } from 'fs/promises';
14
+ import { join } from 'path';
15
+ import { homedir } from 'os';
16
+ import { LoggerService } from '../core/logger.service.js';
17
+ // ---------------------------------------------------------------------------
18
+ // Constants
19
+ // ---------------------------------------------------------------------------
20
+ const ILINK_API_BASE = 'https://ilinkai.weixin.qq.com';
21
+ /** Endpoints for the iLink Bot API. */
22
+ const ENDPOINTS = {
23
+ GET_QR_CODE: '/ilink/bot/get_bot_qrcode',
24
+ QR_CODE_STATUS: '/ilink/bot/get_qrcode_status',
25
+ GET_UPDATES: '/ilink/bot/getupdates',
26
+ SEND_MESSAGE: '/ilink/bot/sendmessage',
27
+ };
28
+ /** Default poll timeout for long-polling (seconds). */
29
+ const POLL_TIMEOUT_S = 35;
30
+ /** QR code status poll interval (milliseconds). */
31
+ const QR_STATUS_POLL_INTERVAL_MS = 5000;
32
+ /** Max time to wait for QR scan (milliseconds). */
33
+ const QR_SCAN_TIMEOUT_MS = 300_000;
34
+ /** Config storage path. */
35
+ const CONFIG_DIR = join(homedir(), '.crewly', 'wechat');
36
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
37
+ // ---------------------------------------------------------------------------
38
+ // Service
39
+ // ---------------------------------------------------------------------------
40
+ /**
41
+ * WeChat iLink Bot Service.
42
+ *
43
+ * Singleton service that manages the WeChat bot connection lifecycle:
44
+ * - QR code generation for login
45
+ * - Long-polling for incoming messages
46
+ * - Sending replies
47
+ * - Persisting credentials to disk
48
+ *
49
+ * Events:
50
+ * - `message` — Fired for each incoming WeChat message
51
+ * - `connected` — Fired when QR code scan is confirmed
52
+ * - `disconnected` — Fired when service disconnects
53
+ * - `error` — Fired on unrecoverable errors
54
+ */
55
+ export class WeChatService extends EventEmitter {
56
+ static instance = null;
57
+ logger;
58
+ state = 'disconnected';
59
+ botToken = null;
60
+ botName = null;
61
+ pollCursor = null;
62
+ pollTimer = null;
63
+ isPolling = false;
64
+ constructor() {
65
+ super();
66
+ this.logger = LoggerService.getInstance().createComponentLogger('WeChatService');
67
+ }
68
+ /**
69
+ * Get the singleton instance.
70
+ *
71
+ * @returns WeChatService instance
72
+ */
73
+ static getInstance() {
74
+ if (!WeChatService.instance) {
75
+ WeChatService.instance = new WeChatService();
76
+ }
77
+ return WeChatService.instance;
78
+ }
79
+ /**
80
+ * Reset the singleton (for testing).
81
+ */
82
+ static resetInstance() {
83
+ if (WeChatService.instance) {
84
+ WeChatService.instance.disconnect();
85
+ }
86
+ WeChatService.instance = null;
87
+ }
88
+ // -------------------------------------------------------------------------
89
+ // Public API
90
+ // -------------------------------------------------------------------------
91
+ /**
92
+ * Get a QR code for WeChat login.
93
+ *
94
+ * Calls the iLink API to generate a QR code that the user scans
95
+ * with their WeChat app to authorize the bot.
96
+ *
97
+ * @returns QR code URL and ticket for status polling
98
+ * @throws Error if API call fails
99
+ */
100
+ async getQRCode() {
101
+ this.state = 'connecting';
102
+ this.logger.info('Requesting WeChat login QR code');
103
+ const response = await fetch(`${ILINK_API_BASE}${ENDPOINTS.GET_QR_CODE}?bot_type=3`, {
104
+ method: 'GET',
105
+ headers: { 'Content-Type': 'application/json' },
106
+ });
107
+ if (!response.ok) {
108
+ this.state = 'error';
109
+ throw new Error(`Failed to get QR code: ${response.status}`);
110
+ }
111
+ const data = await response.json();
112
+ if (data.errcode && data.errcode !== 0) {
113
+ this.state = 'error';
114
+ throw new Error(`iLink API error: ${data.errmsg || data.errcode}`);
115
+ }
116
+ const qrData = data.data || data;
117
+ this.logger.info('QR code generated', { hasUrl: !!qrData.qrcode_url });
118
+ return qrData;
119
+ }
120
+ /**
121
+ * Poll the QR code status until the user scans and confirms.
122
+ *
123
+ * @param ticket - QR code ticket from getQRCode()
124
+ * @returns Resolved when confirmed, rejected on timeout/error
125
+ */
126
+ async pollQRCodeStatus(ticket) {
127
+ const startTime = Date.now();
128
+ return new Promise((resolve, reject) => {
129
+ const checkStatus = async () => {
130
+ if (Date.now() - startTime > QR_SCAN_TIMEOUT_MS) {
131
+ this.state = 'disconnected';
132
+ reject(new Error('QR code scan timed out'));
133
+ return;
134
+ }
135
+ try {
136
+ const response = await fetch(`${ILINK_API_BASE}${ENDPOINTS.QR_CODE_STATUS}`, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ qrcode_ticket: ticket }),
140
+ });
141
+ if (!response.ok) {
142
+ throw new Error(`Status check failed: ${response.status}`);
143
+ }
144
+ const data = await response.json();
145
+ const status = data.data || data;
146
+ if (status.status === 'confirmed' && status.bot_token) {
147
+ this.botToken = status.bot_token;
148
+ this.botName = status.bot_name || null;
149
+ this.state = 'connected';
150
+ await this.persistConfig();
151
+ this.startMessagePolling();
152
+ this.emit('connected', { botName: this.botName });
153
+ this.logger.info('WeChat connected', { botName: this.botName });
154
+ resolve({ botToken: status.bot_token, botName: status.bot_name });
155
+ return;
156
+ }
157
+ if (status.status === 'expired') {
158
+ this.state = 'disconnected';
159
+ reject(new Error('QR code expired'));
160
+ return;
161
+ }
162
+ // Continue polling
163
+ setTimeout(checkStatus, QR_STATUS_POLL_INTERVAL_MS);
164
+ }
165
+ catch (err) {
166
+ this.logger.warn('QR status poll error', {
167
+ error: err instanceof Error ? err.message : String(err),
168
+ });
169
+ // Retry on transient errors
170
+ setTimeout(checkStatus, QR_STATUS_POLL_INTERVAL_MS);
171
+ }
172
+ };
173
+ checkStatus();
174
+ });
175
+ }
176
+ /**
177
+ * Send a message via WeChat.
178
+ *
179
+ * @param content - Message text
180
+ * @param contextToken - Context token from the received message (maintains thread)
181
+ * @throws Error if not connected or API call fails
182
+ */
183
+ async sendMessage(content, contextToken) {
184
+ if (!this.botToken) {
185
+ throw new Error('WeChat is not connected');
186
+ }
187
+ const response = await fetch(`${ILINK_API_BASE}${ENDPOINTS.SEND_MESSAGE}`, {
188
+ method: 'POST',
189
+ headers: this.authHeaders(),
190
+ body: JSON.stringify({
191
+ msg_type: 'text',
192
+ content: { text: content },
193
+ context_token: contextToken,
194
+ }),
195
+ });
196
+ if (!response.ok) {
197
+ const errorText = await response.text().catch(() => '');
198
+ throw new Error(`Failed to send message: ${response.status} ${errorText}`);
199
+ }
200
+ this.logger.info('WeChat message sent', { contentLength: content.length });
201
+ }
202
+ /**
203
+ * Get the current connection state.
204
+ *
205
+ * @returns Connection status object
206
+ */
207
+ getStatus() {
208
+ return {
209
+ state: this.state,
210
+ botName: this.botName,
211
+ connectedAt: this.state === 'connected' ? new Date().toISOString() : null,
212
+ };
213
+ }
214
+ /**
215
+ * Check if the service is connected.
216
+ *
217
+ * @returns True if connected with a valid bot token
218
+ */
219
+ isConnected() {
220
+ return this.state === 'connected' && !!this.botToken;
221
+ }
222
+ /**
223
+ * Disconnect and stop message polling.
224
+ */
225
+ disconnect() {
226
+ this.logger.info('Disconnecting WeChat');
227
+ this.stopMessagePolling();
228
+ this.botToken = null;
229
+ this.botName = null;
230
+ this.pollCursor = null;
231
+ this.state = 'disconnected';
232
+ this.emit('disconnected');
233
+ }
234
+ /**
235
+ * Try to restore connection from persisted config.
236
+ *
237
+ * @returns True if config was loaded and connection restored
238
+ */
239
+ async tryRestore() {
240
+ try {
241
+ const config = await this.loadConfig();
242
+ if (config?.botToken) {
243
+ this.botToken = config.botToken;
244
+ this.botName = config.botName || null;
245
+ this.state = 'connected';
246
+ this.startMessagePolling();
247
+ this.logger.info('WeChat connection restored from config', { botName: this.botName });
248
+ return true;
249
+ }
250
+ }
251
+ catch {
252
+ // Config doesn't exist or is invalid
253
+ }
254
+ return false;
255
+ }
256
+ // -------------------------------------------------------------------------
257
+ // Internal: Message Polling
258
+ // -------------------------------------------------------------------------
259
+ /**
260
+ * Start long-polling for incoming messages.
261
+ */
262
+ startMessagePolling() {
263
+ if (this.isPolling)
264
+ return;
265
+ this.isPolling = true;
266
+ this.pollMessages();
267
+ }
268
+ /**
269
+ * Stop message polling.
270
+ */
271
+ stopMessagePolling() {
272
+ this.isPolling = false;
273
+ if (this.pollTimer) {
274
+ clearTimeout(this.pollTimer);
275
+ this.pollTimer = null;
276
+ }
277
+ }
278
+ /**
279
+ * Long-poll for new messages from the iLink API.
280
+ * Automatically reschedules on completion or error.
281
+ */
282
+ async pollMessages() {
283
+ if (!this.isPolling || !this.botToken)
284
+ return;
285
+ try {
286
+ const body = { timeout: POLL_TIMEOUT_S };
287
+ if (this.pollCursor) {
288
+ body.cursor = this.pollCursor;
289
+ }
290
+ const controller = new AbortController();
291
+ const timeoutId = setTimeout(() => controller.abort(), (POLL_TIMEOUT_S + 10) * 1000);
292
+ const response = await fetch(`${ILINK_API_BASE}${ENDPOINTS.GET_UPDATES}`, {
293
+ method: 'POST',
294
+ headers: this.authHeaders(),
295
+ body: JSON.stringify(body),
296
+ signal: controller.signal,
297
+ });
298
+ clearTimeout(timeoutId);
299
+ if (!response.ok) {
300
+ if (response.status === 401 || response.status === 403) {
301
+ this.logger.error('WeChat auth failed during polling — disconnecting');
302
+ this.state = 'error';
303
+ this.stopMessagePolling();
304
+ this.emit('error', new Error('Authentication failed'));
305
+ return;
306
+ }
307
+ throw new Error(`Poll failed: ${response.status}`);
308
+ }
309
+ const data = await response.json();
310
+ if (data.data?.cursor) {
311
+ this.pollCursor = data.data.cursor;
312
+ }
313
+ const messages = data.data?.messages || [];
314
+ for (const msg of messages) {
315
+ this.logger.info('WeChat message received', {
316
+ from: msg.from_user_name,
317
+ type: msg.msg_type,
318
+ contentLength: msg.content.length,
319
+ });
320
+ this.emit('message', msg);
321
+ }
322
+ }
323
+ catch (err) {
324
+ if (err instanceof Error && err.name === 'AbortError') {
325
+ // Timeout — normal for long-polling
326
+ }
327
+ else {
328
+ this.logger.warn('WeChat poll error', {
329
+ error: err instanceof Error ? err.message : String(err),
330
+ });
331
+ }
332
+ }
333
+ // Schedule next poll immediately (long-polling is self-regulating)
334
+ if (this.isPolling) {
335
+ this.pollTimer = setTimeout(() => this.pollMessages(), 1000);
336
+ }
337
+ }
338
+ // -------------------------------------------------------------------------
339
+ // Internal: Config Persistence
340
+ // -------------------------------------------------------------------------
341
+ /**
342
+ * Persist bot token to disk.
343
+ */
344
+ async persistConfig() {
345
+ try {
346
+ await mkdir(CONFIG_DIR, { recursive: true });
347
+ const config = {
348
+ botToken: this.botToken,
349
+ connectedAt: new Date().toISOString(),
350
+ botName: this.botName || undefined,
351
+ };
352
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
353
+ this.logger.debug('WeChat config persisted');
354
+ }
355
+ catch (err) {
356
+ this.logger.warn('Failed to persist WeChat config', {
357
+ error: err instanceof Error ? err.message : String(err),
358
+ });
359
+ }
360
+ }
361
+ /**
362
+ * Load persisted config from disk.
363
+ *
364
+ * @returns Config or null if not found
365
+ */
366
+ async loadConfig() {
367
+ try {
368
+ const data = await readFile(CONFIG_FILE, 'utf-8');
369
+ return JSON.parse(data);
370
+ }
371
+ catch {
372
+ return null;
373
+ }
374
+ }
375
+ /**
376
+ * Remove persisted config from disk.
377
+ */
378
+ async removeConfig() {
379
+ try {
380
+ const { unlink } = await import('fs/promises');
381
+ await unlink(CONFIG_FILE);
382
+ }
383
+ catch {
384
+ // File may not exist
385
+ }
386
+ }
387
+ // -------------------------------------------------------------------------
388
+ // Internal: Helpers
389
+ // -------------------------------------------------------------------------
390
+ /**
391
+ * Build authorization headers for iLink API.
392
+ *
393
+ * @returns Headers with Bearer token and AuthorizationType
394
+ */
395
+ authHeaders() {
396
+ return {
397
+ 'Content-Type': 'application/json',
398
+ Authorization: `Bearer ${this.botToken}`,
399
+ AuthorizationType: 'ilink_bot_token',
400
+ };
401
+ }
402
+ }
403
+ /**
404
+ * Get the WeChat service singleton.
405
+ *
406
+ * @returns WeChatService instance
407
+ */
408
+ export function getWeChatService() {
409
+ return WeChatService.getInstance();
410
+ }
411
+ //# sourceMappingURL=wechat.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wechat.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wechat/wechat.service.ts"],"names":[],"mappings":"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"}
@@ -14,10 +14,14 @@ import type { CronTask, CreateCronTaskRequest, UpdateCronTaskRequest } from '../
14
14
  * Parse a cron expression and calculate the next run time.
15
15
  * Supports standard 5-field cron: minute hour day-of-month month day-of-week.
16
16
  *
17
+ * All time matching is performed in the given timezone (#268), ensuring cron
18
+ * expressions like "45 8 * * 1-5" with timezone "Asia/Shanghai" fire at
19
+ * 08:45 CST, not 08:45 UTC.
20
+ *
17
21
  * @param cronExpression - Standard 5-field cron expression
18
- * @param timezone - IANA timezone
22
+ * @param timezone - IANA timezone (e.g. 'Asia/Shanghai', 'America/New_York')
19
23
  * @param after - Calculate next run after this date (defaults to now)
20
- * @returns ISO string of next run time
24
+ * @returns ISO string of next run time (UTC)
21
25
  */
22
26
  export declare function getNextRunTime(cronExpression: string, timezone: string, after?: Date): string;
23
27
  /**
@@ -29,6 +33,8 @@ export declare class CronTaskService {
29
33
  private storeFile;
30
34
  private timer;
31
35
  private executionCallback;
36
+ private agentStatusCallback;
37
+ private agentStartCallback;
32
38
  constructor(crewlyHome?: string);
33
39
  /**
34
40
  * Get singleton instance.
@@ -45,6 +51,18 @@ export declare class CronTaskService {
45
51
  * @param callback - Async function that executes a cron task
46
52
  */
47
53
  setExecutionCallback(callback: (task: CronTask) => Promise<void>): void;
54
+ /**
55
+ * Set the callback to check whether a target agent is online.
56
+ *
57
+ * @param callback - Returns true if the agent is online/active
58
+ */
59
+ setAgentStatusCallback(callback: (sessionName: string) => Promise<boolean>): void;
60
+ /**
61
+ * Set the callback to auto-start an offline agent before dispatching a cron task.
62
+ *
63
+ * @param callback - Attempts to start the agent, returns true on success
64
+ */
65
+ setAgentStartCallback(callback: (sessionName: string, teamId: string) => Promise<boolean>): void;
48
66
  /**
49
67
  * Start the cron task evaluation loop.
50
68
  * Checks every minute for tasks whose nextRunAt has passed.
@@ -100,6 +118,11 @@ export declare class CronTaskService {
100
118
  delete(id: string): Promise<boolean>;
101
119
  /**
102
120
  * Evaluate all enabled tasks and execute those whose nextRunAt has passed.
121
+ *
122
+ * #268: Before dispatching, checks whether the target agent is online.
123
+ * If offline and an agentStartCallback is configured, attempts to auto-start
124
+ * the agent. If still offline, skips the task and logs a warning instead of
125
+ * silently dropping it.
103
126
  */
104
127
  evaluateTasks(): Promise<void>;
105
128
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cron-task.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/workflow/cron-task.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EACX,QAAQ,EAER,qBAAqB,EACrB,qBAAqB,EACrB,MAAM,gCAAgC,CAAC;AAOxC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,CA0C7F;AAED;;GAEG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgC;IACvD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,iBAAiB,CAAoD;gBAEjE,UAAU,CAAC,EAAE,MAAM;IAM/B;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,eAAe;IAOrC;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAO5B;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIvE;;;OAGG;IACH,KAAK,IAAI,IAAI;IAgBb;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC;IA0B/D;;;;;OAKG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAcrF;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAK/C;;;;;;;OAOG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAyBlF;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW1C;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCpC;;OAEG;YACW,SAAS;IASvB;;OAEG;YACW,SAAS;CAKvB"}
1
+ {"version":3,"file":"cron-task.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/workflow/cron-task.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EACX,QAAQ,EAER,qBAAqB,EACrB,qBAAqB,EACrB,MAAM,gCAAgC,CAAC;AA0DxC;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,CAsC7F;AAED;;GAEG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgC;IACvD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,iBAAiB,CAAoD;IAC7E,OAAO,CAAC,mBAAmB,CAA4D;IACvF,OAAO,CAAC,kBAAkB,CAA4E;gBAE1F,UAAU,CAAC,EAAE,MAAM;IAM/B;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,eAAe;IAOrC;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAO5B;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIvE;;;;OAIG;IACH,sBAAsB,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAIjF;;;;OAIG;IACH,qBAAqB,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAIhG;;;OAGG;IACH,KAAK,IAAI,IAAI;IAgBb;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC;IA0B/D;;;;;OAKG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAcrF;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAK/C;;;;;;;OAOG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAyBlF;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW1C;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA6FpC;;OAEG;YACW,SAAS;IASvB;;OAEG;YACW,SAAS;CAKvB"}