engine7 7.1.40 → 7.1.42

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/cli.mjs CHANGED
@@ -14,6 +14,951 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/channels/wechat.ts
18
+ var wechat_exports = {};
19
+ __export(wechat_exports, {
20
+ WechatAdapter: () => WechatAdapter,
21
+ wechatQrLogin: () => wechatQrLogin
22
+ });
23
+ import * as crypto from "crypto";
24
+ import * as fs from "fs";
25
+ import * as path from "path";
26
+ import * as os from "os";
27
+ import { promises as dns } from "dns";
28
+ function sleep2(ms) {
29
+ return new Promise((r) => setTimeout(r, ms));
30
+ }
31
+ function pkcs7Pad(data, blockSize = 16) {
32
+ const pad = blockSize - data.length % blockSize;
33
+ return Buffer.concat([data, Buffer.alloc(pad, pad)]);
34
+ }
35
+ function pkcs7Unpad(data) {
36
+ if (!data.length) return data;
37
+ const pad = data[data.length - 1];
38
+ if (pad < 1 || pad > 16 || pad > data.length) return data;
39
+ return data.subarray(0, data.length - pad);
40
+ }
41
+ function aesEncrypt(plaintext, key) {
42
+ const c = crypto.createCipheriv("aes-128-ecb", key, null);
43
+ c.setAutoPadding(false);
44
+ return Buffer.concat([c.update(pkcs7Pad(plaintext)), c.final()]);
45
+ }
46
+ function aesDecrypt(ciphertext, key) {
47
+ const d = crypto.createDecipheriv("aes-128-ecb", key, null);
48
+ d.setAutoPadding(false);
49
+ return pkcs7Unpad(Buffer.concat([d.update(ciphertext), d.final()]));
50
+ }
51
+ function parseAesKey(b64) {
52
+ const decoded = Buffer.from(b64, "base64");
53
+ if (decoded.length === 16) return decoded;
54
+ if (decoded.length === 32) {
55
+ const text = decoded.toString("ascii");
56
+ if (/^[0-9a-fA-F]+$/.test(text)) return Buffer.from(text, "hex");
57
+ }
58
+ throw new Error(`unexpected aes_key format (${decoded.length} bytes)`);
59
+ }
60
+ function randomUin() {
61
+ return Buffer.from(String(crypto.randomBytes(4).readUInt32BE(0)), "utf-8").toString("base64");
62
+ }
63
+ async function isNetworkUp(hostname) {
64
+ try {
65
+ await dns.resolve(hostname, "A");
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ function buildHeaders(token) {
72
+ return {
73
+ "Content-Type": "application/json",
74
+ "AuthorizationType": "ilink_bot_token",
75
+ "X-WECHAT-UIN": randomUin(),
76
+ "iLink-App-Id": ILINK_APP_ID,
77
+ "iLink-App-ClientVersion": String(ILINK_APP_CLIENT_VERSION),
78
+ "Authorization": `Bearer ${token}`
79
+ };
80
+ }
81
+ async function apiPost(baseUrl, endpoint, payload, token, timeoutMs) {
82
+ const body = JSON.stringify({ ...payload, base_info: { channel_version: CHANNEL_VERSION } });
83
+ const url = `${baseUrl.replace(/\/$/, "")}/${endpoint}`;
84
+ const ctrl = new AbortController();
85
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
86
+ try {
87
+ const resp = await fetch(url, { method: "POST", headers: buildHeaders(token), body, signal: ctrl.signal });
88
+ const text = await resp.text();
89
+ if (!resp.ok) throw new Error(`iLink POST ${endpoint} HTTP ${resp.status}: ${text.slice(0, 200)}`);
90
+ return JSON.parse(text);
91
+ } finally {
92
+ clearTimeout(timer);
93
+ }
94
+ }
95
+ function assertCdnUrl(url) {
96
+ let parsed;
97
+ try {
98
+ parsed = new URL(url);
99
+ } catch {
100
+ throw new Error(`Bad media URL: ${url}`);
101
+ }
102
+ if (!["http:", "https:"].includes(parsed.protocol)) throw new Error(`Bad scheme: ${parsed.protocol}`);
103
+ if (!CDN_ALLOWLIST.has(parsed.hostname)) throw new Error(`SSRF: host ${parsed.hostname} not in allowlist`);
104
+ }
105
+ function cdnDownloadUrl(base, param) {
106
+ return `${base.replace(/\/$/, "")}/download?encrypted_query_param=${encodeURIComponent(param)}`;
107
+ }
108
+ async function downloadBytes(url, timeoutMs) {
109
+ const ctrl = new AbortController();
110
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
111
+ try {
112
+ const resp = await fetch(url, { signal: ctrl.signal });
113
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
114
+ return Buffer.from(await resp.arrayBuffer());
115
+ } finally {
116
+ clearTimeout(timer);
117
+ }
118
+ }
119
+ async function downloadAndDecryptMedia(cdnBase, encParam, aesKeyB64, fullUrl, timeoutSec) {
120
+ let raw;
121
+ if (encParam) {
122
+ raw = await downloadBytes(cdnDownloadUrl(cdnBase, encParam), timeoutSec * 1e3);
123
+ } else if (fullUrl) {
124
+ assertCdnUrl(fullUrl);
125
+ raw = await downloadBytes(fullUrl, timeoutSec * 1e3);
126
+ } else {
127
+ throw new Error("media: no encrypt_query_param or full_url");
128
+ }
129
+ if (aesKeyB64) raw = aesDecrypt(raw, parseAesKey(aesKeyB64));
130
+ return raw;
131
+ }
132
+ function cdnUploadUrl(base, uploadParam, filekey) {
133
+ return `${base.replace(/\/$/, "")}/upload?encrypted_query_param=${encodeURIComponent(uploadParam)}&filekey=${encodeURIComponent(filekey)}`;
134
+ }
135
+ async function uploadCiphertext(uploadUrl, ciphertext) {
136
+ const ctrl = new AbortController();
137
+ const timer = setTimeout(() => ctrl.abort(), 12e4);
138
+ try {
139
+ console.log(`[wechat:cdn] upload start: url=${uploadUrl.slice(0, 120)}... ciphertextLen=${ciphertext.length}`);
140
+ const resp = await fetch(uploadUrl, {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/octet-stream" },
143
+ body: new Uint8Array(ciphertext),
144
+ signal: ctrl.signal
145
+ });
146
+ console.log(`[wechat:cdn] upload resp: status=${resp.status} headers=${JSON.stringify(Object.fromEntries(resp.headers.entries()))}`);
147
+ if (resp.status === 200) {
148
+ const param = resp.headers.get("x-encrypted-param");
149
+ const bodyText = await resp.text();
150
+ if (param) {
151
+ return param;
152
+ }
153
+ throw new Error(`CDN upload missing x-encrypted-param: ${bodyText.slice(0, 200)}`);
154
+ }
155
+ const text = await resp.text();
156
+ throw new Error(`CDN upload HTTP ${resp.status}: ${text.slice(0, 200)}`);
157
+ } finally {
158
+ clearTimeout(timer);
159
+ }
160
+ }
161
+ function safeId(id, keep = 8) {
162
+ return id.length <= keep ? id : id.slice(0, keep) + "...";
163
+ }
164
+ function isSessionExpired(ret, errcode, errmsg) {
165
+ if (ret === SESSION_EXPIRED_ERRCODE || errcode === SESSION_EXPIRED_ERRCODE) return true;
166
+ if ((ret === RATE_LIMIT_ERRCODE || errcode === RATE_LIMIT_ERRCODE) && (errmsg || "").toLowerCase() === "unknown error") return true;
167
+ return false;
168
+ }
169
+ function extractText(itemList) {
170
+ for (const item of itemList) {
171
+ if (item.type === ITEM_TEXT) {
172
+ const text = String(item.text_item?.text || "");
173
+ const ref = item.ref_msg || {};
174
+ const refItem = ref.message_item || {};
175
+ if ([ITEM_IMAGE, ITEM_VIDEO, ITEM_FILE, ITEM_VOICE].includes(refItem.type)) {
176
+ const title = ref.title || "";
177
+ return `${title ? `[\u5F15\u7528\u5A92\u4F53: ${title}]
178
+ ` : "[\u5F15\u7528\u5A92\u4F53]\n"}${text}`.trim();
179
+ }
180
+ if (refItem && Object.keys(refItem).length > 0) {
181
+ const parts = [];
182
+ if (ref.title) parts.push(String(ref.title));
183
+ const rt = extractText([refItem]);
184
+ if (rt) parts.push(rt);
185
+ if (parts.length) return `[\u5F15\u7528: ${parts.join(" | ")}]
186
+ ${text}`.trim();
187
+ }
188
+ return text;
189
+ }
190
+ }
191
+ for (const item of itemList) {
192
+ if (item.type === ITEM_VOICE) {
193
+ const vt = String(item.voice_item?.text || "");
194
+ if (vt) return vt;
195
+ }
196
+ }
197
+ return "";
198
+ }
199
+ function guessChatType(msg, accountId) {
200
+ const roomId = String(msg.room_id || msg.chat_room_id || "").trim();
201
+ const toId = String(msg.to_user_id || "").trim();
202
+ const isGroup = !!roomId || !!toId && !!accountId && toId !== accountId && msg.msg_type === 1;
203
+ return isGroup ? { type: "group", chatId: roomId || toId || String(msg.from_user_id || "") } : { type: "dm", chatId: String(msg.from_user_id || "") };
204
+ }
205
+ function mimeFromFilename(filename) {
206
+ const ext = path.extname(filename).toLowerCase();
207
+ const map = {
208
+ ".jpg": "image/jpeg",
209
+ ".jpeg": "image/jpeg",
210
+ ".png": "image/png",
211
+ ".gif": "image/gif",
212
+ ".webp": "image/webp",
213
+ ".bmp": "image/bmp",
214
+ ".mp4": "video/mp4",
215
+ ".mov": "video/quicktime",
216
+ ".avi": "video/x-msvideo",
217
+ ".mp3": "audio/mpeg",
218
+ ".wav": "audio/wav",
219
+ ".m4a": "audio/mp4",
220
+ ".silk": "audio/silk",
221
+ ".pdf": "application/pdf",
222
+ ".doc": "application/msword",
223
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
224
+ ".zip": "application/zip",
225
+ ".txt": "text/plain"
226
+ };
227
+ return map[ext] || "application/octet-stream";
228
+ }
229
+ function formatForWechat(content) {
230
+ let lines = content.split("\n");
231
+ lines = lines.map((l) => {
232
+ const m = l.match(/^#{1,4}\s+(.*)/);
233
+ return m ? `\u3010${m[1].trim()}\u3011` : l;
234
+ });
235
+ const result = [];
236
+ for (const line of lines) {
237
+ if (line.trim().startsWith("|") && line.trim().endsWith("|")) {
238
+ if (/^\|[\s:-]+\|$/.test(line.trim())) continue;
239
+ const cells = line.trim().slice(1, -1).split("|").map((c) => c.trim());
240
+ if (cells.every((c) => /^[-:]+$/.test(c))) continue;
241
+ result.push(`\u2022 ${cells.join(" | ")}`);
242
+ } else {
243
+ result.push(line);
244
+ }
245
+ }
246
+ const out = [];
247
+ let prevBlank = false;
248
+ for (const l of result) {
249
+ if (!l.trim() && prevBlank) continue;
250
+ out.push(l);
251
+ prevBlank = !l.trim();
252
+ }
253
+ return out.join("\n").trim();
254
+ }
255
+ function splitText(text, maxLen) {
256
+ if (text.length <= maxLen) return [text];
257
+ const chunks = [];
258
+ let remaining = text;
259
+ while (remaining.length > maxLen) {
260
+ let idx = remaining.lastIndexOf("\n", maxLen);
261
+ if (idx <= 0) idx = remaining.lastIndexOf(" ", maxLen);
262
+ if (idx <= 0) idx = maxLen;
263
+ chunks.push(remaining.slice(0, idx).trim());
264
+ remaining = remaining.slice(idx).trim();
265
+ }
266
+ if (remaining) chunks.push(remaining);
267
+ return chunks.filter((c) => c);
268
+ }
269
+ function loadSyncBuf(stateDir, accountId) {
270
+ try {
271
+ const fp = path.join(stateDir, "weixin", `${accountId}.sync.json`);
272
+ if (fs.existsSync(fp)) return String(JSON.parse(fs.readFileSync(fp, "utf-8")).sync_buf || "");
273
+ } catch {
274
+ }
275
+ return "";
276
+ }
277
+ function saveSyncBuf(stateDir, accountId, buf) {
278
+ try {
279
+ const fp = path.join(stateDir, "weixin", `${accountId}.sync.json`);
280
+ const dir = path.dirname(fp);
281
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
282
+ fs.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
283
+ } catch {
284
+ }
285
+ }
286
+ async function wechatQrLogin(options) {
287
+ const botType = options?.botType || "3";
288
+ const timeoutSeconds = options?.timeoutSeconds || 480;
289
+ const stateDir = options?.stateDir || path.join(os.homedir?.() || "/tmp", ".engine7");
290
+ console.log("[wechat] Fetching QR code from iLink...");
291
+ let qrResp;
292
+ try {
293
+ qrResp = await apiGet(ILINK_BASE_URL, `${EP_GET_BOT_QR}?bot_type=${botType}`, "", QR_TIMEOUT_MS);
294
+ } catch (err) {
295
+ console.error(`[wechat] Failed to fetch QR code: ${err.message}`);
296
+ return null;
297
+ }
298
+ const qrcodeValue = String(qrResp?.qrcode || "");
299
+ const qrcodeUrl = String(qrResp?.qrcode_img_content || "");
300
+ if (!qrcodeValue) {
301
+ console.error("[wechat] QR response missing qrcode field");
302
+ return null;
303
+ }
304
+ const qrScanData = qrcodeUrl || qrcodeValue;
305
+ console.log("\n========== \u5FAE\u4FE1\u626B\u7801\u767B\u5F55 ==========");
306
+ if (qrcodeUrl) {
307
+ console.log(`\u626B\u7801\u94FE\u63A5: ${qrcodeUrl}`);
308
+ }
309
+ console.log("\u8BF7\u7528\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\uFF08\u6216\u6253\u5F00\u4E0A\u9762\u7684\u94FE\u63A5\uFF09:");
310
+ console.log("===================================\n");
311
+ const deadline = Date.now() + timeoutSeconds * 1e3;
312
+ let currentBaseUrl = ILINK_BASE_URL;
313
+ let refreshCount = 0;
314
+ while (Date.now() < deadline) {
315
+ let statusResp;
316
+ try {
317
+ statusResp = await apiGet(currentBaseUrl, `${EP_GET_QR_STATUS}?qrcode=${qrcodeValue}`, "", QR_TIMEOUT_MS);
318
+ } catch {
319
+ await sleep2(1e3);
320
+ continue;
321
+ }
322
+ const status = String(statusResp?.status || "wait");
323
+ if (status === "wait") {
324
+ process.stdout.write(".");
325
+ } else if (status === "scaned") {
326
+ console.log("\n\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u91CC\u786E\u8BA4...");
327
+ } else if (status === "scaned_but_redirect") {
328
+ const redirectHost = String(statusResp?.redirect_host || "");
329
+ if (redirectHost) currentBaseUrl = `https://${redirectHost}`;
330
+ } else if (status === "expired") {
331
+ refreshCount++;
332
+ if (refreshCount > 3) {
333
+ console.log("\n\u4E8C\u7EF4\u7801\u591A\u6B21\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u767B\u5F55\u3002");
334
+ return null;
335
+ }
336
+ console.log(`
337
+ \u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u5237\u65B0\u4E2D... (${refreshCount}/3)`);
338
+ try {
339
+ qrResp = await apiGet(ILINK_BASE_URL, `${EP_GET_BOT_QR}?bot_type=${botType}`, "", QR_TIMEOUT_MS);
340
+ const newQrValue = String(qrResp?.qrcode || "");
341
+ const newQrUrl = String(qrResp?.qrcode_img_content || "");
342
+ if (newQrUrl) console.log(`\u65B0\u626B\u7801\u94FE\u63A5: ${newQrUrl}`);
343
+ } catch (err) {
344
+ console.error(`[wechat] QR refresh failed: ${err.message}`);
345
+ return null;
346
+ }
347
+ } else if (status === "confirmed") {
348
+ const accountId = String(statusResp?.ilink_bot_id || "");
349
+ const token = String(statusResp?.bot_token || "");
350
+ const baseUrl = String(statusResp?.baseurl || ILINK_BASE_URL);
351
+ const userId = String(statusResp?.ilink_user_id || "");
352
+ if (!accountId || !token) {
353
+ console.error("[wechat] QR confirmed but credential payload incomplete");
354
+ return null;
355
+ }
356
+ if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
357
+ const credFile = path.join(stateDir, `weixin-${accountId}.json`);
358
+ fs.writeFileSync(credFile, JSON.stringify({ accountId, token, baseUrl, userId }, null, 2), "utf8");
359
+ console.log(`
360
+ \u2705 \u5FAE\u4FE1\u767B\u5F55\u6210\u529F!`);
361
+ console.log(` accountId: ${accountId}`);
362
+ console.log(` \u51ED\u8BC1\u5DF2\u4FDD\u5B58: ${credFile}`);
363
+ console.log(`
364
+ \u8BF7\u5C06\u4EE5\u4E0B\u914D\u7F6E\u6DFB\u52A0\u5230 xiaoke.json:`);
365
+ console.log(JSON.stringify({
366
+ wechat: {
367
+ token,
368
+ accountId,
369
+ baseUrl: baseUrl !== ILINK_BASE_URL ? baseUrl : void 0
370
+ }
371
+ }, null, 2));
372
+ return { accountId, token, baseUrl, userId };
373
+ }
374
+ await sleep2(1e3);
375
+ }
376
+ console.log("\n[wechat] QR login timed out");
377
+ return null;
378
+ }
379
+ async function apiGet(baseUrl, endpoint, _token, timeoutMs) {
380
+ const url = `${baseUrl.replace(/\/$/, "")}/${endpoint.replace(/^\//, "")}`;
381
+ const controller = new AbortController();
382
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
383
+ try {
384
+ const resp = await fetch(url, {
385
+ method: "GET",
386
+ signal: controller.signal,
387
+ headers: { "Accept": "application/json" }
388
+ });
389
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
390
+ return await resp.json();
391
+ } finally {
392
+ clearTimeout(timer);
393
+ }
394
+ }
395
+ var ILINK_BASE_URL, WEIXIN_CDN_BASE_URL, ILINK_APP_ID, CHANNEL_VERSION, ILINK_APP_CLIENT_VERSION, EP_GET_UPDATES, EP_SEND_MESSAGE, EP_SEND_TYPING, EP_GET_CONFIG, EP_GET_UPLOAD_URL, LONG_POLL_TIMEOUT_MS, API_TIMEOUT_MS, MAX_MESSAGE_LENGTH, MAX_CONSECUTIVE_FAILURES, RETRY_DELAY_MS, BACKOFF_DELAY_MS, DISCONNECTED_THRESHOLD, DISCONNECTED_POLL_INTERVAL, SESSION_EXPIRED_ERRCODE, RATE_LIMIT_ERRCODE, SEND_CHUNK_DELAY_MS, SEND_CHUNK_RETRIES, SEND_CHUNK_RETRY_DELAY_MS, ITEM_TEXT, ITEM_IMAGE, ITEM_VOICE, ITEM_FILE, ITEM_VIDEO, MSG_TYPE_BOT, MSG_STATE_FINISH, MEDIA_IMAGE, MEDIA_VIDEO, MEDIA_FILE, MEDIA_VOICE, CDN_ALLOWLIST, ContextTokenStore, MessageDeduplicator, WechatAdapter, EP_GET_BOT_QR, EP_GET_QR_STATUS, QR_TIMEOUT_MS;
396
+ var init_wechat = __esm({
397
+ "src/channels/wechat.ts"() {
398
+ "use strict";
399
+ ILINK_BASE_URL = "https://ilinkai.weixin.qq.com";
400
+ WEIXIN_CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
401
+ ILINK_APP_ID = "bot";
402
+ CHANNEL_VERSION = "2.2.0";
403
+ ILINK_APP_CLIENT_VERSION = 2 << 16 | 2 << 8 | 0;
404
+ EP_GET_UPDATES = "ilink/bot/getupdates";
405
+ EP_SEND_MESSAGE = "ilink/bot/sendmessage";
406
+ EP_SEND_TYPING = "ilink/bot/sendtyping";
407
+ EP_GET_CONFIG = "ilink/bot/getconfig";
408
+ EP_GET_UPLOAD_URL = "ilink/bot/getuploadurl";
409
+ LONG_POLL_TIMEOUT_MS = 35e3;
410
+ API_TIMEOUT_MS = 15e3;
411
+ MAX_MESSAGE_LENGTH = 2e3;
412
+ MAX_CONSECUTIVE_FAILURES = 3;
413
+ RETRY_DELAY_MS = 2e3;
414
+ BACKOFF_DELAY_MS = 3e4;
415
+ DISCONNECTED_THRESHOLD = 5;
416
+ DISCONNECTED_POLL_INTERVAL = 5e3;
417
+ SESSION_EXPIRED_ERRCODE = -14;
418
+ RATE_LIMIT_ERRCODE = -2;
419
+ SEND_CHUNK_DELAY_MS = 1500;
420
+ SEND_CHUNK_RETRIES = 4;
421
+ SEND_CHUNK_RETRY_DELAY_MS = 1e3;
422
+ ITEM_TEXT = 1;
423
+ ITEM_IMAGE = 2;
424
+ ITEM_VOICE = 3;
425
+ ITEM_FILE = 4;
426
+ ITEM_VIDEO = 5;
427
+ MSG_TYPE_BOT = 2;
428
+ MSG_STATE_FINISH = 2;
429
+ MEDIA_IMAGE = 1;
430
+ MEDIA_VIDEO = 2;
431
+ MEDIA_FILE = 3;
432
+ MEDIA_VOICE = 4;
433
+ CDN_ALLOWLIST = /* @__PURE__ */ new Set([
434
+ "novac2c.cdn.weixin.qq.com",
435
+ "ilinkai.weixin.qq.com",
436
+ "wx.qlogo.cn",
437
+ "thirdwx.qlogo.cn",
438
+ "res.wx.qq.com",
439
+ "mmbiz.qpic.cn",
440
+ "mmbiz.qlogo.cn"
441
+ ]);
442
+ ContextTokenStore = class {
443
+ cache = /* @__PURE__ */ new Map();
444
+ filePath;
445
+ constructor(stateDir, accountId) {
446
+ this.filePath = path.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
447
+ }
448
+ k(a, p) {
449
+ return `${a}:${p}`;
450
+ }
451
+ restore() {
452
+ try {
453
+ if (fs.existsSync(this.filePath)) {
454
+ for (const [k, v] of Object.entries(JSON.parse(fs.readFileSync(this.filePath, "utf-8"))))
455
+ this.cache.set(k, String(v));
456
+ }
457
+ } catch {
458
+ }
459
+ }
460
+ save() {
461
+ try {
462
+ const dir = path.dirname(this.filePath);
463
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
464
+ const obj = {};
465
+ this.cache.forEach((v, k) => obj[k] = v);
466
+ fs.writeFileSync(this.filePath, JSON.stringify(obj));
467
+ } catch {
468
+ }
469
+ }
470
+ get(a, p) {
471
+ return p !== void 0 ? this.cache.get(this.k(a, p)) : this.cache.get(a);
472
+ }
473
+ set(a, pOrT, t) {
474
+ if (t !== void 0) this.cache.set(this.k(a, pOrT), t);
475
+ else this.cache.set(a, pOrT);
476
+ this.save();
477
+ }
478
+ delete(a, p) {
479
+ const key = p !== void 0 ? this.k(a, p) : a;
480
+ this.cache.delete(key);
481
+ this.save();
482
+ }
483
+ };
484
+ MessageDeduplicator = class {
485
+ ids = /* @__PURE__ */ new Map();
486
+ ttlMs;
487
+ constructor(ttlMs = 3e5) {
488
+ this.ttlMs = ttlMs;
489
+ }
490
+ isDuplicate(id) {
491
+ const now = Date.now();
492
+ for (const [k, ts] of this.ids) {
493
+ if (now - ts > this.ttlMs) this.ids.delete(k);
494
+ }
495
+ if (this.ids.has(id)) return true;
496
+ this.ids.set(id, now);
497
+ return false;
498
+ }
499
+ };
500
+ WechatAdapter = class {
501
+ name = "wechat";
502
+ suppressToolDisplay = true;
503
+ // 微信不支持 tool display(rate limit 太严)
504
+ config;
505
+ messageHandler = null;
506
+ tokenStore;
507
+ dedup = new MessageDeduplicator();
508
+ running = false;
509
+ connected = false;
510
+ typingTasks = /* @__PURE__ */ new Map();
511
+ lastSendAt = 0;
512
+ // 全局发送节流
513
+ typingTicketCache = /* @__PURE__ */ new Map();
514
+ // typing ticket 缓存(10min TTL)
515
+ baseUrl;
516
+ cdnBaseUrl;
517
+ stateDir;
518
+ constructor(config) {
519
+ this.config = config;
520
+ this.baseUrl = config.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
521
+ this.cdnBaseUrl = config.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
522
+ this.stateDir = config.stateDir || path.join(os.homedir?.() || "/tmp", ".engine7");
523
+ if (!fs.existsSync(this.stateDir)) fs.mkdirSync(this.stateDir, { recursive: true });
524
+ }
525
+ // --- ChannelAdapter interface ---
526
+ onMessage(handler) {
527
+ this.messageHandler = handler;
528
+ }
529
+ async connect() {
530
+ if (!this.config.token) throw new Error("WechatAdapter: token is required");
531
+ if (!this.config.accountId) throw new Error("WechatAdapter: accountId is required");
532
+ this.tokenStore = new ContextTokenStore(this.stateDir, this.config.accountId);
533
+ this.tokenStore.restore();
534
+ this.running = true;
535
+ this.connected = true;
536
+ this.pollLoop().catch((err) => {
537
+ console.error(`[wechat] poll loop crashed: ${err.message}`);
538
+ this.connected = false;
539
+ });
540
+ console.log(`[wechat] Connected account=${safeId(this.config.accountId)} base=${this.baseUrl}`);
541
+ if (this.config.groupPolicy && this.config.groupPolicy !== "disabled") {
542
+ console.warn(`[wechat] groupPolicy=${this.config.groupPolicy} \u2014 iLink bot accounts typically cannot join ordinary WeChat groups`);
543
+ }
544
+ }
545
+ async disconnect() {
546
+ this.running = false;
547
+ this.connected = false;
548
+ console.log(`[wechat] Disconnected`);
549
+ }
550
+ // --- Streaming preview(微信没有编辑API,只在finish时发一次) ---
551
+ async sendPreview(channelId, _content, _agentName) {
552
+ this.previewSent = false;
553
+ return { channelId, messageId: `preview-${Date.now()}` };
554
+ }
555
+ previewSent = false;
556
+ // 防止 freeze() 重复触发发送
557
+ async editPreview(_handle, _content, _agentName, _isFinal) {
558
+ }
559
+ async deletePreview(_handle) {
560
+ }
561
+ async send(target, message, options) {
562
+ const formatted = formatForWechat(message);
563
+ if (!formatted.trim()) return;
564
+ const elapsed = Date.now() - this.lastSendAt;
565
+ if (elapsed < 3e3) await sleep2(3e3 - elapsed);
566
+ const chunks = splitText(formatted, MAX_MESSAGE_LENGTH);
567
+ const contextToken = this.tokenStore.get(this.config.accountId, target);
568
+ for (let i = 0; i < chunks.length; i++) {
569
+ await this.sendTextChunk(target, chunks[i], contextToken);
570
+ this.lastSendAt = Date.now();
571
+ if (i < chunks.length - 1) await sleep2(SEND_CHUNK_DELAY_MS);
572
+ }
573
+ }
574
+ async sendFile(target, message, attachment) {
575
+ const filePath = attachment.path;
576
+ if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
577
+ const plaintext = fs.readFileSync(filePath);
578
+ const mediaType = this.guessMediaType(filePath, attachment.mimeType);
579
+ const filekey = crypto.randomBytes(16).toString("hex");
580
+ const aesKey = crypto.randomBytes(16);
581
+ const rawsize = plaintext.length;
582
+ const rawfilemd5 = crypto.createHash("md5").update(plaintext).digest("hex");
583
+ const ciphertext = aesEncrypt(plaintext, aesKey);
584
+ const aeskeyHex = aesKey.toString("hex");
585
+ const uploadResp = await apiPost(this.baseUrl, EP_GET_UPLOAD_URL, {
586
+ to_user_id: target,
587
+ media_type: mediaType,
588
+ filekey,
589
+ rawsize,
590
+ rawfilemd5,
591
+ filesize: ciphertext.length,
592
+ aeskey: aeskeyHex,
593
+ no_need_thumb: true
594
+ }, this.config.token, API_TIMEOUT_MS);
595
+ const uploadFullUrl = String(uploadResp.upload_full_url || "");
596
+ const uploadParam = String(uploadResp.upload_param || "");
597
+ console.log(`[wechat] sendFile getUploadUrl: mediaType=${mediaType} size=${rawsize} uploadFullUrl=${uploadFullUrl ? "YES" : "NO"} uploadParam=${uploadParam ? uploadParam.slice(0, 40) + "..." : "NO"} resp=${JSON.stringify(uploadResp).slice(0, 200)}`);
598
+ if (!uploadFullUrl && !uploadParam) {
599
+ throw new Error(`getUploadUrl returned neither upload_param nor upload_full_url: ${JSON.stringify(uploadResp).slice(0, 200)}`);
600
+ }
601
+ const uploadUrl = uploadFullUrl || cdnUploadUrl(this.cdnBaseUrl, uploadParam, filekey);
602
+ const encryptedQueryParam = await uploadCiphertext(uploadUrl, ciphertext);
603
+ const aesKeyForApi = Buffer.from(aeskeyHex, "ascii").toString("base64");
604
+ const mediaItem = this.buildMediaItem(mediaType, {
605
+ encryptedQueryParam,
606
+ aesKeyForApi,
607
+ ciphertextSize: ciphertext.length,
608
+ plaintextSize: rawsize,
609
+ filename: path.basename(filePath),
610
+ rawfilemd5,
611
+ voiceDurationSec: attachment.voiceDurationSec
612
+ });
613
+ if (message && message.trim()) {
614
+ const contextToken2 = this.tokenStore.get(target);
615
+ const clientId2 = `engine-weixin-${crypto.randomUUID().replace(/-/g, "")}`;
616
+ await apiPost(this.baseUrl, EP_SEND_MESSAGE, {
617
+ msg: {
618
+ from_user_id: "",
619
+ to_user_id: target,
620
+ client_id: clientId2,
621
+ message_type: MSG_TYPE_BOT,
622
+ message_state: MSG_STATE_FINISH,
623
+ item_list: [{ type: ITEM_TEXT, text_item: { text: this.formatMessage(message) } }],
624
+ ...contextToken2 ? { context_token: contextToken2 } : {}
625
+ }
626
+ }, this.config.token, API_TIMEOUT_MS);
627
+ }
628
+ const clientId = `engine-weixin-${crypto.randomUUID().replace(/-/g, "")}`;
629
+ const contextToken = this.tokenStore.get(target);
630
+ await apiPost(this.baseUrl, EP_SEND_MESSAGE, {
631
+ msg: {
632
+ from_user_id: "",
633
+ to_user_id: target,
634
+ client_id: clientId,
635
+ message_type: MSG_TYPE_BOT,
636
+ message_state: MSG_STATE_FINISH,
637
+ item_list: [mediaItem],
638
+ ...contextToken ? { context_token: contextToken } : {}
639
+ }
640
+ }, this.config.token, API_TIMEOUT_MS);
641
+ }
642
+ // --- Typing indicator ---
643
+ getTypingTicket(userId) {
644
+ const entry = this.typingTicketCache.get(userId);
645
+ if (!entry) return void 0;
646
+ if (Date.now() >= entry.expires) {
647
+ this.typingTicketCache.delete(userId);
648
+ return void 0;
649
+ }
650
+ return entry.ticket;
651
+ }
652
+ async fetchTypingTicket(target) {
653
+ if (!this.connected) return void 0;
654
+ try {
655
+ const ctxToken = this.tokenStore.get(this.config.accountId, target);
656
+ const payload = { ilink_user_id: target };
657
+ if (ctxToken) payload.context_token = ctxToken;
658
+ const resp = await apiPost(this.baseUrl, EP_GET_CONFIG, payload, this.config.token, 1e4);
659
+ const ticket = String(resp.typing_ticket || "");
660
+ if (ticket) {
661
+ this.typingTicketCache.set(target, { ticket, expires: Date.now() + 6e5 });
662
+ }
663
+ return ticket || void 0;
664
+ } catch {
665
+ return void 0;
666
+ }
667
+ }
668
+ async sendTypingIndicator(target, status) {
669
+ if (!this.connected) return;
670
+ try {
671
+ let ticket = this.getTypingTicket(target);
672
+ if (!ticket) ticket = await this.fetchTypingTicket(target);
673
+ if (!ticket) return;
674
+ await apiPost(this.baseUrl, EP_SEND_TYPING, {
675
+ ilink_user_id: target,
676
+ typing_ticket: ticket,
677
+ status
678
+ }, this.config.token, 1e4);
679
+ } catch {
680
+ }
681
+ }
682
+ startTyping(channelId) {
683
+ if (this.typingTasks.has(channelId)) return;
684
+ this.sendTypingIndicator(channelId, 1).catch(() => {
685
+ });
686
+ const timer = setInterval(() => {
687
+ const state = this.typingTasks.get(channelId);
688
+ if (!state || state.paused) return;
689
+ this.sendTypingIndicator(channelId, 1).catch(() => {
690
+ this.stopTyping(channelId);
691
+ });
692
+ }, 8e3);
693
+ this.typingTasks.set(channelId, { timer, paused: false });
694
+ }
695
+ async stopTyping(channelId) {
696
+ const state = this.typingTasks.get(channelId);
697
+ if (!state) return;
698
+ clearInterval(state.timer);
699
+ this.typingTasks.delete(channelId);
700
+ await this.sendTypingIndicator(channelId, 2);
701
+ }
702
+ pauseTyping(channelId) {
703
+ const state = this.typingTasks.get(channelId);
704
+ if (state) state.paused = true;
705
+ }
706
+ resumeTyping(channelId) {
707
+ const state = this.typingTasks.get(channelId);
708
+ if (state) {
709
+ state.paused = false;
710
+ this.sendTypingIndicator(channelId, 1).catch(() => {
711
+ });
712
+ }
713
+ }
714
+ // --- Internal: poll loop ---
715
+ async pollLoop() {
716
+ let syncBuf = loadSyncBuf(this.stateDir, this.config.accountId);
717
+ let timeoutMs = LONG_POLL_TIMEOUT_MS;
718
+ let consecutiveFailures = 0;
719
+ let disconnected = false;
720
+ while (this.running) {
721
+ try {
722
+ const response = await apiPost(this.baseUrl, EP_GET_UPDATES, {
723
+ get_updates_buf: syncBuf
724
+ }, this.config.token, timeoutMs);
725
+ const suggestedTimeout = response.longpolling_timeout_ms;
726
+ if (typeof suggestedTimeout === "number" && suggestedTimeout > 0) timeoutMs = suggestedTimeout;
727
+ const ret = response.ret ?? 0;
728
+ const errcode = response.errcode ?? 0;
729
+ if (ret !== 0 || errcode !== 0) {
730
+ if (isSessionExpired(ret, errcode, response.errmsg)) {
731
+ console.error(`[wechat] Session expired; pausing 10 min`);
732
+ await sleep2(6e5);
733
+ consecutiveFailures = 0;
734
+ continue;
735
+ }
736
+ consecutiveFailures++;
737
+ if (consecutiveFailures >= DISCONNECTED_THRESHOLD && !disconnected) {
738
+ disconnected = true;
739
+ console.warn(`[wechat] \u26A0\uFE0F network disconnected \u2014 poll failing continuously`);
740
+ }
741
+ console.warn(`[wechat] getUpdates failed ret=${ret} errcode=${errcode} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
742
+ await sleep2(consecutiveFailures >= MAX_CONSECUTIVE_FAILURES ? BACKOFF_DELAY_MS : RETRY_DELAY_MS);
743
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) consecutiveFailures = 0;
744
+ continue;
745
+ }
746
+ if (disconnected) {
747
+ disconnected = false;
748
+ console.log(`[wechat] \u2705 network recovered \u2014 poll resumed successfully`);
749
+ }
750
+ consecutiveFailures = 0;
751
+ const newSyncBuf = String(response.get_updates_buf || "");
752
+ if (newSyncBuf) {
753
+ syncBuf = newSyncBuf;
754
+ saveSyncBuf(this.stateDir, this.config.accountId, syncBuf);
755
+ }
756
+ for (const message of response.msgs || []) {
757
+ this.processMessage(message).catch((err) => {
758
+ console.error(`[wechat] processMessage error from=${safeId(String(message.from_user_id || ""))}: ${err.message}`);
759
+ });
760
+ }
761
+ } catch (err) {
762
+ if (err.name === "AbortError") continue;
763
+ consecutiveFailures++;
764
+ if (consecutiveFailures >= DISCONNECTED_THRESHOLD && !disconnected) {
765
+ disconnected = true;
766
+ console.warn(`[wechat] \u26A0\uFE0F network disconnected \u2014 poll failing continuously`);
767
+ }
768
+ console.error(`[wechat] poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${err.message}`);
769
+ if (disconnected) {
770
+ const host = new URL(this.baseUrl).hostname;
771
+ while (this.running && !await isNetworkUp(host)) {
772
+ await sleep2(DISCONNECTED_POLL_INTERVAL);
773
+ }
774
+ console.log(`[wechat] DNS probe passed for ${host}, retrying poll...`);
775
+ } else {
776
+ await sleep2(consecutiveFailures >= MAX_CONSECUTIVE_FAILURES ? BACKOFF_DELAY_MS : RETRY_DELAY_MS);
777
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) consecutiveFailures = 0;
778
+ }
779
+ }
780
+ }
781
+ }
782
+ async processMessage(message) {
783
+ const senderId = String(message.from_user_id || "").trim();
784
+ if (!senderId || senderId === this.config.accountId) return;
785
+ const messageId = String(message.message_id || "").trim();
786
+ if (messageId && this.dedup.isDuplicate(messageId)) return;
787
+ const itemList = message.item_list || [];
788
+ const text = extractText(itemList);
789
+ if (text) {
790
+ const contentKey = `content:${senderId}:${crypto.createHash("md5").update(text).digest("hex")}`;
791
+ if (this.dedup.isDuplicate(contentKey)) return;
792
+ }
793
+ const { type: chatType, chatId } = guessChatType(message, this.config.accountId);
794
+ if (chatType === "group") {
795
+ if (this.config.groupPolicy === "disabled") return;
796
+ if (this.config.groupPolicy === "allowlist" && !(this.config.groupAllowFrom || []).includes(chatId)) return;
797
+ } else {
798
+ if (!this.isDmAllowed(senderId)) return;
799
+ }
800
+ const contextToken = String(message.context_token || "").trim();
801
+ if (contextToken) this.tokenStore.set(senderId, contextToken);
802
+ const attachments = [];
803
+ for (const item of itemList) {
804
+ const media = await this.downloadMediaItem(item).catch((err) => {
805
+ console.warn(`[wechat] media download failed: ${err.message}`);
806
+ return null;
807
+ });
808
+ if (media) attachments.push(media);
809
+ }
810
+ if (!text && attachments.length === 0) return;
811
+ console.log(`[wechat] inbound from=${safeId(senderId)} type=${chatType} media=${attachments.length}`);
812
+ this.messageHandler?.({
813
+ content: text,
814
+ from: senderId,
815
+ fromName: senderId,
816
+ channel_id: chatId,
817
+ channel: "wechat",
818
+ channelType: chatType,
819
+ isBot: false,
820
+ messageId: messageId || void 0,
821
+ recvAt: Date.now(),
822
+ attachments: attachments.length > 0 ? attachments : void 0
823
+ });
824
+ }
825
+ isDmAllowed(senderId) {
826
+ if (this.config.dmPolicy === "disabled") return false;
827
+ if (this.config.dmPolicy === "allowlist") return (this.config.allowFrom || []).includes(senderId);
828
+ return true;
829
+ }
830
+ async downloadMediaItem(item) {
831
+ const itemType = item.type;
832
+ let mediaRef = {};
833
+ let filename = "media.bin";
834
+ let contentType = "application/octet-stream";
835
+ let timeoutSec = 60;
836
+ if (itemType === ITEM_IMAGE) {
837
+ mediaRef = (item.image_item || {}).media || {};
838
+ filename = "image.jpg";
839
+ contentType = "image/jpeg";
840
+ timeoutSec = 30;
841
+ } else if (itemType === ITEM_VIDEO) {
842
+ mediaRef = (item.video_item || {}).media || {};
843
+ filename = "video.mp4";
844
+ contentType = "video/mp4";
845
+ timeoutSec = 120;
846
+ } else if (itemType === ITEM_FILE) {
847
+ const fileItem = item.file_item || {};
848
+ mediaRef = fileItem.media || {};
849
+ filename = String(fileItem.file_name || "document.bin");
850
+ contentType = mimeFromFilename(filename);
851
+ timeoutSec = 60;
852
+ } else if (itemType === ITEM_VOICE) {
853
+ mediaRef = (item.voice_item || {}).media || {};
854
+ if ((item.voice_item || {}).text) return null;
855
+ filename = "voice.silk";
856
+ contentType = "audio/silk";
857
+ timeoutSec = 60;
858
+ } else {
859
+ return null;
860
+ }
861
+ const data = await downloadAndDecryptMedia(
862
+ this.cdnBaseUrl,
863
+ mediaRef.encrypt_query_param,
864
+ mediaRef.aes_key,
865
+ mediaRef.full_url,
866
+ timeoutSec
867
+ );
868
+ const tmpDir = path.join(os.tmpdir(), "weixin-media");
869
+ if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
870
+ const tmpPath = path.join(tmpDir, `${crypto.randomUUID().replace(/-/g, "")}-${filename}`);
871
+ fs.writeFileSync(tmpPath, data);
872
+ return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
873
+ }
874
+ // --- Internal: send helpers ---
875
+ async sendTextChunk(target, text, contextToken) {
876
+ let lastError = null;
877
+ let retriedWithoutToken = false;
878
+ let currentToken = contextToken;
879
+ for (let attempt = 0; attempt <= SEND_CHUNK_RETRIES; attempt++) {
880
+ try {
881
+ const clientId = `engine-weixin-${crypto.randomUUID().replace(/-/g, "")}`;
882
+ const resp = await apiPost(this.baseUrl, EP_SEND_MESSAGE, {
883
+ msg: {
884
+ from_user_id: "",
885
+ to_user_id: target,
886
+ client_id: clientId,
887
+ message_type: MSG_TYPE_BOT,
888
+ message_state: MSG_STATE_FINISH,
889
+ item_list: [{ type: ITEM_TEXT, text_item: { text } }],
890
+ ...currentToken ? { context_token: currentToken } : {}
891
+ }
892
+ }, this.config.token, API_TIMEOUT_MS);
893
+ const ret = resp.ret ?? 0;
894
+ const errcode = resp.errcode ?? 0;
895
+ if (ret !== 0 || errcode !== 0) {
896
+ if (isSessionExpired(ret, errcode, resp.errmsg) && !retriedWithoutToken && currentToken) {
897
+ retriedWithoutToken = true;
898
+ currentToken = void 0;
899
+ this.tokenStore.delete(target);
900
+ console.warn(`[wechat] session expired for ${safeId(target)}; retrying without context_token`);
901
+ continue;
902
+ }
903
+ if (ret === RATE_LIMIT_ERRCODE || errcode === RATE_LIMIT_ERRCODE) {
904
+ lastError = new Error(`iLink rate limited: ret=${ret} errcode=${errcode}`);
905
+ if (attempt >= SEND_CHUNK_RETRIES) break;
906
+ await sleep2(5e3);
907
+ continue;
908
+ }
909
+ throw new Error(`iLink sendmessage error: ret=${ret} errcode=${errcode} errmsg=${resp.errmsg || ""}`);
910
+ }
911
+ return;
912
+ } catch (err) {
913
+ lastError = err;
914
+ if (attempt >= SEND_CHUNK_RETRIES) break;
915
+ await sleep2(SEND_CHUNK_RETRY_DELAY_MS * (attempt + 1));
916
+ }
917
+ }
918
+ throw lastError;
919
+ }
920
+ guessMediaType(filePath, mimeType) {
921
+ const ext = path.extname(filePath).toLowerCase();
922
+ const mime = mimeType || mimeFromFilename(filePath);
923
+ if (mime.startsWith("image/")) return MEDIA_IMAGE;
924
+ if (mime.startsWith("video/")) return MEDIA_VIDEO;
925
+ if (ext === ".silk") return MEDIA_VOICE;
926
+ return MEDIA_FILE;
927
+ }
928
+ buildMediaItem(mediaType, params) {
929
+ const media = {
930
+ encrypt_query_param: params.encryptedQueryParam,
931
+ aes_key: params.aesKeyForApi,
932
+ encrypt_type: 1
933
+ };
934
+ if (mediaType === MEDIA_IMAGE) {
935
+ return { type: ITEM_IMAGE, image_item: { media, mid_size: params.ciphertextSize } };
936
+ }
937
+ if (mediaType === MEDIA_VIDEO) {
938
+ return { type: ITEM_VIDEO, video_item: { media, video_size: params.ciphertextSize, video_md5: params.rawfilemd5 } };
939
+ }
940
+ if (mediaType === MEDIA_VOICE) {
941
+ const voiceItem = { media, encode_type: 6, sample_rate: 24e3, bits_per_sample: 16 };
942
+ if (typeof params.voiceDurationSec === "number" && params.voiceDurationSec > 0) {
943
+ voiceItem.playtime = Math.round(params.voiceDurationSec * 1e3);
944
+ }
945
+ return { type: ITEM_VOICE, voice_item: voiceItem };
946
+ }
947
+ return { type: ITEM_FILE, file_item: { media, file_name: params.filename, len: String(params.plaintextSize) } };
948
+ }
949
+ formatMessage(content) {
950
+ return formatForWechat(content);
951
+ }
952
+ splitText(text) {
953
+ return splitText(text, MAX_MESSAGE_LENGTH);
954
+ }
955
+ };
956
+ EP_GET_BOT_QR = "ilink/bot/get_bot_qrcode";
957
+ EP_GET_QR_STATUS = "ilink/bot/get_qrcode_status";
958
+ QR_TIMEOUT_MS = 15e3;
959
+ }
960
+ });
961
+
17
962
  // src/cli-travel.ts
18
963
  var cli_travel_exports = {};
19
964
  __export(cli_travel_exports, {
@@ -22,20 +967,20 @@ __export(cli_travel_exports, {
22
967
  loadTravelConfig: () => loadTravelConfig,
23
968
  saveTravelConfig: () => saveTravelConfig
24
969
  });
25
- import * as path from "node:path";
26
- import * as fs from "node:fs";
27
- import * as os from "node:os";
970
+ import * as path2 from "node:path";
971
+ import * as fs2 from "node:fs";
972
+ import * as os2 from "node:os";
28
973
  import { execSync } from "node:child_process";
29
974
  function loadTravelConfig() {
30
975
  try {
31
- if (!fs.existsSync(CONFIG_PATH)) return null;
32
- return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
976
+ if (!fs2.existsSync(CONFIG_PATH)) return null;
977
+ return JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf-8"));
33
978
  } catch {
34
979
  return null;
35
980
  }
36
981
  }
37
982
  function saveTravelConfig(cfg) {
38
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
983
+ fs2.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
39
984
  }
40
985
  function sanitizeContent(content, dirs) {
41
986
  let result = content;
@@ -60,7 +1005,7 @@ function restoreContent(content, dirs) {
60
1005
  return result;
61
1006
  }
62
1007
  function isTextFile(filePath) {
63
- const ext = path.extname(filePath).toLowerCase();
1008
+ const ext = path2.extname(filePath).toLowerCase();
64
1009
  return TEXT_EXTENSIONS.has(ext);
65
1010
  }
66
1011
  function shouldExclude(name) {
@@ -75,14 +1020,14 @@ function shouldExclude(name) {
75
1020
  }
76
1021
  function collectFiles(rootDir, includeSet, optionalSet) {
77
1022
  const files = [];
78
- const entries = fs.readdirSync(rootDir, { withFileTypes: true });
1023
+ const entries = fs2.readdirSync(rootDir, { withFileTypes: true });
79
1024
  for (const entry of entries) {
80
1025
  if (shouldExclude(entry.name)) continue;
81
- const fullPath = path.join(rootDir, entry.name);
82
- const realPath = fs.realpathSync(fullPath);
1026
+ const fullPath = path2.join(rootDir, entry.name);
1027
+ const realPath = fs2.realpathSync(fullPath);
83
1028
  if (realPath !== fullPath) {
84
1029
  try {
85
- const lstat = fs.lstatSync(fullPath);
1030
+ const lstat = fs2.lstatSync(fullPath);
86
1031
  if (lstat.isSymbolicLink()) {
87
1032
  continue;
88
1033
  }
@@ -103,12 +1048,12 @@ function collectFiles(rootDir, includeSet, optionalSet) {
103
1048
  function collectAllFiles(dir) {
104
1049
  const files = [];
105
1050
  try {
106
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1051
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
107
1052
  for (const entry of entries) {
108
1053
  if (shouldExclude(entry.name)) continue;
109
- const fullPath = path.join(dir, entry.name);
1054
+ const fullPath = path2.join(dir, entry.name);
110
1055
  try {
111
- if (fs.lstatSync(fullPath).isSymbolicLink()) continue;
1056
+ if (fs2.lstatSync(fullPath).isSymbolicLink()) continue;
112
1057
  } catch {
113
1058
  }
114
1059
  if (entry.isDirectory()) {
@@ -123,30 +1068,30 @@ function collectAllFiles(dir) {
123
1068
  }
124
1069
  function readMainEngineUuid(agentsDir) {
125
1070
  try {
126
- const platformMapPath = path.join(agentsDir, "main", "sessions", "platform-map.json");
127
- const indexMapPath = path.join(agentsDir, "main", "sessions", "session-index.json");
128
- if (!fs.existsSync(platformMapPath) || !fs.existsSync(indexMapPath)) return null;
129
- const platformMap = JSON.parse(fs.readFileSync(platformMapPath, "utf-8"));
1071
+ const platformMapPath = path2.join(agentsDir, "main", "sessions", "platform-map.json");
1072
+ const indexMapPath = path2.join(agentsDir, "main", "sessions", "session-index.json");
1073
+ if (!fs2.existsSync(platformMapPath) || !fs2.existsSync(indexMapPath)) return null;
1074
+ const platformMap = JSON.parse(fs2.readFileSync(platformMapPath, "utf-8"));
130
1075
  const mainPlatformId = platformMap["scope:main"] || platformMap["main"];
131
1076
  if (!mainPlatformId) return null;
132
- const indexMap = JSON.parse(fs.readFileSync(indexMapPath, "utf-8"));
1077
+ const indexMap = JSON.parse(fs2.readFileSync(indexMapPath, "utf-8"));
133
1078
  const entry = indexMap[mainPlatformId];
134
1079
  if (!entry || !entry.file) return null;
135
- const basename3 = path.basename(entry.file).replace(/\.jsonl$/, "");
136
- return basename3;
1080
+ const basename4 = path2.basename(entry.file).replace(/\.jsonl$/, "");
1081
+ return basename4;
137
1082
  } catch {
138
1083
  return null;
139
1084
  }
140
1085
  }
141
1086
  function collectRecentSessions(agentsDir, _days = 7) {
142
1087
  const files = [];
143
- if (!fs.existsSync(agentsDir)) return files;
1088
+ if (!fs2.existsSync(agentsDir)) return files;
144
1089
  const sessionGroups = /* @__PURE__ */ new Map();
145
1090
  function scanSessionsDir(dir) {
146
1091
  try {
147
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1092
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
148
1093
  for (const entry of entries) {
149
- const fullPath = path.join(dir, entry.name);
1094
+ const fullPath = path2.join(dir, entry.name);
150
1095
  if (entry.isDirectory()) {
151
1096
  scanSessionsDir(fullPath);
152
1097
  continue;
@@ -170,7 +1115,7 @@ function collectRecentSessions(agentsDir, _days = 7) {
170
1115
  if (entry.name.endsWith(".jsonl")) {
171
1116
  group.jsonl = fullPath;
172
1117
  } else {
173
- const stat = fs.statSync(fullPath);
1118
+ const stat = fs2.statSync(fullPath);
174
1119
  group.archived.push({ file: fullPath, mtime: stat.mtimeMs });
175
1120
  }
176
1121
  }
@@ -189,37 +1134,37 @@ function collectRecentSessions(agentsDir, _days = 7) {
189
1134
  }
190
1135
  async function doExport(opts) {
191
1136
  const { agentName, stateDir, note, dryRun: dryRun2 } = opts;
192
- const workspace = (opts.workspace || path.join(stateDir, "workspace")).replace(/[/\\]+$/, "");
193
- const engineHome = path.join(os.homedir(), ".engine7").replace(/[/\\]+$/, "");
1137
+ const workspace = (opts.workspace || path2.join(stateDir, "workspace")).replace(/[/\\]+$/, "");
1138
+ const engineHome = path2.join(os2.homedir(), ".engine7").replace(/[/\\]+$/, "");
194
1139
  console.log(`\u{1F4E6} engine7 export`);
195
1140
  console.log(` agent: ${agentName}`);
196
1141
  console.log(` state: ${stateDir}`);
197
1142
  console.log(` workspace: ${workspace}`);
198
- if (!fs.existsSync(workspace)) {
1143
+ if (!fs2.existsSync(workspace)) {
199
1144
  console.error(`\u274C workspace \u4E0D\u5B58\u5728: ${workspace}`);
200
1145
  process.exit(1);
201
1146
  }
202
1147
  const dirs = { workspace, stateDir, engineHome };
203
1148
  const wsFiles = collectFiles(workspace, WORKSPACE_INCLUDE, WORKSPACE_OPTIONAL);
204
- const sessionFiles = collectRecentSessions(path.join(stateDir, "agents"), 7);
205
- const configsDir = path.join(stateDir, "configs");
1149
+ const sessionFiles = collectRecentSessions(path2.join(stateDir, "agents"), 7);
1150
+ const configsDir = path2.join(stateDir, "configs");
206
1151
  const configFiles = [];
207
- if (fs.existsSync(configsDir)) {
1152
+ if (fs2.existsSync(configsDir)) {
208
1153
  for (const f of collectAllFiles(configsDir)) {
209
- if (shouldExclude(path.basename(f))) continue;
1154
+ if (shouldExclude(path2.basename(f))) continue;
210
1155
  configFiles.push(f);
211
1156
  }
212
1157
  }
213
1158
  const EVEROS_SKIP = /* @__PURE__ */ new Set([".tmp", ".lock", "import.log", "import_progress.json"]);
214
- const everosDir = path.join(stateDir, ".everos");
1159
+ const everosDir = path2.join(stateDir, ".everos");
215
1160
  const everosFiles = [];
216
- if (fs.existsSync(everosDir)) {
1161
+ if (fs2.existsSync(everosDir)) {
217
1162
  for (const f of collectAllFiles(everosDir)) {
218
- if (EVEROS_SKIP.has(path.basename(f))) continue;
1163
+ if (EVEROS_SKIP.has(path2.basename(f))) continue;
219
1164
  everosFiles.push(f);
220
1165
  }
221
- const walFile = path.join(everosDir, ".index", "sqlite", "system.db-wal");
222
- if (fs.existsSync(walFile) && fs.statSync(walFile).size > 0) {
1166
+ const walFile = path2.join(everosDir, ".index", "sqlite", "system.db-wal");
1167
+ if (fs2.existsSync(walFile) && fs2.statSync(walFile).size > 0) {
223
1168
  console.warn(`\u26A0\uFE0F .everos/system.db-wal \u975E\u7A7A\u2014\u2014everos \u670D\u52A1\u53EF\u80FD\u6B63\u5728\u5199\u5165\uFF0C\u5FEB\u7167\u53EF\u80FD\u4E0D\u4E00\u81F4`);
224
1169
  console.warn(` \u5EFA\u8BAE: \u5148\u505C everos \u670D\u52A1\u518D export\uFF0C\u6216 import \u540E\u91CD\u5EFA\u7D22\u5F15`);
225
1170
  }
@@ -227,16 +1172,16 @@ async function doExport(opts) {
227
1172
  console.log(` workspace \u6587\u4EF6: ${wsFiles.length}`);
228
1173
  console.log(` session jsonl: ${sessionFiles.length} (\u6700\u8FD17\u5929)`);
229
1174
  console.log(` configs: ${configFiles.length}`);
230
- console.log(` everos: ${everosFiles.length} (${(everosFiles.reduce((s, f) => s + fs.statSync(f).size, 0) / 1024 / 1024).toFixed(1)} MB)`);
1175
+ console.log(` everos: ${everosFiles.length} (${(everosFiles.reduce((s, f) => s + fs2.statSync(f).size, 0) / 1024 / 1024).toFixed(1)} MB)`);
231
1176
  if (dryRun2) {
232
1177
  console.log(`
233
1178
  [DRY RUN] \u4F1A\u6253\u5305\u4EE5\u4E0B\u6587\u4EF6:`);
234
- wsFiles.slice(0, 20).forEach((f) => console.log(` ${path.relative(workspace, f)}`));
1179
+ wsFiles.slice(0, 20).forEach((f) => console.log(` ${path2.relative(workspace, f)}`));
235
1180
  if (wsFiles.length > 20) console.log(` ... \u8FD8\u6709 ${wsFiles.length - 20} \u4E2A\u6587\u4EF6`);
236
1181
  if (everosFiles.length > 0) {
237
1182
  console.log(`
238
1183
  [DRY RUN] everos (${everosFiles.length} \u6587\u4EF6):`);
239
- everosFiles.slice(0, 10).forEach((f) => console.log(` ${path.relative(stateDir, f)}`));
1184
+ everosFiles.slice(0, 10).forEach((f) => console.log(` ${path2.relative(stateDir, f)}`));
240
1185
  if (everosFiles.length > 10) console.log(` ... \u8FD8\u6709 ${everosFiles.length - 10} \u4E2A\u6587\u4EF6`);
241
1186
  }
242
1187
  console.log(`
@@ -245,46 +1190,46 @@ async function doExport(opts) {
245
1190
  }
246
1191
  const now = /* @__PURE__ */ new Date();
247
1192
  const version = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
248
- const tmpDir = path.join(os.tmpdir(), `engine7-export-${agentName}-${version}`);
249
- fs.mkdirSync(tmpDir, { recursive: true });
250
- const stagingDir = path.join(tmpDir, agentName);
251
- fs.mkdirSync(stagingDir, { recursive: true });
1193
+ const tmpDir = path2.join(os2.tmpdir(), `engine7-export-${agentName}-${version}`);
1194
+ fs2.mkdirSync(tmpDir, { recursive: true });
1195
+ const stagingDir = path2.join(tmpDir, agentName);
1196
+ fs2.mkdirSync(stagingDir, { recursive: true });
252
1197
  let fileCount = 0;
253
1198
  let totalSize = 0;
254
1199
  let copyFail = 0;
255
1200
  const copyOne = (srcFile, relRoot, destRoot) => {
256
- const relPath = path.relative(relRoot, srcFile);
257
- const destFile = path.join(destRoot, relPath);
258
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
1201
+ const relPath = path2.relative(relRoot, srcFile);
1202
+ const destFile = path2.join(destRoot, relPath);
1203
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
259
1204
  try {
260
1205
  if (isTextFile(srcFile)) {
261
- const content = fs.readFileSync(srcFile, "utf-8");
262
- fs.writeFileSync(destFile, sanitizeContent(content, dirs));
1206
+ const content = fs2.readFileSync(srcFile, "utf-8");
1207
+ fs2.writeFileSync(destFile, sanitizeContent(content, dirs));
263
1208
  } else {
264
- fs.copyFileSync(srcFile, destFile);
1209
+ fs2.copyFileSync(srcFile, destFile);
265
1210
  }
266
1211
  fileCount++;
267
- totalSize += fs.statSync(srcFile).size;
1212
+ totalSize += fs2.statSync(srcFile).size;
268
1213
  } catch (e) {
269
1214
  copyFail++;
270
1215
  console.warn(` \u26A0\uFE0F \u62F7\u8D1D\u5931\u8D25: ${relPath} (${e.code || e.message})`);
271
1216
  }
272
1217
  };
273
- for (const srcFile of wsFiles) copyOne(srcFile, workspace, path.join(stagingDir, "workspace"));
1218
+ for (const srcFile of wsFiles) copyOne(srcFile, workspace, path2.join(stagingDir, "workspace"));
274
1219
  for (const srcFile of sessionFiles) {
275
- const relPath = path.relative(stateDir, srcFile);
276
- const destFile = path.join(stagingDir, relPath);
277
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
1220
+ const relPath = path2.relative(stateDir, srcFile);
1221
+ const destFile = path2.join(stagingDir, relPath);
1222
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
278
1223
  try {
279
- let content = fs.readFileSync(srcFile, "utf-8");
1224
+ let content = fs2.readFileSync(srcFile, "utf-8");
280
1225
  content = sanitizeContent(content, dirs);
281
- const baseName = path.basename(srcFile);
1226
+ const baseName = path2.basename(srcFile);
282
1227
  if (baseName === "session-index.json" || baseName === "platform-map.json") {
283
1228
  content = content.split("\\\\").join("/");
284
1229
  }
285
- fs.writeFileSync(destFile, content);
1230
+ fs2.writeFileSync(destFile, content);
286
1231
  fileCount++;
287
- totalSize += fs.statSync(srcFile).size;
1232
+ totalSize += fs2.statSync(srcFile).size;
288
1233
  } catch (e) {
289
1234
  copyFail++;
290
1235
  console.warn(` \u26A0\uFE0F \u62F7\u8D1D\u5931\u8D25: ${relPath} (${e.code || e.message})`);
@@ -306,13 +1251,13 @@ async function doExport(opts) {
306
1251
  totalSize,
307
1252
  note
308
1253
  };
309
- fs.writeFileSync(path.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2));
310
- const archiveFile = path.join(tmpDir, `${agentName}-${version}.tar.gz`);
1254
+ fs2.writeFileSync(path2.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2));
1255
+ const archiveFile = path2.join(tmpDir, `${agentName}-${version}.tar.gz`);
311
1256
  console.log(`
312
1257
  \u{1F5DC}\uFE0F \u6253\u5305\u4E2D...`);
313
1258
  const tarExe = process.platform === "win32" ? "C:\\Windows\\System32\\tar.exe" : "tar";
314
1259
  execSync(`"${tarExe}" -czf "${archiveFile}" -C "${tmpDir}" ${agentName}`, { stdio: "pipe" });
315
- const archiveSize = fs.statSync(archiveFile).size;
1260
+ const archiveSize = fs2.statSync(archiveFile).size;
316
1261
  console.log(`\u2705 \u6253\u5305\u5B8C\u6210: ${archiveFile} (${(archiveSize / 1024 / 1024).toFixed(1)} MB, ${fileCount} \u6587\u4EF6${copyFail ? `, \u5931\u8D25 ${copyFail}` : ""})`);
317
1262
  const cfg = loadTravelConfig();
318
1263
  if (!cfg || !cfg.githubToken) {
@@ -335,8 +1280,8 @@ async function doImport(opts) {
335
1280
  console.error(` \u914D\u7F6E: \u7F16\u8F91 ${CONFIG_PATH}`);
336
1281
  process.exit(1);
337
1282
  }
338
- const tmpDir = path.join(os.tmpdir(), `engine7-import-${agentName}-${Date.now()}`);
339
- fs.mkdirSync(tmpDir, { recursive: true });
1283
+ const tmpDir = path2.join(os2.tmpdir(), `engine7-import-${agentName}-${Date.now()}`);
1284
+ fs2.mkdirSync(tmpDir, { recursive: true });
340
1285
  const archiveFile = await downloadFromGitHub(cfg, agentName, version, tmpDir);
341
1286
  console.log(`\u2705 \u4E0B\u8F7D\u5B8C\u6210: ${archiveFile}`);
342
1287
  if (dryRun2) {
@@ -347,99 +1292,99 @@ async function doImport(opts) {
347
1292
  console.log(`\u{1F4C2} \u89E3\u5305\u4E2D...`);
348
1293
  const tarExe = process.platform === "win32" ? "C:\\Windows\\System32\\tar.exe" : "tar";
349
1294
  execSync(`"${tarExe}" -xzf "${archiveFile}" -C "${tmpDir}"`, { stdio: "pipe" });
350
- const stagingDir = path.join(tmpDir, agentName);
351
- const manifestPath = path.join(stagingDir, "manifest.json");
352
- if (!fs.existsSync(manifestPath)) {
1295
+ const stagingDir = path2.join(tmpDir, agentName);
1296
+ const manifestPath = path2.join(stagingDir, "manifest.json");
1297
+ if (!fs2.existsSync(manifestPath)) {
353
1298
  console.error(`\u274C manifest.json \u4E0D\u5B58\u5728\uFF0C\u6587\u4EF6\u53EF\u80FD\u635F\u574F`);
354
1299
  process.exit(1);
355
1300
  }
356
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
357
- const workspace = path.join(stateDir, "workspace").replace(/\\/g, "/").replace(/[/\\]+$/, "");
358
- const engineHome = path.join(os.homedir(), ".engine7").replace(/\\/g, "/").replace(/[/\\]+$/, "");
1301
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1302
+ const workspace = path2.join(stateDir, "workspace").replace(/\\/g, "/").replace(/[/\\]+$/, "");
1303
+ const engineHome = path2.join(os2.homedir(), ".engine7").replace(/\\/g, "/").replace(/[/\\]+$/, "");
359
1304
  const dirs = { workspace, stateDir: stateDir.replace(/\\/g, "/").replace(/[/\\]+$/, ""), engineHome };
360
1305
  console.log(` \u7248\u672C: ${manifest.version}`);
361
1306
  console.log(` \u521B\u5EFA: ${manifest.createdAt}`);
362
1307
  console.log(` \u6587\u4EF6\u6570: ${manifest.fileCount}`);
363
- const wsStaging = path.join(stagingDir, "workspace");
1308
+ const wsStaging = path2.join(stagingDir, "workspace");
364
1309
  let restoredCount = 0;
365
1310
  let skipCount = 0;
366
- if (fs.existsSync(wsStaging)) {
1311
+ if (fs2.existsSync(wsStaging)) {
367
1312
  const allFiles = collectAllFiles(wsStaging);
368
1313
  for (const srcFile of allFiles) {
369
- const relPath = path.relative(wsStaging, srcFile);
370
- const destFile = path.join(workspace, relPath);
371
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
1314
+ const relPath = path2.relative(wsStaging, srcFile);
1315
+ const destFile = path2.join(workspace, relPath);
1316
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
372
1317
  try {
373
1318
  if (isTextFile(srcFile)) {
374
- const content = fs.readFileSync(srcFile, "utf-8");
375
- fs.writeFileSync(destFile, restoreContent(content, dirs));
1319
+ const content = fs2.readFileSync(srcFile, "utf-8");
1320
+ fs2.writeFileSync(destFile, restoreContent(content, dirs));
376
1321
  } else {
377
- fs.copyFileSync(srcFile, destFile);
1322
+ fs2.copyFileSync(srcFile, destFile);
378
1323
  }
379
1324
  restoredCount++;
380
1325
  } catch (e) {
381
1326
  skipCount++;
382
- console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path.relative(wsStaging, srcFile)} (${e.code || e.message})`);
1327
+ console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path2.relative(wsStaging, srcFile)} (${e.code || e.message})`);
383
1328
  }
384
1329
  }
385
1330
  }
386
- const agentsStaging = path.join(stagingDir, "agents");
387
- if (fs.existsSync(agentsStaging)) {
1331
+ const agentsStaging = path2.join(stagingDir, "agents");
1332
+ if (fs2.existsSync(agentsStaging)) {
388
1333
  const sessionFiles = collectAllFiles(agentsStaging);
389
1334
  for (const srcFile of sessionFiles) {
390
- const relPath = path.relative(stagingDir, srcFile);
391
- const destFile = path.join(stateDir, relPath);
392
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
393
- const content = fs.readFileSync(srcFile, "utf-8");
394
- fs.writeFileSync(destFile, restoreContent(content, dirs));
1335
+ const relPath = path2.relative(stagingDir, srcFile);
1336
+ const destFile = path2.join(stateDir, relPath);
1337
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
1338
+ const content = fs2.readFileSync(srcFile, "utf-8");
1339
+ fs2.writeFileSync(destFile, restoreContent(content, dirs));
395
1340
  restoredCount++;
396
1341
  }
397
1342
  }
398
- const configsStaging = path.join(stagingDir, "configs");
399
- if (fs.existsSync(configsStaging)) {
1343
+ const configsStaging = path2.join(stagingDir, "configs");
1344
+ if (fs2.existsSync(configsStaging)) {
400
1345
  const cfgFiles = collectAllFiles(configsStaging);
401
1346
  for (const srcFile of cfgFiles) {
402
- const relPath = path.relative(stagingDir, srcFile);
403
- const destFile = path.join(stateDir, relPath);
404
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
1347
+ const relPath = path2.relative(stagingDir, srcFile);
1348
+ const destFile = path2.join(stateDir, relPath);
1349
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
405
1350
  if (isTextFile(srcFile)) {
406
- const content = fs.readFileSync(srcFile, "utf-8");
407
- fs.writeFileSync(destFile, restoreContent(content, dirs));
1351
+ const content = fs2.readFileSync(srcFile, "utf-8");
1352
+ fs2.writeFileSync(destFile, restoreContent(content, dirs));
408
1353
  } else {
409
- fs.copyFileSync(srcFile, destFile);
1354
+ fs2.copyFileSync(srcFile, destFile);
410
1355
  }
411
1356
  restoredCount++;
412
1357
  }
413
1358
  }
414
- const everosStaging = path.join(stagingDir, ".everos");
415
- if (fs.existsSync(everosStaging)) {
1359
+ const everosStaging = path2.join(stagingDir, ".everos");
1360
+ if (fs2.existsSync(everosStaging)) {
416
1361
  const everosFiles = collectAllFiles(everosStaging);
417
1362
  for (const srcFile of everosFiles) {
418
- const relPath = path.relative(stagingDir, srcFile);
419
- const destFile = path.join(stateDir, relPath);
420
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
1363
+ const relPath = path2.relative(stagingDir, srcFile);
1364
+ const destFile = path2.join(stateDir, relPath);
1365
+ fs2.mkdirSync(path2.dirname(destFile), { recursive: true });
421
1366
  try {
422
1367
  if (isTextFile(srcFile)) {
423
- const content = fs.readFileSync(srcFile, "utf-8");
424
- fs.writeFileSync(destFile, restoreContent(content, dirs));
1368
+ const content = fs2.readFileSync(srcFile, "utf-8");
1369
+ fs2.writeFileSync(destFile, restoreContent(content, dirs));
425
1370
  } else {
426
- fs.copyFileSync(srcFile, destFile);
1371
+ fs2.copyFileSync(srcFile, destFile);
427
1372
  }
428
1373
  restoredCount++;
429
1374
  } catch (e) {
430
1375
  skipCount++;
431
- console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path.relative(everosStaging, srcFile)} (${e.code || e.message})`);
1376
+ console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path2.relative(everosStaging, srcFile)} (${e.code || e.message})`);
432
1377
  }
433
1378
  }
434
1379
  console.log(` everos: ${everosFiles.length} \u6587\u4EF6\u5DF2\u6062\u590D`);
435
1380
  }
436
1381
  console.log(`\u2705 \u6062\u590D\u5B8C\u6210: ${restoredCount} \u6587\u4EF6 \u2192 ${stateDir}${skipCount ? ` (\u8DF3\u8FC7 ${skipCount})` : ""}`);
437
1382
  const platformConfigName = process.platform === "darwin" ? "xiaoke-mac.json" : "xiaoke-win.json";
438
- const restoredConfigPath = path.join(stateDir, "configs", platformConfigName);
439
- const configToUse = fs.existsSync(restoredConfigPath) ? restoredConfigPath : path.join(stateDir, "configs", "xiaoke.json");
1383
+ const restoredConfigPath = path2.join(stateDir, "configs", platformConfigName);
1384
+ const configToUse = fs2.existsSync(restoredConfigPath) ? restoredConfigPath : path2.join(stateDir, "configs", "xiaoke.json");
440
1385
  console.log(`
441
1386
  \u{1F4A1} \u4E0B\u4E00\u6B65: engine7 start --config "${configToUse}"`);
442
- fs.rmSync(tmpDir, { recursive: true, force: true });
1387
+ fs2.rmSync(tmpDir, { recursive: true, force: true });
443
1388
  }
444
1389
  async function uploadToGitHub(cfg, archiveFile, agentName, version, manifest) {
445
1390
  const repo = `${cfg.githubOwner}/${cfg.repoName}`;
@@ -469,8 +1414,8 @@ async function uploadToGitHub(cfg, archiveFile, agentName, version, manifest) {
469
1414
  }
470
1415
  const release = await createRes.json();
471
1416
  const uploadUrl = release.upload_url.replace("{?name,label}", "");
472
- const fileBuffer = fs.readFileSync(archiveFile);
473
- const fileName = path.basename(archiveFile);
1417
+ const fileBuffer = fs2.readFileSync(archiveFile);
1418
+ const fileName = path2.basename(archiveFile);
474
1419
  const uploadRes = await fetch(`${uploadUrl}?name=${fileName}`, {
475
1420
  method: "POST",
476
1421
  headers: {
@@ -493,7 +1438,7 @@ async function uploadToGitHub(cfg, archiveFile, agentName, version, manifest) {
493
1438
  throw e;
494
1439
  }
495
1440
  try {
496
- fs.rmSync(path.dirname(archiveFile), { recursive: true, force: true });
1441
+ fs2.rmSync(path2.dirname(archiveFile), { recursive: true, force: true });
497
1442
  } catch {
498
1443
  }
499
1444
  }
@@ -525,8 +1470,8 @@ async function downloadFromGitHub(cfg, agentName, version, destDir) {
525
1470
  throw new Error(`\u4E0B\u8F7D asset \u5931\u8D25: ${downloadRes.status}`);
526
1471
  }
527
1472
  const buffer = Buffer.from(await downloadRes.arrayBuffer());
528
- const archiveFile = path.join(destDir, asset.name);
529
- fs.writeFileSync(archiveFile, buffer);
1473
+ const archiveFile = path2.join(destDir, asset.name);
1474
+ fs2.writeFileSync(archiveFile, buffer);
530
1475
  return archiveFile;
531
1476
  }
532
1477
  async function getLatestReleaseTag(cfg, agentName) {
@@ -551,7 +1496,7 @@ var CONFIG_PATH, PATH_PLACEHOLDERS, WORKSPACE_INCLUDE, WORKSPACE_OPTIONAL, WORKS
551
1496
  var init_cli_travel = __esm({
552
1497
  "src/cli-travel.ts"() {
553
1498
  "use strict";
554
- CONFIG_PATH = path.join(os.homedir(), ".engine7-travel.json");
1499
+ CONFIG_PATH = path2.join(os2.homedir(), ".engine7-travel.json");
555
1500
  PATH_PLACEHOLDERS = [
556
1501
  { placeholder: "{{WORKSPACE}}", getOriginal: (d) => d.workspace },
557
1502
  { placeholder: "{{ENGINE_HOME}}", getOriginal: (d) => d.engineHome },
@@ -624,8 +1569,8 @@ var init_cli_travel = __esm({
624
1569
  });
625
1570
 
626
1571
  // src/cli-init.ts
627
- import * as path2 from "node:path";
628
- import * as fs2 from "node:fs";
1572
+ import * as path3 from "node:path";
1573
+ import * as fs3 from "node:fs";
629
1574
  import * as readline from "node:readline";
630
1575
  import { fileURLToPath } from "node:url";
631
1576
 
@@ -743,6 +1688,9 @@ function sleep(ms) {
743
1688
  return new Promise((resolve2) => setTimeout(resolve2, ms));
744
1689
  }
745
1690
 
1691
+ // src/cli-init.ts
1692
+ init_wechat();
1693
+
746
1694
  // src/qr-render.ts
747
1695
  import { createRequire } from "node:module";
748
1696
  var require2 = createRequire(import.meta.url);
@@ -761,7 +1709,7 @@ async function renderQrTerminal(url, options) {
761
1709
 
762
1710
  // src/cli-init.ts
763
1711
  var __filename = fileURLToPath(import.meta.url);
764
- var __dirname = path2.dirname(__filename);
1712
+ var __dirname = path3.dirname(__filename);
765
1713
  var SCHEMA_VERSION = 1;
766
1714
  function parseArgs() {
767
1715
  const args = process.argv.slice(2);
@@ -786,10 +1734,10 @@ function parseArgs() {
786
1734
  }
787
1735
  }
788
1736
  if (!stateDir) {
789
- stateDir = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
1737
+ stateDir = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
790
1738
  console.log(` \u4F7F\u7528\u9ED8\u8BA4\u76EE\u5F55: ${stateDir}`);
791
1739
  }
792
- stateDir = path2.resolve(stateDir);
1740
+ stateDir = path3.resolve(stateDir);
793
1741
  return { stateDir, quick, dryRun: dryRun2, force };
794
1742
  }
795
1743
  function printHelp() {
@@ -798,6 +1746,7 @@ Engine 7 \u2014 Self-hosted AI agent engine
798
1746
 
799
1747
  \u7528\u6CD5:
800
1748
  engine7 init --state-dir <path> [\u9009\u9879] \u521D\u59CB\u5316 agent \u5DE5\u4F5C\u76EE\u5F55
1749
+ engine7 addchannel wechat [\u9009\u9879] \u7ED9\u5DF2\u88C5\u7684 agent \u52A0\u901A\u9053\uFF08\u626B\u7801\uFF0C\u4E0D\u91CD\u8DD1 init\uFF09
801
1750
  engine7 start [--config <path>] \u542F\u52A8 Engine
802
1751
  engine7 restart [--config <path>] \u91CD\u542F Engine\uFF08\u6740\u65E7\u8FDB\u7A0B+\u542F\u52A8\uFF09
803
1752
  engine7 service install|uninstall|status \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
@@ -860,6 +1809,7 @@ function getDefaultValues(stateDir) {
860
1809
  stateDir,
861
1810
  agentName: "Agent",
862
1811
  primaryProvider: "dashscope",
1812
+ zhipuPlan: "",
863
1813
  primaryApiKey: "",
864
1814
  primaryModel: "dashscope/qwen3.7-max",
865
1815
  visionModel: "dashscope/qwen3.7-max",
@@ -870,6 +1820,9 @@ function getDefaultValues(stateDir) {
870
1820
  feishuAppId: "",
871
1821
  feishuAppSecret: "",
872
1822
  feishuOpenId: "",
1823
+ wechatEnabled: false,
1824
+ wechatToken: "",
1825
+ wechatAccountId: "",
873
1826
  tavilyKey: "",
874
1827
  apiPort: 16990
875
1828
  };
@@ -887,6 +1840,12 @@ async function interactiveConfig(rl, defaults) {
887
1840
  };
888
1841
  const chosenProvider = await askChoice(rl, "\u4E3B\u6A21\u578B Provider:", providers, 0);
889
1842
  v.primaryProvider = providerMap[chosenProvider] || "dashscope";
1843
+ v.zhipuPlan = "";
1844
+ if (v.primaryProvider === "zhipu") {
1845
+ const planOptions = ["coding-plan (GLM Coding \u8BA2\u9605)", "token-plan (\u666E\u901A\u6309\u91CF/TokenPlan)"];
1846
+ const chosenPlan = await askChoice(rl, "\u667A\u8C31\u8BA2\u9605\u7C7B\u578B:", planOptions, 0);
1847
+ v.zhipuPlan = chosenPlan.startsWith("coding") ? "coding" : "token";
1848
+ }
890
1849
  const keyMap = {
891
1850
  dashscope: "DashScope API Key",
892
1851
  minimax: "MiniMax API Key",
@@ -974,6 +1933,44 @@ async function interactiveConfig(rl, defaults) {
974
1933
  v.feishuOpenId = await ask(rl, "\u4F60\u7684\u98DE\u4E66 open_id\uFF08\u56DE\u8F66\u8DF3\u8FC7\uFF09", "");
975
1934
  }
976
1935
  }
1936
+ const wechatAns = await ask(rl, "\u542F\u7528\u5FAE\u4FE1? (y/n)", "n");
1937
+ v.wechatEnabled = wechatAns.toLowerCase() === "y";
1938
+ if (v.wechatEnabled) {
1939
+ console.log("\n \u5FAE\u4FE1\u63A5\u5165\u65B9\u5F0F\uFF1A");
1940
+ console.log(" 1. \u626B\u7801\u7ED1\u5B9A\uFF08\u63A8\u8350\uFF0C\u7528\u4F60\u81EA\u5DF1\u7684\u5FAE\u4FE1\u626B\u4E00\u4E0B\u5C31\u884C\uFF09");
1941
+ console.log(" 2. \u624B\u52A8\u8F93\u5165 token\uFF08\u5DF2\u6709\u51ED\u8BC1\u65F6\uFF09");
1942
+ const wechatMode = await ask(rl, "\u9009\u62E9 (1/2)", "1");
1943
+ if (wechatMode === "1") {
1944
+ console.log("\n\u{1F4DD} \u6B63\u5728\u751F\u6210\u5FAE\u4FE1\u4E8C\u7EF4\u7801...\n");
1945
+ console.log(" \u63D0\u793A\uFF1A\u4E00\u4E2A\u5FAE\u4FE1\u53F7\u53EA\u80FD\u7ED1\u4E00\u4E2A bot\uFF1Bbot \u4E0D\u8FDB\u7FA4\uFF08\u817E\u8BAF\u9650\u5236\uFF09\uFF0C\u79C1\u804A 1v1");
1946
+ const cred = await wechatQrLogin({ timeoutSeconds: 300, stateDir: v.stateDir });
1947
+ if (cred) {
1948
+ v.wechatToken = cred.token;
1949
+ v.wechatAccountId = cred.accountId;
1950
+ console.log(`
1951
+ \u2705 accountId: ${cred.accountId}`);
1952
+ console.log(` \u2705 \u51ED\u8BC1\u5DF2\u81EA\u52A8\u4FDD\u5B58\uFF0Cconfig \u5C06\u81EA\u52A8\u5199\u5165`);
1953
+ } else {
1954
+ console.log("\n \u26A0\uFE0F \u626B\u7801\u8D85\u65F6\u6216\u5931\u8D25");
1955
+ const retry = await ask(rl, "\u91CD\u8BD5\u626B\u7801? (y/n)", "y");
1956
+ if (retry.toLowerCase() === "y") {
1957
+ const cred2 = await wechatQrLogin({ timeoutSeconds: 300, stateDir: v.stateDir });
1958
+ if (cred2) {
1959
+ v.wechatToken = cred2.token;
1960
+ v.wechatAccountId = cred2.accountId;
1961
+ } else {
1962
+ console.log("\n \u26A0\uFE0F \u518D\u6B21\u5931\u8D25\uFF0C\u8DF3\u8FC7\u5FAE\u4FE1\uFF08\u4E4B\u540E\u53EF\u624B\u52A8\u914D\u7F6E\uFF09");
1963
+ v.wechatEnabled = false;
1964
+ }
1965
+ } else {
1966
+ v.wechatEnabled = false;
1967
+ }
1968
+ }
1969
+ } else {
1970
+ v.wechatToken = await ask(rl, "\u5FAE\u4FE1 token");
1971
+ v.wechatAccountId = await ask(rl, "\u5FAE\u4FE1 accountId");
1972
+ }
1973
+ }
977
1974
  const tavilyKey = await ask(rl, "Tavily API Key\uFF08\u8054\u7F51\u641C\u7D22\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09", "");
978
1975
  v.tavilyKey = tavilyKey;
979
1976
  const portAns = await ask(rl, "API \u7AEF\u53E3", String(defaults.apiPort));
@@ -981,7 +1978,7 @@ async function interactiveConfig(rl, defaults) {
981
1978
  return v;
982
1979
  }
983
1980
  function generateConfig(v) {
984
- const workspace = path2.join(v.stateDir, "workspace").replace(/\\/g, "/");
1981
+ const workspace = path3.join(v.stateDir, "workspace").replace(/\\/g, "/");
985
1982
  const config = {
986
1983
  schemaVersion: SCHEMA_VERSION,
987
1984
  stateDir: v.stateDir.replace(/\\/g, "/"),
@@ -1041,7 +2038,16 @@ function generateConfig(v) {
1041
2038
  connectionMode: "websocket",
1042
2039
  dmPolicy: "pairing",
1043
2040
  groupPolicy: "open"
1044
- }
2041
+ },
2042
+ ...v.wechatEnabled && v.wechatToken ? {
2043
+ wechat: {
2044
+ enabled: true,
2045
+ token: v.wechatToken,
2046
+ accountId: v.wechatAccountId,
2047
+ dmPolicy: "pairing",
2048
+ group: { policy: "disabled" }
2049
+ }
2050
+ } : {}
1045
2051
  },
1046
2052
  api: { port: v.apiPort },
1047
2053
  prompt: {
@@ -1080,7 +2086,7 @@ function generateConfig(v) {
1080
2086
  ]
1081
2087
  },
1082
2088
  zhipu: {
1083
- baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4 ",
2089
+ baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
1084
2090
  api: "openai-completions",
1085
2091
  models: [
1086
2092
  { id: "glm-5.1", name: "GLM-5.1", reasoning: true, input: ["text"], contextWindow: 204800, maxTokens: 131072 },
@@ -1098,8 +2104,12 @@ function generateConfig(v) {
1098
2104
  }
1099
2105
  };
1100
2106
  if (providerDefs[v.primaryProvider]) {
2107
+ const def = { ...providerDefs[v.primaryProvider] };
2108
+ if (v.primaryProvider === "zhipu" && v.zhipuPlan === "token") {
2109
+ def.baseUrl = "https://open.bigmodel.cn/api/paas/v4";
2110
+ }
1101
2111
  config.models.providers[v.primaryProvider] = {
1102
- ...providerDefs[v.primaryProvider],
2112
+ ...def,
1103
2113
  apiKey: v.primaryApiKey
1104
2114
  };
1105
2115
  }
@@ -1107,45 +2117,45 @@ function generateConfig(v) {
1107
2117
  }
1108
2118
  function buildDirTree(v) {
1109
2119
  const d = v.stateDir;
1110
- const w = path2.join(d, "workspace");
2120
+ const w = path3.join(d, "workspace");
1111
2121
  const now = (/* @__PURE__ */ new Date()).toLocaleString("sv-SE", { timeZone: "Asia/Shanghai", hour12: false });
1112
2122
  return [
1113
2123
  // 目录
1114
- { path: path2.join(d, "configs"), type: "dir" },
1115
- { path: path2.join(d, "state", "agents", "main", "sessions"), type: "dir" },
1116
- { path: path2.join(w, "prompts"), type: "dir" },
1117
- { path: path2.join(w, "memory", "daily"), type: "dir" },
1118
- { path: path2.join(w, "docs", "research"), type: "dir" },
1119
- { path: path2.join(w, "docs", "todo"), type: "dir" },
1120
- { path: path2.join(w, "docs", "decisions"), type: "dir" },
1121
- { path: path2.join(w, "docs", "knowledge"), type: "dir" },
1122
- { path: path2.join(w, "docs", "sop"), type: "dir" },
1123
- { path: path2.join(d, "logs"), type: "dir" },
1124
- { path: path2.join(d, "media", "inbound"), type: "dir" },
2124
+ { path: path3.join(d, "configs"), type: "dir" },
2125
+ { path: path3.join(d, "state", "agents", "main", "sessions"), type: "dir" },
2126
+ { path: path3.join(w, "prompts"), type: "dir" },
2127
+ { path: path3.join(w, "memory", "daily"), type: "dir" },
2128
+ { path: path3.join(w, "docs", "research"), type: "dir" },
2129
+ { path: path3.join(w, "docs", "todo"), type: "dir" },
2130
+ { path: path3.join(w, "docs", "decisions"), type: "dir" },
2131
+ { path: path3.join(w, "docs", "knowledge"), type: "dir" },
2132
+ { path: path3.join(w, "docs", "sop"), type: "dir" },
2133
+ { path: path3.join(d, "logs"), type: "dir" },
2134
+ { path: path3.join(d, "media", "inbound"), type: "dir" },
1125
2135
  // workspace 文件
1126
2136
  {
1127
- path: path2.join(w, "SESSION-STATE.md"),
2137
+ path: path3.join(w, "SESSION-STATE.md"),
1128
2138
  type: "file",
1129
2139
  content: readTemplate("workspace/SESSION-STATE.md").replace("{{CURRENT_TIME}}", now)
1130
2140
  },
1131
- { path: path2.join(w, "HEARTBEAT.md"), type: "file", content: readTemplate("workspace/HEARTBEAT.md") },
2141
+ { path: path3.join(w, "HEARTBEAT.md"), type: "file", content: readTemplate("workspace/HEARTBEAT.md") },
1132
2142
  {
1133
- path: path2.join(w, "SOUL.md"),
2143
+ path: path3.join(w, "SOUL.md"),
1134
2144
  type: "file",
1135
2145
  content: readTemplate("workspace/SOUL.md").replace(/\{\{AGENT_NAME\}\}/g, v.agentName)
1136
2146
  },
1137
- { path: path2.join(w, "AGENTS.md"), type: "file", content: readTemplate("workspace/AGENTS.md") },
2147
+ { path: path3.join(w, "AGENTS.md"), type: "file", content: readTemplate("workspace/AGENTS.md") },
1138
2148
  {
1139
- path: path2.join(w, "prompts", "contacts.md"),
2149
+ path: path3.join(w, "prompts", "contacts.md"),
1140
2150
  type: "file",
1141
2151
  content: readTemplate("workspace/prompts/contacts.md").replace("{{DISCORD_USER_ID}}", v.discordUserId || "YOUR_DISCORD_ID").replace("{{FEISHU_OPEN_ID}}", v.feishuOpenId || "YOUR_FEISHU_OPEN_ID")
1142
2152
  },
1143
- { path: path2.join(w, "prompts", "auto-memory-instructions.md"), type: "file", content: readTemplate("workspace/prompts/auto-memory-instructions.md") },
1144
- { path: path2.join(w, "MEMORY.md"), type: "file", content: "# MEMORY.md \u2014 \u8BB0\u5FC6\u6587\u4EF6\u7D22\u5F15\n\n> \u6700\u540E\u66F4\u65B0\uFF1A\u521D\u59CB\u5316\n" },
1145
- { path: path2.join(w, "USER.md"), type: "file", content: "# USER.md \u2014 \u7528\u6237\u4FE1\u606F\n\n\uFF08\u5728\u8FD9\u91CC\u8BB0\u5F55\u7528\u6237\u7684\u504F\u597D\u3001\u80CC\u666F\u7B49\uFF09\n" },
2153
+ { path: path3.join(w, "prompts", "auto-memory-instructions.md"), type: "file", content: readTemplate("workspace/prompts/auto-memory-instructions.md") },
2154
+ { path: path3.join(w, "MEMORY.md"), type: "file", content: "# MEMORY.md \u2014 \u8BB0\u5FC6\u6587\u4EF6\u7D22\u5F15\n\n> \u6700\u540E\u66F4\u65B0\uFF1A\u521D\u59CB\u5316\n" },
2155
+ { path: path3.join(w, "USER.md"), type: "file", content: "# USER.md \u2014 \u7528\u6237\u4FE1\u606F\n\n\uFF08\u5728\u8FD9\u91CC\u8BB0\u5F55\u7528\u6237\u7684\u504F\u597D\u3001\u80CC\u666F\u7B49\uFF09\n" },
1146
2156
  // package.json — 让 agent 目录成为独立 npm 项目根,防止 npm hoisting
1147
2157
  {
1148
- path: path2.join(d, "package.json"),
2158
+ path: path3.join(d, "package.json"),
1149
2159
  type: "file",
1150
2160
  content: JSON.stringify({
1151
2161
  name: v.agentName.toLowerCase().replace(/[^a-z0-9-]/g, "-") || "agent",
@@ -1156,7 +2166,7 @@ function buildDirTree(v) {
1156
2166
  },
1157
2167
  // 启动脚本(根据 OS 生成)
1158
2168
  ...process.platform === "win32" ? [{
1159
- path: path2.join(d, "start.cmd"),
2169
+ path: path3.join(d, "start.cmd"),
1160
2170
  type: "file",
1161
2171
  content: `@echo off
1162
2172
  rem Engine 7 startup script
@@ -1188,7 +2198,7 @@ if %ERRORLEVEL% NEQ 0 (
1188
2198
  )
1189
2199
  `
1190
2200
  }] : [{
1191
- path: path2.join(d, "start.sh"),
2201
+ path: path3.join(d, "start.sh"),
1192
2202
  type: "file",
1193
2203
  content: `#!/bin/bash
1194
2204
  # Engine 7 startup script
@@ -1221,13 +2231,13 @@ node "$ENGINE7_BIN" --engine-config configs/main7.json
1221
2231
  }
1222
2232
  function readTemplate(relativePath) {
1223
2233
  const candidates = [
1224
- path2.join(__dirname, "..", "templates", relativePath),
1225
- path2.join(__dirname, "..", "..", "templates", relativePath),
1226
- path2.join(process.cwd(), "templates", relativePath)
2234
+ path3.join(__dirname, "..", "templates", relativePath),
2235
+ path3.join(__dirname, "..", "..", "templates", relativePath),
2236
+ path3.join(process.cwd(), "templates", relativePath)
1227
2237
  ];
1228
2238
  for (const p of candidates) {
1229
- if (fs2.existsSync(p)) {
1230
- return fs2.readFileSync(p, "utf-8");
2239
+ if (fs3.existsSync(p)) {
2240
+ return fs3.readFileSync(p, "utf-8");
1231
2241
  }
1232
2242
  }
1233
2243
  throw new Error(`\u6A21\u677F\u6587\u4EF6\u4E0D\u5B58\u5728: ${relativePath}\uFF08\u627E\u4E86: ${candidates.join(", ")}\uFF09`);
@@ -1242,11 +2252,11 @@ function dryRun(entries) {
1242
2252
  function execute(entries) {
1243
2253
  for (const e of entries) {
1244
2254
  if (e.type === "dir") {
1245
- fs2.mkdirSync(e.path, { recursive: true });
2255
+ fs3.mkdirSync(e.path, { recursive: true });
1246
2256
  console.log(` \u{1F4C1} ${e.path}`);
1247
2257
  } else {
1248
- fs2.mkdirSync(path2.dirname(e.path), { recursive: true });
1249
- fs2.writeFileSync(e.path, e.content || "", "utf-8");
2258
+ fs3.mkdirSync(path3.dirname(e.path), { recursive: true });
2259
+ fs3.writeFileSync(e.path, e.content || "", "utf-8");
1250
2260
  console.log(` \u{1F4C4} ${e.path}`);
1251
2261
  }
1252
2262
  }
@@ -1262,6 +2272,68 @@ async function main() {
1262
2272
  printHelp();
1263
2273
  process.exit(0);
1264
2274
  }
2275
+ if (subcommand === "addchannel") {
2276
+ const channel = args[1] || "";
2277
+ let stateDir = "";
2278
+ let configName = "";
2279
+ for (let i = 1; i < args.length; i++) {
2280
+ if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i];
2281
+ else if (args[i] === "--config" && args[i + 1]) configName = args[++i];
2282
+ }
2283
+ if (!stateDir) stateDir = path3.resolve(process.cwd());
2284
+ if (channel === "wechat") {
2285
+ console.log("\u{1F4F1} \u5FAE\u4FE1\u626B\u7801\u63A5\u5165\uFF08iLink \u4E2A\u4EBA\u5FAE\u4FE1\uFF0C1v1 \u79C1\u804A\uFF09");
2286
+ console.log(" \u63D0\u793A\uFF1A\u4E00\u4E2A\u5FAE\u4FE1\u53F7\u53EA\u80FD\u7ED1\u4E00\u4E2A bot\uFF1Bbot \u4E0D\u8FDB\u7FA4\uFF08\u817E\u8BAF\u9650\u5236\uFF09\n");
2287
+ const { wechatQrLogin: wechatQrLogin2 } = await Promise.resolve().then(() => (init_wechat(), wechat_exports));
2288
+ const cred = await wechatQrLogin2({ timeoutSeconds: 300, stateDir });
2289
+ if (!cred) {
2290
+ console.error("\u274C \u626B\u7801\u8D85\u65F6\u6216\u5931\u8D25\uFF0C\u672A\u4FEE\u6539\u4EFB\u4F55\u914D\u7F6E");
2291
+ process.exit(1);
2292
+ }
2293
+ const configsDir = path3.join(stateDir, "configs");
2294
+ let configFile = "";
2295
+ if (configName) {
2296
+ configFile = path3.join(configsDir, configName);
2297
+ } else if (fs3.existsSync(configsDir)) {
2298
+ const candidates = fs3.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
2299
+ for (const f of candidates) {
2300
+ const full = path3.join(configsDir, f);
2301
+ try {
2302
+ const j2 = JSON.parse(fs3.readFileSync(full, "utf8"));
2303
+ if (j2.channels) {
2304
+ configFile = full;
2305
+ break;
2306
+ }
2307
+ } catch {
2308
+ }
2309
+ }
2310
+ if (!configFile && candidates.length) configFile = path3.join(configsDir, candidates[0]);
2311
+ }
2312
+ if (!configFile || !fs3.existsSync(configFile)) {
2313
+ console.log("\n\u26A0\uFE0F \u6CA1\u627E\u5230 config \u6587\u4EF6\uFF0C\u8BF7\u624B\u52A8\u628A\u4EE5\u4E0B\u6BB5\u52A0\u8FDB\u4F60\u7684 config \u7684 channels \u91CC\uFF1A");
2314
+ console.log(JSON.stringify({ wechat: { enabled: true, token: cred.token, accountId: cred.accountId, dmPolicy: "pairing", group: { policy: "disabled" } } }, null, 2));
2315
+ process.exit(0);
2316
+ }
2317
+ const j = JSON.parse(fs3.readFileSync(configFile, "utf8"));
2318
+ j.channels = j.channels || {};
2319
+ j.channels.wechat = {
2320
+ enabled: true,
2321
+ token: cred.token,
2322
+ accountId: cred.accountId,
2323
+ dmPolicy: "pairing",
2324
+ group: { policy: "disabled" }
2325
+ };
2326
+ fs3.writeFileSync(configFile, JSON.stringify(j, null, 2), "utf8");
2327
+ console.log(`
2328
+ \u2705 \u5FAE\u4FE1\u901A\u9053\u5DF2\u5199\u5165: ${configFile}`);
2329
+ console.log(` accountId: ${cred.accountId}`);
2330
+ console.log("\n\u91CD\u542F engine \u751F\u6548: engine7 restart");
2331
+ process.exit(0);
2332
+ }
2333
+ console.error(`\u672A\u77E5\u901A\u9053: ${channel || "(\u7A7A)"}\u3002\u76EE\u524D\u652F\u6301: wechat`);
2334
+ console.error("\u7528\u6CD5: engine7 addchannel wechat [--state-dir <path>] [--config <file>]");
2335
+ process.exit(1);
2336
+ }
1265
2337
  if (subcommand === "export") {
1266
2338
  const { doExport: doExport2 } = await Promise.resolve().then(() => (init_cli_travel(), cli_travel_exports));
1267
2339
  let exportStateDir = "";
@@ -1274,8 +2346,8 @@ async function main() {
1274
2346
  else if (args[i] === "--note" && args[i + 1]) note = args[++i];
1275
2347
  else if (args[i] === "--dry-run") dryRun2 = true;
1276
2348
  }
1277
- if (!exportStateDir) exportStateDir = path2.resolve(process.cwd());
1278
- if (!agentName) agentName = path2.basename(exportStateDir);
2349
+ if (!exportStateDir) exportStateDir = path3.resolve(process.cwd());
2350
+ if (!agentName) agentName = path3.basename(exportStateDir);
1279
2351
  await doExport2({ agentName, stateDir: exportStateDir, note, dryRun: dryRun2 });
1280
2352
  process.exit(0);
1281
2353
  }
@@ -1295,7 +2367,7 @@ async function main() {
1295
2367
  console.error("\u274C import \u9700\u8981 --state-dir <path>");
1296
2368
  process.exit(1);
1297
2369
  }
1298
- if (!agentName) agentName = path2.basename(importStateDir);
2370
+ if (!agentName) agentName = path3.basename(importStateDir);
1299
2371
  await doImport2({ agentName, stateDir: importStateDir, version, dryRun: dryRun2 });
1300
2372
  process.exit(0);
1301
2373
  }
@@ -1320,18 +2392,18 @@ engine7 start \u2014 \u542F\u52A8 Engine
1320
2392
  }
1321
2393
  }
1322
2394
  if (!configPath2) {
1323
- const defaultCfg = path2.join("configs", "main7.json");
1324
- const homeCfg = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
1325
- if (fs2.existsSync(defaultCfg)) {
2395
+ const defaultCfg = path3.join("configs", "main7.json");
2396
+ const homeCfg = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
2397
+ if (fs3.existsSync(defaultCfg)) {
1326
2398
  configPath2 = defaultCfg;
1327
- } else if (fs2.existsSync(homeCfg)) {
2399
+ } else if (fs3.existsSync(homeCfg)) {
1328
2400
  configPath2 = homeCfg;
1329
2401
  console.log(` \u4F7F\u7528\u9ED8\u8BA4 config: ${homeCfg}`);
1330
2402
  } else {
1331
2403
  const ptr = readHomePointer();
1332
2404
  if (ptr?.stateDir) {
1333
- const ptrCfg = path2.join(ptr.stateDir, "configs", "main7.json");
1334
- if (fs2.existsSync(ptrCfg)) {
2405
+ const ptrCfg = path3.join(ptr.stateDir, "configs", "main7.json");
2406
+ if (fs3.existsSync(ptrCfg)) {
1335
2407
  configPath2 = ptrCfg;
1336
2408
  console.log(` \u4F7F\u7528 config: ${ptrCfg}`);
1337
2409
  }
@@ -1346,19 +2418,19 @@ engine7 start \u2014 \u542F\u52A8 Engine
1346
2418
  }
1347
2419
  const { execSync: execSync2, spawn } = await import("node:child_process");
1348
2420
  let enginePath;
1349
- const localPath = path2.join("node_modules", "engine7", "dist", "main.mjs");
1350
- if (fs2.existsSync(localPath)) {
2421
+ const localPath = path3.join("node_modules", "engine7", "dist", "main.mjs");
2422
+ if (fs3.existsSync(localPath)) {
1351
2423
  enginePath = localPath;
1352
2424
  } else {
1353
- const cliPath = path2.resolve(process.argv[1]);
1354
- const distDir = path2.dirname(cliPath);
1355
- const candidate = path2.join(distDir, "main.mjs");
1356
- if (fs2.existsSync(candidate)) {
2425
+ const cliPath = path3.resolve(process.argv[1]);
2426
+ const distDir = path3.dirname(cliPath);
2427
+ const candidate = path3.join(distDir, "main.mjs");
2428
+ if (fs3.existsSync(candidate)) {
1357
2429
  enginePath = candidate;
1358
2430
  } else {
1359
2431
  try {
1360
2432
  const globalRoot = execSync2("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
1361
- enginePath = path2.join(globalRoot, "engine7", "dist", "main.mjs");
2433
+ enginePath = path3.join(globalRoot, "engine7", "dist", "main.mjs");
1362
2434
  } catch {
1363
2435
  console.error("\u274C \u627E\u4E0D\u5230 engine7 main.mjs");
1364
2436
  process.exit(1);
@@ -1368,7 +2440,7 @@ engine7 start \u2014 \u542F\u52A8 Engine
1368
2440
  console.log(label);
1369
2441
  console.log(` config: ${configPath2}`);
1370
2442
  console.log(` engine: ${enginePath}`);
1371
- const configName = path2.basename(configPath2);
2443
+ const configName = path3.basename(configPath2);
1372
2444
  const myPid = process.pid;
1373
2445
  try {
1374
2446
  if (process.platform === "win32") {
@@ -1386,16 +2458,16 @@ engine7 start \u2014 \u542F\u52A8 Engine
1386
2458
  }
1387
2459
  console.log("");
1388
2460
  const envForChild = { ...process.env };
1389
- const secretsDir = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
1390
- const cfgBase = path2.basename(configPath2, ".json");
2461
+ const secretsDir = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
2462
+ const cfgBase = path3.basename(configPath2, ".json");
1391
2463
  const secretCandidates = [
1392
- path2.join(secretsDir, `${cfgBase}.env`),
1393
- path2.join(path2.dirname(configPath2), `.env.${cfgBase}`),
1394
- path2.join(path2.dirname(configPath2), ".env")
2464
+ path3.join(secretsDir, `${cfgBase}.env`),
2465
+ path3.join(path3.dirname(configPath2), `.env.${cfgBase}`),
2466
+ path3.join(path3.dirname(configPath2), ".env")
1395
2467
  ];
1396
2468
  for (const secretPath of secretCandidates) {
1397
- if (fs2.existsSync(secretPath)) {
1398
- const content = fs2.readFileSync(secretPath, "utf-8");
2469
+ if (fs3.existsSync(secretPath)) {
2470
+ const content = fs3.readFileSync(secretPath, "utf-8");
1399
2471
  for (const line of content.split("\n")) {
1400
2472
  const trimmed = line.trim();
1401
2473
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1469,14 +2541,14 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
1469
2541
  console.error(`\u274C \u672A\u77E5\u6A21\u5F0F: ${mode}\uFF08\u53EF\u9009: archive / drop-last / strip-images\uFF09`);
1470
2542
  process.exit(1);
1471
2543
  }
1472
- const configRaw = JSON.parse(fs2.readFileSync(configPath2, "utf-8"));
2544
+ const configRaw = JSON.parse(fs3.readFileSync(configPath2, "utf-8"));
1473
2545
  const stateDir = configRaw.stateDir;
1474
2546
  if (!stateDir) {
1475
2547
  console.error("\u274C config \u91CC\u6CA1\u6709 stateDir");
1476
2548
  process.exit(1);
1477
2549
  }
1478
- const sessionsDir = path2.join(stateDir, "agents", "main", "sessions");
1479
- if (!fs2.existsSync(sessionsDir)) {
2550
+ const sessionsDir = path3.join(stateDir, "agents", "main", "sessions");
2551
+ if (!fs3.existsSync(sessionsDir)) {
1480
2552
  console.error(`\u274C sessions \u76EE\u5F55\u4E0D\u5B58\u5728: ${sessionsDir}`);
1481
2553
  process.exit(1);
1482
2554
  }
@@ -1484,37 +2556,37 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
1484
2556
  console.log(` config: ${configPath2}`);
1485
2557
  console.log(` mode: ${mode}`);
1486
2558
  if (mode === "drop-last") console.log(` n: ${dropN}`);
1487
- const platformMapPath = path2.join(sessionsDir, "platform-map.json");
1488
- const indexPath = path2.join(sessionsDir, "session-index.json");
2559
+ const platformMapPath = path3.join(sessionsDir, "platform-map.json");
2560
+ const indexPath = path3.join(sessionsDir, "session-index.json");
1489
2561
  let sessionFileUUID = null;
1490
- if (fs2.existsSync(indexPath)) {
1491
- const index = JSON.parse(fs2.readFileSync(indexPath, "utf-8"));
2562
+ if (fs3.existsSync(indexPath)) {
2563
+ const index = JSON.parse(fs3.readFileSync(indexPath, "utf-8"));
1492
2564
  const entries = Object.values(index);
1493
2565
  if (entries.length > 0) {
1494
2566
  const fileVal = entries[0].file || "";
1495
- sessionFileUUID = path2.isAbsolute(fileVal) ? path2.basename(fileVal, ".jsonl") : fileVal.replace(".jsonl", "");
2567
+ sessionFileUUID = path3.isAbsolute(fileVal) ? path3.basename(fileVal, ".jsonl") : fileVal.replace(".jsonl", "");
1496
2568
  }
1497
2569
  }
1498
2570
  if (!sessionFileUUID) {
1499
- const jsonlFiles = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs2.statSync(path2.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2571
+ const jsonlFiles = fs3.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs3.statSync(path3.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
1500
2572
  if (jsonlFiles.length === 0) {
1501
2573
  console.error("\u274C \u627E\u4E0D\u5230\u6D3B\u8DC3\u7684 session JSONL \u6587\u4EF6");
1502
2574
  process.exit(1);
1503
2575
  }
1504
2576
  sessionFileUUID = jsonlFiles[0].name.replace(".jsonl", "");
1505
2577
  }
1506
- let jsonlPath = path2.join(sessionsDir, `${sessionFileUUID}.jsonl`);
1507
- if (!fs2.existsSync(jsonlPath)) {
1508
- const jsonlFiles = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs2.statSync(path2.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2578
+ let jsonlPath = path3.join(sessionsDir, `${sessionFileUUID}.jsonl`);
2579
+ if (!fs3.existsSync(jsonlPath)) {
2580
+ const jsonlFiles = fs3.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs3.statSync(path3.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
1509
2581
  if (jsonlFiles.length === 0) {
1510
2582
  console.error(`\u274C JSONL \u6587\u4EF6\u4E0D\u5B58\u5728: ${jsonlPath}`);
1511
2583
  process.exit(1);
1512
2584
  }
1513
2585
  sessionFileUUID = jsonlFiles[0].name.replace(".jsonl", "");
1514
- jsonlPath = path2.join(sessionsDir, `${sessionFileUUID}.jsonl`);
2586
+ jsonlPath = path3.join(sessionsDir, `${sessionFileUUID}.jsonl`);
1515
2587
  }
1516
2588
  console.log(` file: ${jsonlPath}`);
1517
- const configName = path2.basename(configPath2);
2589
+ const configName = path3.basename(configPath2);
1518
2590
  if (autoRestart) {
1519
2591
  try {
1520
2592
  const myPid = process.pid;
@@ -1530,15 +2602,15 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
1530
2602
  }
1531
2603
  }
1532
2604
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1533
- const { readFileSync: readFileSync3, writeFileSync: writeFileSync3, renameSync } = fs2;
2605
+ const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, renameSync } = fs3;
1534
2606
  if (mode === "archive") {
1535
2607
  const archiveName = `${sessionFileUUID}.jsonl.archived.${timestamp}`;
1536
- renameSync(jsonlPath, path2.join(sessionsDir, archiveName));
2608
+ renameSync(jsonlPath, path3.join(sessionsDir, archiveName));
1537
2609
  console.log(`
1538
2610
  \u2705 \u5F52\u6863\u5B8C\u6210: ${archiveName}`);
1539
2611
  console.log(` \u91CD\u542F\u540E\u5C06\u4ECE\u7A7A\u767D\u4F1A\u8BDD\u5F00\u59CB`);
1540
2612
  } else if (mode === "drop-last") {
1541
- const lines = readFileSync3(jsonlPath, "utf-8").trim().split("\n");
2613
+ const lines = readFileSync4(jsonlPath, "utf-8").trim().split("\n");
1542
2614
  const parsed = lines.map((l) => {
1543
2615
  try {
1544
2616
  return JSON.parse(l);
@@ -1557,12 +2629,12 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
1557
2629
  }
1558
2630
  const kept = parsed.slice(0, cutIndex);
1559
2631
  const newContent = kept.map((p) => JSON.stringify(p)).join("\n") + "\n";
1560
- writeFileSync3(jsonlPath, newContent);
2632
+ writeFileSync4(jsonlPath, newContent);
1561
2633
  console.log(`
1562
2634
  \u2705 \u780D\u6389\u6700\u8FD1 ${parsed.length - cutIndex} \u884C\uFF08${dropN} \u8F6E\uFF09`);
1563
2635
  console.log(` \u4FDD\u7559 ${kept.length} \u884C`);
1564
2636
  } else if (mode === "strip-images") {
1565
- const lines = readFileSync3(jsonlPath, "utf-8").trim().split("\n");
2637
+ const lines = readFileSync4(jsonlPath, "utf-8").trim().split("\n");
1566
2638
  let stripped = 0;
1567
2639
  const newLines = lines.map((line) => {
1568
2640
  try {
@@ -1603,17 +2675,17 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
1603
2675
  return line;
1604
2676
  }
1605
2677
  });
1606
- writeFileSync3(jsonlPath, newLines.join("\n") + "\n");
2678
+ writeFileSync4(jsonlPath, newLines.join("\n") + "\n");
1607
2679
  console.log(`
1608
2680
  \u2705 \u6458\u9664 ${stripped} \u884C\u56FE\u7247\u76F8\u5173\u5185\u5BB9`);
1609
2681
  }
1610
2682
  if (autoRestart) {
1611
- const realCliPath = fs2.realpathSync(process.argv[1]);
1612
- const cliDir = path2.dirname(realCliPath);
1613
- const pkgRoot = path2.resolve(cliDir, "..");
1614
- let enginePath = path2.join(pkgRoot, "dist", "main.mjs");
1615
- if (!fs2.existsSync(enginePath)) {
1616
- enginePath = path2.join(cliDir, "main.mjs");
2683
+ const realCliPath = fs3.realpathSync(process.argv[1]);
2684
+ const cliDir = path3.dirname(realCliPath);
2685
+ const pkgRoot = path3.resolve(cliDir, "..");
2686
+ let enginePath = path3.join(pkgRoot, "dist", "main.mjs");
2687
+ if (!fs3.existsSync(enginePath)) {
2688
+ enginePath = path3.join(cliDir, "main.mjs");
1617
2689
  }
1618
2690
  console.log(`
1619
2691
  \u{1F680} \u62C9\u8D77 engine...`);
@@ -1656,18 +2728,18 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
1656
2728
  else if (args[i] === "--cwd" && args[i + 1]) cwd = args[++i];
1657
2729
  }
1658
2730
  if (!configPath2) {
1659
- const defaultCfg = path2.join(cwd, "configs", "main7.json");
1660
- const homeCfg = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
1661
- if (fs2.existsSync(defaultCfg)) {
2731
+ const defaultCfg = path3.join(cwd, "configs", "main7.json");
2732
+ const homeCfg = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
2733
+ if (fs3.existsSync(defaultCfg)) {
1662
2734
  configPath2 = defaultCfg;
1663
- } else if (fs2.existsSync(homeCfg)) {
2735
+ } else if (fs3.existsSync(homeCfg)) {
1664
2736
  configPath2 = homeCfg;
1665
- cwd = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
2737
+ cwd = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
1666
2738
  } else {
1667
2739
  const ptr = readHomePointer();
1668
2740
  if (ptr?.stateDir) {
1669
- const ptrCfg = path2.join(ptr.stateDir, "configs", "main7.json");
1670
- if (fs2.existsSync(ptrCfg)) {
2741
+ const ptrCfg = path3.join(ptr.stateDir, "configs", "main7.json");
2742
+ if (fs3.existsSync(ptrCfg)) {
1671
2743
  configPath2 = ptrCfg;
1672
2744
  cwd = ptr.stateDir;
1673
2745
  }
@@ -1686,14 +2758,14 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
1686
2758
  if (process.platform === "win32") {
1687
2759
  const taskName = "Engine7";
1688
2760
  const nodeExe = process.execPath;
1689
- const cliMjs = path2.resolve(engine7Bin);
1690
- const absConfig = path2.resolve(configPath2);
1691
- const wrapperPath = path2.join(cwd, "engine7-start.cmd");
2761
+ const cliMjs = path3.resolve(engine7Bin);
2762
+ const absConfig = path3.resolve(configPath2);
2763
+ const wrapperPath = path3.join(cwd, "engine7-start.cmd");
1692
2764
  const wrapperContent = `@echo off\r
1693
2765
  cd /d "${cwd}"\r
1694
2766
  "${nodeExe}" "${cliMjs}" start --config "${absConfig}"\r
1695
2767
  `;
1696
- fs2.writeFileSync(wrapperPath, wrapperContent);
2768
+ fs3.writeFileSync(wrapperPath, wrapperContent);
1697
2769
  try {
1698
2770
  execSync2(`schtasks /create /tn "${taskName}" /tr "${wrapperPath}" /sc onlogon /rl highest /f`, { stdio: "inherit", shell: true });
1699
2771
  console.log(`\u2705 Windows \u8BA1\u5212\u4EFB\u52A1 "${taskName}" \u5DF2\u521B\u5EFA\uFF08\u5F00\u673A\u81EA\u542F\u52A8\uFF09`);
@@ -1704,11 +2776,11 @@ cd /d "${cwd}"\r
1704
2776
  }
1705
2777
  } else if (process.platform === "darwin") {
1706
2778
  const label = "com.engine7.agent";
1707
- const plistDir = path2.join(process.env.HOME, "Library", "LaunchAgents");
1708
- fs2.mkdirSync(plistDir, { recursive: true });
1709
- const plistPath = path2.join(plistDir, `${label}.plist`);
1710
- const localCli = path2.join("node_modules", "engine7", "dist", "cli.mjs");
1711
- const cliMjs = fs2.existsSync(localCli) ? path2.resolve(localCli) : path2.join(path2.dirname(path2.dirname(engine7Bin)), "lib", "node_modules", "engine7", "dist", "cli.mjs");
2779
+ const plistDir = path3.join(process.env.HOME, "Library", "LaunchAgents");
2780
+ fs3.mkdirSync(plistDir, { recursive: true });
2781
+ const plistPath = path3.join(plistDir, `${label}.plist`);
2782
+ const localCli = path3.join("node_modules", "engine7", "dist", "cli.mjs");
2783
+ const cliMjs = fs3.existsSync(localCli) ? path3.resolve(localCli) : path3.join(path3.dirname(path3.dirname(engine7Bin)), "lib", "node_modules", "engine7", "dist", "cli.mjs");
1712
2784
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
1713
2785
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1714
2786
  <plist version="1.0">
@@ -1720,14 +2792,14 @@ cd /d "${cwd}"\r
1720
2792
  <string>${cliMjs}</string>
1721
2793
  <string>start</string>
1722
2794
  <string>--config</string>
1723
- <string>${path2.resolve(configPath2)}</string>
2795
+ <string>${path3.resolve(configPath2)}</string>
1724
2796
  </array>
1725
2797
  <key>WorkingDirectory</key><string>${cwd}</string>
1726
2798
  <key>RunAtLoad</key><true/>
1727
2799
  <key>KeepAlive</key><true/>
1728
2800
  </dict>
1729
2801
  </plist>`;
1730
- fs2.writeFileSync(plistPath, plist);
2802
+ fs3.writeFileSync(plistPath, plist);
1731
2803
  try {
1732
2804
  execSync2(`launchctl load "${plistPath}"`, { stdio: "inherit" });
1733
2805
  console.log(`\u2705 Mac launchd \u670D\u52A1\u5DF2\u521B\u5EFA\uFF08\u5F00\u673A\u81EA\u542F\u52A8\uFF09`);
@@ -1738,9 +2810,9 @@ cd /d "${cwd}"\r
1738
2810
  }
1739
2811
  } else {
1740
2812
  const svcName = "engine7";
1741
- const svcDir = path2.join(process.env.HOME, ".config", "systemd", "user");
1742
- fs2.mkdirSync(svcDir, { recursive: true });
1743
- const svcPath = path2.join(svcDir, `${svcName}.service`);
2813
+ const svcDir = path3.join(process.env.HOME, ".config", "systemd", "user");
2814
+ fs3.mkdirSync(svcDir, { recursive: true });
2815
+ const svcPath = path3.join(svcDir, `${svcName}.service`);
1744
2816
  const svc = `[Unit]
1745
2817
  Description=Engine 7 Agent
1746
2818
  After=network.target
@@ -1748,13 +2820,13 @@ After=network.target
1748
2820
  [Service]
1749
2821
  Type=simple
1750
2822
  WorkingDirectory=${cwd}
1751
- ExecStart=${process.execPath} ${engine7Bin} start --config ${path2.resolve(configPath2)}
2823
+ ExecStart=${process.execPath} ${engine7Bin} start --config ${path3.resolve(configPath2)}
1752
2824
  Restart=on-failure
1753
2825
  RestartSec=10
1754
2826
 
1755
2827
  [Install]
1756
2828
  WantedBy=default.target`;
1757
- fs2.writeFileSync(svcPath, svc);
2829
+ fs3.writeFileSync(svcPath, svc);
1758
2830
  try {
1759
2831
  execSync2(`systemctl --user daemon-reload`, { stdio: "inherit" });
1760
2832
  execSync2(`systemctl --user enable ${svcName}`, { stdio: "inherit" });
@@ -1775,13 +2847,13 @@ WantedBy=default.target`;
1775
2847
  process.exit(1);
1776
2848
  }
1777
2849
  } else if (process.platform === "darwin") {
1778
- const plistPath = path2.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
2850
+ const plistPath = path3.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
1779
2851
  try {
1780
2852
  execSync2(`launchctl unload "${plistPath}"`, { stdio: "inherit" });
1781
2853
  } catch {
1782
2854
  }
1783
2855
  try {
1784
- fs2.unlinkSync(plistPath);
2856
+ fs3.unlinkSync(plistPath);
1785
2857
  } catch {
1786
2858
  }
1787
2859
  console.log(`\u2705 Mac launchd \u670D\u52A1\u5DF2\u5220\u9664`);
@@ -1790,9 +2862,9 @@ WantedBy=default.target`;
1790
2862
  execSync2(`systemctl --user disable engine7`, { stdio: "inherit" });
1791
2863
  } catch {
1792
2864
  }
1793
- const svcPath = path2.join(process.env.HOME, ".config", "systemd", "user", "engine7.service");
2865
+ const svcPath = path3.join(process.env.HOME, ".config", "systemd", "user", "engine7.service");
1794
2866
  try {
1795
- fs2.unlinkSync(svcPath);
2867
+ fs3.unlinkSync(svcPath);
1796
2868
  } catch {
1797
2869
  }
1798
2870
  try {
@@ -1809,8 +2881,8 @@ WantedBy=default.target`;
1809
2881
  console.log("\u274C \u672A\u5B89\u88C5");
1810
2882
  }
1811
2883
  } else if (process.platform === "darwin") {
1812
- const plistPath = path2.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
1813
- if (fs2.existsSync(plistPath)) {
2884
+ const plistPath = path3.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
2885
+ if (fs3.existsSync(plistPath)) {
1814
2886
  console.log("\u2705 \u5DF2\u5B89\u88C5\uFF08launchd\uFF09");
1815
2887
  } else {
1816
2888
  console.log("\u274C \u672A\u5B89\u88C5");
@@ -1833,13 +2905,13 @@ WantedBy=default.target`;
1833
2905
  console.log(`
1834
2906
  \u{1F680} engine7 init`);
1835
2907
  console.log(` state-dir: ${opts.stateDir}`);
1836
- if (fs2.existsSync(opts.stateDir) && !opts.dryRun) {
1837
- const files = fs2.readdirSync(opts.stateDir);
2908
+ if (fs3.existsSync(opts.stateDir) && !opts.dryRun) {
2909
+ const files = fs3.readdirSync(opts.stateDir);
1838
2910
  if (files.length > 0) {
1839
2911
  if (opts.force) {
1840
2912
  console.log(`
1841
2913
  \u26A0\uFE0F --force: \u6E05\u7A7A\u5DF2\u6709\u76EE\u5F55 ${opts.stateDir}`);
1842
- fs2.rmSync(opts.stateDir, { recursive: true, force: true });
2914
+ fs3.rmSync(opts.stateDir, { recursive: true, force: true });
1843
2915
  } else {
1844
2916
  console.error(`
1845
2917
  \u274C \u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A: ${opts.stateDir}`);
@@ -1859,7 +2931,7 @@ WantedBy=default.target`;
1859
2931
  rl.close();
1860
2932
  }
1861
2933
  const config = generateConfig(values);
1862
- const configPath = path2.join(opts.stateDir, "configs", "main7.json");
2934
+ const configPath = path3.join(opts.stateDir, "configs", "main7.json");
1863
2935
  const tree = buildDirTree(values);
1864
2936
  tree.push({
1865
2937
  path: configPath,
@@ -1870,9 +2942,9 @@ WantedBy=default.target`;
1870
2942
  dryRun(tree);
1871
2943
  const skillsSrc = findTemplateDir("skills");
1872
2944
  if (skillsSrc) {
1873
- const skills = fs2.readdirSync(skillsSrc);
2945
+ const skills = fs3.readdirSync(skillsSrc);
1874
2946
  for (const s of skills) {
1875
- console.log(` \u{1F4C1} ${path2.join(opts.stateDir, "workspace", "skills", s)}`);
2947
+ console.log(` \u{1F4C1} ${path3.join(opts.stateDir, "workspace", "skills", s)}`);
1876
2948
  }
1877
2949
  }
1878
2950
  } else {
@@ -1880,55 +2952,55 @@ WantedBy=default.target`;
1880
2952
  execute(tree);
1881
2953
  const skillsSrc = findTemplateDir("skills");
1882
2954
  if (skillsSrc) {
1883
- const skillsDest = path2.join(opts.stateDir, "workspace", "skills");
1884
- fs2.mkdirSync(skillsDest, { recursive: true });
1885
- const skills = fs2.readdirSync(skillsSrc);
2955
+ const skillsDest = path3.join(opts.stateDir, "workspace", "skills");
2956
+ fs3.mkdirSync(skillsDest, { recursive: true });
2957
+ const skills = fs3.readdirSync(skillsSrc);
1886
2958
  for (const s of skills) {
1887
- copyDirRecursive(path2.join(skillsSrc, s), path2.join(skillsDest, s));
2959
+ copyDirRecursive(path3.join(skillsSrc, s), path3.join(skillsDest, s));
1888
2960
  console.log(` \u{1F4E6} skill: ${s}`);
1889
2961
  }
1890
2962
  }
1891
2963
  console.log(`
1892
2964
  \u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01
1893
2965
  `);
1894
- const homePointer = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
2966
+ const homePointer = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
1895
2967
  const pointerData = { stateDir: opts.stateDir };
1896
- fs2.writeFileSync(homePointer, JSON.stringify(pointerData, null, 2) + "\n");
2968
+ fs3.writeFileSync(homePointer, JSON.stringify(pointerData, null, 2) + "\n");
1897
2969
  console.log(` \u{1F4C4} ${homePointer}\uFF08\u4ECE\u4EFB\u610F\u76EE\u5F55\u90FD\u80FD\u627E\u5230\u6B64 agent\uFF09`);
1898
2970
  console.log(`\u4E0B\u4E00\u6B65\uFF1A`);
1899
2971
  console.log(` 1. \u68C0\u67E5\u5E76\u4FEE\u6539 ${opts.stateDir}/configs/main7.json`);
1900
2972
  console.log(` 2. \u542F\u52A8 Engine: engine7 start\uFF08\u5728\u4EFB\u610F\u76EE\u5F55\u90FD\u884C\uFF09`);
1901
2973
  console.log(` 3. \u5F00\u673A\u81EA\u542F: engine7 service install\uFF08\u5728\u4EFB\u610F\u76EE\u5F55\u90FD\u884C\uFF09`);
1902
- console.log(` 4. \u67E5\u770B workspace: ${path2.join(opts.stateDir, "workspace")}`);
2974
+ console.log(` 4. \u67E5\u770B workspace: ${path3.join(opts.stateDir, "workspace")}`);
1903
2975
  }
1904
2976
  }
1905
2977
  function copyDirRecursive(src, dest) {
1906
- fs2.mkdirSync(dest, { recursive: true });
1907
- for (const entry of fs2.readdirSync(src, { withFileTypes: true })) {
1908
- const srcPath = path2.join(src, entry.name);
1909
- const destPath = path2.join(dest, entry.name);
2978
+ fs3.mkdirSync(dest, { recursive: true });
2979
+ for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
2980
+ const srcPath = path3.join(src, entry.name);
2981
+ const destPath = path3.join(dest, entry.name);
1910
2982
  if (entry.isDirectory()) {
1911
2983
  copyDirRecursive(srcPath, destPath);
1912
2984
  } else {
1913
- fs2.copyFileSync(srcPath, destPath);
2985
+ fs3.copyFileSync(srcPath, destPath);
1914
2986
  }
1915
2987
  }
1916
2988
  }
1917
2989
  function findTemplateDir(name) {
1918
2990
  const candidates = [
1919
- path2.join(__dirname, "..", "templates", name),
1920
- path2.join(__dirname, "..", "..", "templates", name),
1921
- path2.join(process.cwd(), "templates", name)
2991
+ path3.join(__dirname, "..", "templates", name),
2992
+ path3.join(__dirname, "..", "..", "templates", name),
2993
+ path3.join(process.cwd(), "templates", name)
1922
2994
  ];
1923
2995
  for (const p of candidates) {
1924
- if (fs2.existsSync(p)) return p;
2996
+ if (fs3.existsSync(p)) return p;
1925
2997
  }
1926
2998
  return null;
1927
2999
  }
1928
3000
  function readHomePointer() {
1929
- const ptrPath = path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
3001
+ const ptrPath = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
1930
3002
  try {
1931
- return JSON.parse(fs2.readFileSync(ptrPath, "utf-8"));
3003
+ return JSON.parse(fs3.readFileSync(ptrPath, "utf-8"));
1932
3004
  } catch {
1933
3005
  return null;
1934
3006
  }