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 +1327 -255
- package/dist/engine-startup.mjs +2107 -1799
- package/dist/main.mjs +2106 -1798
- package/package.json +1 -1
- package/templates/config.template.json +2 -1
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
|
|
26
|
-
import * as
|
|
27
|
-
import * as
|
|
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 (!
|
|
32
|
-
return JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
82
|
-
const realPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1054
|
+
const fullPath = path2.join(dir, entry.name);
|
|
110
1055
|
try {
|
|
111
|
-
if (
|
|
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 =
|
|
127
|
-
const indexMapPath =
|
|
128
|
-
if (!
|
|
129
|
-
const platformMap = JSON.parse(
|
|
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(
|
|
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
|
|
136
|
-
return
|
|
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 (!
|
|
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 =
|
|
1092
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
148
1093
|
for (const entry of entries) {
|
|
149
|
-
const fullPath =
|
|
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 =
|
|
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 ||
|
|
193
|
-
const engineHome =
|
|
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 (!
|
|
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(
|
|
205
|
-
const configsDir =
|
|
1149
|
+
const sessionFiles = collectRecentSessions(path2.join(stateDir, "agents"), 7);
|
|
1150
|
+
const configsDir = path2.join(stateDir, "configs");
|
|
206
1151
|
const configFiles = [];
|
|
207
|
-
if (
|
|
1152
|
+
if (fs2.existsSync(configsDir)) {
|
|
208
1153
|
for (const f of collectAllFiles(configsDir)) {
|
|
209
|
-
if (shouldExclude(
|
|
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 =
|
|
1159
|
+
const everosDir = path2.join(stateDir, ".everos");
|
|
215
1160
|
const everosFiles = [];
|
|
216
|
-
if (
|
|
1161
|
+
if (fs2.existsSync(everosDir)) {
|
|
217
1162
|
for (const f of collectAllFiles(everosDir)) {
|
|
218
|
-
if (EVEROS_SKIP.has(
|
|
1163
|
+
if (EVEROS_SKIP.has(path2.basename(f))) continue;
|
|
219
1164
|
everosFiles.push(f);
|
|
220
1165
|
}
|
|
221
|
-
const walFile =
|
|
222
|
-
if (
|
|
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 +
|
|
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(` ${
|
|
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(` ${
|
|
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 =
|
|
249
|
-
|
|
250
|
-
const stagingDir =
|
|
251
|
-
|
|
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 =
|
|
257
|
-
const destFile =
|
|
258
|
-
|
|
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 =
|
|
262
|
-
|
|
1206
|
+
const content = fs2.readFileSync(srcFile, "utf-8");
|
|
1207
|
+
fs2.writeFileSync(destFile, sanitizeContent(content, dirs));
|
|
263
1208
|
} else {
|
|
264
|
-
|
|
1209
|
+
fs2.copyFileSync(srcFile, destFile);
|
|
265
1210
|
}
|
|
266
1211
|
fileCount++;
|
|
267
|
-
totalSize +=
|
|
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,
|
|
1218
|
+
for (const srcFile of wsFiles) copyOne(srcFile, workspace, path2.join(stagingDir, "workspace"));
|
|
274
1219
|
for (const srcFile of sessionFiles) {
|
|
275
|
-
const relPath =
|
|
276
|
-
const destFile =
|
|
277
|
-
|
|
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 =
|
|
1224
|
+
let content = fs2.readFileSync(srcFile, "utf-8");
|
|
280
1225
|
content = sanitizeContent(content, dirs);
|
|
281
|
-
const baseName =
|
|
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
|
-
|
|
1230
|
+
fs2.writeFileSync(destFile, content);
|
|
286
1231
|
fileCount++;
|
|
287
|
-
totalSize +=
|
|
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
|
-
|
|
310
|
-
const archiveFile =
|
|
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 =
|
|
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 =
|
|
339
|
-
|
|
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 =
|
|
351
|
-
const manifestPath =
|
|
352
|
-
if (!
|
|
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(
|
|
357
|
-
const workspace =
|
|
358
|
-
const engineHome =
|
|
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 =
|
|
1308
|
+
const wsStaging = path2.join(stagingDir, "workspace");
|
|
364
1309
|
let restoredCount = 0;
|
|
365
1310
|
let skipCount = 0;
|
|
366
|
-
if (
|
|
1311
|
+
if (fs2.existsSync(wsStaging)) {
|
|
367
1312
|
const allFiles = collectAllFiles(wsStaging);
|
|
368
1313
|
for (const srcFile of allFiles) {
|
|
369
|
-
const relPath =
|
|
370
|
-
const destFile =
|
|
371
|
-
|
|
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 =
|
|
375
|
-
|
|
1319
|
+
const content = fs2.readFileSync(srcFile, "utf-8");
|
|
1320
|
+
fs2.writeFileSync(destFile, restoreContent(content, dirs));
|
|
376
1321
|
} else {
|
|
377
|
-
|
|
1322
|
+
fs2.copyFileSync(srcFile, destFile);
|
|
378
1323
|
}
|
|
379
1324
|
restoredCount++;
|
|
380
1325
|
} catch (e) {
|
|
381
1326
|
skipCount++;
|
|
382
|
-
console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${
|
|
1327
|
+
console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path2.relative(wsStaging, srcFile)} (${e.code || e.message})`);
|
|
383
1328
|
}
|
|
384
1329
|
}
|
|
385
1330
|
}
|
|
386
|
-
const agentsStaging =
|
|
387
|
-
if (
|
|
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 =
|
|
391
|
-
const destFile =
|
|
392
|
-
|
|
393
|
-
const content =
|
|
394
|
-
|
|
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 =
|
|
399
|
-
if (
|
|
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 =
|
|
403
|
-
const destFile =
|
|
404
|
-
|
|
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 =
|
|
407
|
-
|
|
1351
|
+
const content = fs2.readFileSync(srcFile, "utf-8");
|
|
1352
|
+
fs2.writeFileSync(destFile, restoreContent(content, dirs));
|
|
408
1353
|
} else {
|
|
409
|
-
|
|
1354
|
+
fs2.copyFileSync(srcFile, destFile);
|
|
410
1355
|
}
|
|
411
1356
|
restoredCount++;
|
|
412
1357
|
}
|
|
413
1358
|
}
|
|
414
|
-
const everosStaging =
|
|
415
|
-
if (
|
|
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 =
|
|
419
|
-
const destFile =
|
|
420
|
-
|
|
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 =
|
|
424
|
-
|
|
1368
|
+
const content = fs2.readFileSync(srcFile, "utf-8");
|
|
1369
|
+
fs2.writeFileSync(destFile, restoreContent(content, dirs));
|
|
425
1370
|
} else {
|
|
426
|
-
|
|
1371
|
+
fs2.copyFileSync(srcFile, destFile);
|
|
427
1372
|
}
|
|
428
1373
|
restoredCount++;
|
|
429
1374
|
} catch (e) {
|
|
430
1375
|
skipCount++;
|
|
431
|
-
console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${
|
|
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 =
|
|
439
|
-
const configToUse =
|
|
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
|
-
|
|
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 =
|
|
473
|
-
const fileName =
|
|
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
|
-
|
|
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 =
|
|
529
|
-
|
|
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 =
|
|
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
|
|
628
|
-
import * as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
...
|
|
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 =
|
|
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:
|
|
1115
|
-
{ path:
|
|
1116
|
-
{ path:
|
|
1117
|
-
{ path:
|
|
1118
|
-
{ path:
|
|
1119
|
-
{ path:
|
|
1120
|
-
{ path:
|
|
1121
|
-
{ path:
|
|
1122
|
-
{ path:
|
|
1123
|
-
{ path:
|
|
1124
|
-
{ path:
|
|
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:
|
|
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:
|
|
2141
|
+
{ path: path3.join(w, "HEARTBEAT.md"), type: "file", content: readTemplate("workspace/HEARTBEAT.md") },
|
|
1132
2142
|
{
|
|
1133
|
-
path:
|
|
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:
|
|
2147
|
+
{ path: path3.join(w, "AGENTS.md"), type: "file", content: readTemplate("workspace/AGENTS.md") },
|
|
1138
2148
|
{
|
|
1139
|
-
path:
|
|
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:
|
|
1144
|
-
{ path:
|
|
1145
|
-
{ path:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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 (
|
|
1230
|
-
return
|
|
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
|
-
|
|
2255
|
+
fs3.mkdirSync(e.path, { recursive: true });
|
|
1246
2256
|
console.log(` \u{1F4C1} ${e.path}`);
|
|
1247
2257
|
} else {
|
|
1248
|
-
|
|
1249
|
-
|
|
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 =
|
|
1278
|
-
if (!agentName) agentName =
|
|
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 =
|
|
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 =
|
|
1324
|
-
const homeCfg =
|
|
1325
|
-
if (
|
|
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 (
|
|
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 =
|
|
1334
|
-
if (
|
|
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 =
|
|
1350
|
-
if (
|
|
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 =
|
|
1354
|
-
const distDir =
|
|
1355
|
-
const candidate =
|
|
1356
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1390
|
-
const cfgBase =
|
|
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
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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 (
|
|
1398
|
-
const content =
|
|
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(
|
|
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 =
|
|
1479
|
-
if (!
|
|
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 =
|
|
1488
|
-
const indexPath =
|
|
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 (
|
|
1491
|
-
const index = JSON.parse(
|
|
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 =
|
|
2567
|
+
sessionFileUUID = path3.isAbsolute(fileVal) ? path3.basename(fileVal, ".jsonl") : fileVal.replace(".jsonl", "");
|
|
1496
2568
|
}
|
|
1497
2569
|
}
|
|
1498
2570
|
if (!sessionFileUUID) {
|
|
1499
|
-
const jsonlFiles =
|
|
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 =
|
|
1507
|
-
if (!
|
|
1508
|
-
const jsonlFiles =
|
|
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 =
|
|
2586
|
+
jsonlPath = path3.join(sessionsDir, `${sessionFileUUID}.jsonl`);
|
|
1515
2587
|
}
|
|
1516
2588
|
console.log(` file: ${jsonlPath}`);
|
|
1517
|
-
const configName =
|
|
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:
|
|
2605
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, renameSync } = fs3;
|
|
1534
2606
|
if (mode === "archive") {
|
|
1535
2607
|
const archiveName = `${sessionFileUUID}.jsonl.archived.${timestamp}`;
|
|
1536
|
-
renameSync(jsonlPath,
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1612
|
-
const cliDir =
|
|
1613
|
-
const pkgRoot =
|
|
1614
|
-
let enginePath =
|
|
1615
|
-
if (!
|
|
1616
|
-
enginePath =
|
|
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 =
|
|
1660
|
-
const homeCfg =
|
|
1661
|
-
if (
|
|
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 (
|
|
2735
|
+
} else if (fs3.existsSync(homeCfg)) {
|
|
1664
2736
|
configPath2 = homeCfg;
|
|
1665
|
-
cwd =
|
|
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 =
|
|
1670
|
-
if (
|
|
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 =
|
|
1690
|
-
const absConfig =
|
|
1691
|
-
const wrapperPath =
|
|
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
|
-
|
|
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 =
|
|
1708
|
-
|
|
1709
|
-
const plistPath =
|
|
1710
|
-
const localCli =
|
|
1711
|
-
const cliMjs =
|
|
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>${
|
|
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
|
-
|
|
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 =
|
|
1742
|
-
|
|
1743
|
-
const svcPath =
|
|
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 ${
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
2865
|
+
const svcPath = path3.join(process.env.HOME, ".config", "systemd", "user", "engine7.service");
|
|
1794
2866
|
try {
|
|
1795
|
-
|
|
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 =
|
|
1813
|
-
if (
|
|
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 (
|
|
1837
|
-
const files =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
2945
|
+
const skills = fs3.readdirSync(skillsSrc);
|
|
1874
2946
|
for (const s of skills) {
|
|
1875
|
-
console.log(` \u{1F4C1} ${
|
|
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 =
|
|
1884
|
-
|
|
1885
|
-
const skills =
|
|
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(
|
|
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 =
|
|
2966
|
+
const homePointer = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
|
|
1895
2967
|
const pointerData = { stateDir: opts.stateDir };
|
|
1896
|
-
|
|
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: ${
|
|
2974
|
+
console.log(` 4. \u67E5\u770B workspace: ${path3.join(opts.stateDir, "workspace")}`);
|
|
1903
2975
|
}
|
|
1904
2976
|
}
|
|
1905
2977
|
function copyDirRecursive(src, dest) {
|
|
1906
|
-
|
|
1907
|
-
for (const entry of
|
|
1908
|
-
const srcPath =
|
|
1909
|
-
const destPath =
|
|
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
|
-
|
|
2985
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
1914
2986
|
}
|
|
1915
2987
|
}
|
|
1916
2988
|
}
|
|
1917
2989
|
function findTemplateDir(name) {
|
|
1918
2990
|
const candidates = [
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
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 (
|
|
2996
|
+
if (fs3.existsSync(p)) return p;
|
|
1925
2997
|
}
|
|
1926
2998
|
return null;
|
|
1927
2999
|
}
|
|
1928
3000
|
function readHomePointer() {
|
|
1929
|
-
const ptrPath =
|
|
3001
|
+
const ptrPath = path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
|
|
1930
3002
|
try {
|
|
1931
|
-
return JSON.parse(
|
|
3003
|
+
return JSON.parse(fs3.readFileSync(ptrPath, "utf-8"));
|
|
1932
3004
|
} catch {
|
|
1933
3005
|
return null;
|
|
1934
3006
|
}
|