engine7 7.1.42 → 7.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -14,6 +14,156 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/feishu-quick-register.ts
18
+ var feishu_quick_register_exports = {};
19
+ __export(feishu_quick_register_exports, {
20
+ registerFeishuApp: () => registerFeishuApp
21
+ });
22
+ async function postRegistration(domain, body) {
23
+ const url = `${ACCOUNTS_URL[domain]}${REGISTRATION_PATH}`;
24
+ const res = await fetch(url, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
27
+ body: new URLSearchParams(body).toString(),
28
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
29
+ });
30
+ if (!res.ok) {
31
+ throw new Error(`\u98DE\u4E66\u6CE8\u518C\u63A5\u53E3\u8FD4\u56DE ${res.status}: ${await res.text()}`);
32
+ }
33
+ return res.json();
34
+ }
35
+ async function initRegistration(domain) {
36
+ const res = await postRegistration(domain, { action: "init" });
37
+ if (!res.supported_auth_methods?.includes("client_secret")) {
38
+ throw new Error("\u5F53\u524D\u98DE\u4E66\u73AF\u5883\u4E0D\u652F\u6301 client_secret \u8BA4\u8BC1\uFF0C\u65E0\u6CD5\u81EA\u52A8\u6CE8\u518C");
39
+ }
40
+ }
41
+ async function beginRegistration(domain) {
42
+ const res = await postRegistration(domain, {
43
+ action: "begin",
44
+ archetype: "PersonalAgent",
45
+ auth_method: "client_secret",
46
+ request_user_info: "open_id"
47
+ });
48
+ if (!res.device_code || !res.verification_uri_complete) {
49
+ throw new Error(`\u98DE\u4E66\u6CE8\u518C\u5931\u8D25: \u672A\u8FD4\u56DE device_code
50
+ ${JSON.stringify(res)}`);
51
+ }
52
+ return {
53
+ deviceCode: res.device_code,
54
+ qrUrl: res.verification_uri_complete,
55
+ userCode: res.user_code,
56
+ interval: res.interval ?? DEFAULT_POLL_INTERVAL,
57
+ expireIn: res.expire_in ?? 300
58
+ };
59
+ }
60
+ async function pollRegistration(domain, deviceCode, interval, deadline, onLog, signal) {
61
+ let currentInterval = interval;
62
+ let currentDomain = domain;
63
+ while (Date.now() < deadline) {
64
+ if (signal?.aborted) return null;
65
+ let res;
66
+ try {
67
+ res = await postRegistration(currentDomain, {
68
+ action: "poll",
69
+ device_code: deviceCode
70
+ });
71
+ } catch {
72
+ await sleep(currentInterval * 1e3);
73
+ continue;
74
+ }
75
+ if (res.user_info?.tenant_brand === "lark" && currentDomain === "feishu") {
76
+ currentDomain = "lark";
77
+ onLog?.("\u68C0\u6D4B\u5230 Lark \u8D26\u53F7\uFF0C\u5207\u6362\u57DF\u540D...");
78
+ continue;
79
+ }
80
+ if (res.client_id && res.client_secret) {
81
+ return {
82
+ appId: res.client_id,
83
+ appSecret: res.client_secret,
84
+ openId: res.user_info?.open_id,
85
+ domain: currentDomain
86
+ };
87
+ }
88
+ if (res.error) {
89
+ if (res.error === "authorization_pending") {
90
+ } else if (res.error === "slow_down") {
91
+ currentInterval += 5;
92
+ } else if (res.error === "access_denied") {
93
+ throw new Error("\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743");
94
+ } else if (res.error === "expired_token") {
95
+ throw new Error("\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F");
96
+ } else {
97
+ throw new Error(`\u98DE\u4E66\u6CE8\u518C\u9519\u8BEF: ${res.error} \u2014 ${res.error_description ?? ""}`);
98
+ }
99
+ }
100
+ await sleep(currentInterval * 1e3);
101
+ }
102
+ return null;
103
+ }
104
+ async function registerFeishuApp(options = {}) {
105
+ const domain = options.domain ?? "feishu";
106
+ const timeoutSec = options.timeoutSec ?? 300;
107
+ const log = options.onLog ?? (() => {
108
+ });
109
+ log("\u68C0\u67E5\u98DE\u4E66\u73AF\u5883...");
110
+ await initRegistration(domain);
111
+ log("\u751F\u6210\u4E8C\u7EF4\u7801...");
112
+ const { deviceCode, qrUrl, interval, expireIn } = await beginRegistration(domain);
113
+ options.onQrCode?.(qrUrl);
114
+ log(`\u8BF7\u7528\u98DE\u4E66 App \u626B\u63CF\u4E8C\u7EF4\u7801\uFF08${expireIn}\u79D2\u540E\u8FC7\u671F\uFF09...`);
115
+ const deadline = Date.now() + Math.min(expireIn, timeoutSec) * 1e3;
116
+ const result = await pollRegistration(domain, deviceCode, interval, deadline, log, options.signal);
117
+ if (result) {
118
+ log(`\u2705 \u6CE8\u518C\u6210\u529F\uFF01App ID: ${result.appId}`);
119
+ } else {
120
+ log("\u23F0 \u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F");
121
+ }
122
+ return result;
123
+ }
124
+ function sleep(ms) {
125
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
126
+ }
127
+ var ACCOUNTS_URL, REGISTRATION_PATH, REQUEST_TIMEOUT_MS, DEFAULT_POLL_INTERVAL;
128
+ var init_feishu_quick_register = __esm({
129
+ "src/feishu-quick-register.ts"() {
130
+ "use strict";
131
+ ACCOUNTS_URL = {
132
+ feishu: "https://accounts.feishu.cn",
133
+ lark: "https://accounts.larksuite.com"
134
+ };
135
+ REGISTRATION_PATH = "/oauth/v1/app/registration";
136
+ REQUEST_TIMEOUT_MS = 1e4;
137
+ DEFAULT_POLL_INTERVAL = 5;
138
+ }
139
+ });
140
+
141
+ // src/qr-render.ts
142
+ var qr_render_exports = {};
143
+ __export(qr_render_exports, {
144
+ renderQrTerminal: () => renderQrTerminal
145
+ });
146
+ import { createRequire } from "node:module";
147
+ async function renderQrTerminal(url, options) {
148
+ return new Promise((resolve2, reject) => {
149
+ try {
150
+ const qt = require2("qrcode-terminal");
151
+ qt.generate(url, { small: options?.small ?? true }, (output) => {
152
+ resolve2(output);
153
+ });
154
+ } catch (err) {
155
+ reject(err);
156
+ }
157
+ });
158
+ }
159
+ var require2;
160
+ var init_qr_render = __esm({
161
+ "src/qr-render.ts"() {
162
+ "use strict";
163
+ require2 = createRequire(import.meta.url);
164
+ }
165
+ });
166
+
17
167
  // src/channels/wechat.ts
18
168
  var wechat_exports = {};
19
169
  __export(wechat_exports, {
@@ -301,20 +451,35 @@ async function wechatQrLogin(options) {
301
451
  console.error("[wechat] QR response missing qrcode field");
302
452
  return null;
303
453
  }
454
+ let pollQrValue = qrcodeValue;
304
455
  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}`);
456
+ try {
457
+ const { renderQrTerminal: renderQrTerminal2 } = await Promise.resolve().then(() => (init_qr_render(), qr_render_exports));
458
+ const qrBlock = await renderQrTerminal2(qrScanData);
459
+ console.log("\n========== \u5FAE\u4FE1\u626B\u7801\u767B\u5F55 ==========");
460
+ if (qrcodeUrl) {
461
+ console.log(`\u626B\u7801\u94FE\u63A5: ${qrcodeUrl}`);
462
+ }
463
+ if (qrBlock) {
464
+ console.log("\u8BF7\u7528\u5FAE\u4FE1\u626B\u63CF\u4EE5\u4E0B\u4E8C\u7EF4\u7801:");
465
+ console.log(qrBlock);
466
+ } else {
467
+ console.log("\u4E8C\u7EF4\u7801\u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u7528\u5FAE\u4FE1\u300C\u626B\u4E00\u626B\u300D\u6253\u5F00\u4E0A\u9762\u7684\u94FE\u63A5:");
468
+ }
469
+ console.log("===================================\n");
470
+ } catch {
471
+ console.log("\n========== \u5FAE\u4FE1\u626B\u7801\u767B\u5F55 ==========");
472
+ if (qrcodeUrl) console.log(`\u626B\u7801\u94FE\u63A5: ${qrcodeUrl}`);
473
+ console.log("\u8BF7\u7528\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\uFF08\u6216\u6253\u5F00\u4E0A\u9762\u7684\u94FE\u63A5\uFF09:");
474
+ console.log("===================================\n");
308
475
  }
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
476
  const deadline = Date.now() + timeoutSeconds * 1e3;
312
477
  let currentBaseUrl = ILINK_BASE_URL;
313
478
  let refreshCount = 0;
314
479
  while (Date.now() < deadline) {
315
480
  let statusResp;
316
481
  try {
317
- statusResp = await apiGet(currentBaseUrl, `${EP_GET_QR_STATUS}?qrcode=${qrcodeValue}`, "", QR_TIMEOUT_MS);
482
+ statusResp = await apiGet(currentBaseUrl, `${EP_GET_QR_STATUS}?qrcode=${pollQrValue}`, "", QR_TIMEOUT_MS);
318
483
  } catch {
319
484
  await sleep2(1e3);
320
485
  continue;
@@ -339,7 +504,17 @@ async function wechatQrLogin(options) {
339
504
  qrResp = await apiGet(ILINK_BASE_URL, `${EP_GET_BOT_QR}?bot_type=${botType}`, "", QR_TIMEOUT_MS);
340
505
  const newQrValue = String(qrResp?.qrcode || "");
341
506
  const newQrUrl = String(qrResp?.qrcode_img_content || "");
342
- if (newQrUrl) console.log(`\u65B0\u626B\u7801\u94FE\u63A5: ${newQrUrl}`);
507
+ if (!newQrValue) throw new Error("refresh response missing qrcode");
508
+ pollQrValue = newQrValue;
509
+ console.log("\u65B0\u626B\u7801\u94FE\u63A5:" + (newQrUrl ? ` ${newQrUrl}` : " (\u89C1\u4E0A\u65B9\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u626B)"));
510
+ if (newQrUrl) {
511
+ try {
512
+ const { renderQrTerminal: renderQrTerminal2 } = await Promise.resolve().then(() => (init_qr_render(), qr_render_exports));
513
+ const qrBlock = await renderQrTerminal2(newQrUrl);
514
+ if (qrBlock) console.log(qrBlock);
515
+ } catch {
516
+ }
517
+ }
343
518
  } catch (err) {
344
519
  console.error(`[wechat] QR refresh failed: ${err.message}`);
345
520
  return null;
@@ -360,15 +535,6 @@ async function wechatQrLogin(options) {
360
535
  \u2705 \u5FAE\u4FE1\u767B\u5F55\u6210\u529F!`);
361
536
  console.log(` accountId: ${accountId}`);
362
537
  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
538
  return { accountId, token, baseUrl, userId };
373
539
  }
374
540
  await sleep2(1e3);
@@ -1569,145 +1735,13 @@ var init_cli_travel = __esm({
1569
1735
  });
1570
1736
 
1571
1737
  // src/cli-init.ts
1738
+ init_feishu_quick_register();
1739
+ init_wechat();
1740
+ init_qr_render();
1572
1741
  import * as path3 from "node:path";
1573
1742
  import * as fs3 from "node:fs";
1574
1743
  import * as readline from "node:readline";
1575
1744
  import { fileURLToPath } from "node:url";
1576
-
1577
- // src/feishu-quick-register.ts
1578
- var ACCOUNTS_URL = {
1579
- feishu: "https://accounts.feishu.cn",
1580
- lark: "https://accounts.larksuite.com"
1581
- };
1582
- var REGISTRATION_PATH = "/oauth/v1/app/registration";
1583
- var REQUEST_TIMEOUT_MS = 1e4;
1584
- var DEFAULT_POLL_INTERVAL = 5;
1585
- async function postRegistration(domain, body) {
1586
- const url = `${ACCOUNTS_URL[domain]}${REGISTRATION_PATH}`;
1587
- const res = await fetch(url, {
1588
- method: "POST",
1589
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1590
- body: new URLSearchParams(body).toString(),
1591
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1592
- });
1593
- if (!res.ok) {
1594
- throw new Error(`\u98DE\u4E66\u6CE8\u518C\u63A5\u53E3\u8FD4\u56DE ${res.status}: ${await res.text()}`);
1595
- }
1596
- return res.json();
1597
- }
1598
- async function initRegistration(domain) {
1599
- const res = await postRegistration(domain, { action: "init" });
1600
- if (!res.supported_auth_methods?.includes("client_secret")) {
1601
- throw new Error("\u5F53\u524D\u98DE\u4E66\u73AF\u5883\u4E0D\u652F\u6301 client_secret \u8BA4\u8BC1\uFF0C\u65E0\u6CD5\u81EA\u52A8\u6CE8\u518C");
1602
- }
1603
- }
1604
- async function beginRegistration(domain) {
1605
- const res = await postRegistration(domain, {
1606
- action: "begin",
1607
- archetype: "PersonalAgent",
1608
- auth_method: "client_secret",
1609
- request_user_info: "open_id"
1610
- });
1611
- if (!res.device_code || !res.verification_uri_complete) {
1612
- throw new Error(`\u98DE\u4E66\u6CE8\u518C\u5931\u8D25: \u672A\u8FD4\u56DE device_code
1613
- ${JSON.stringify(res)}`);
1614
- }
1615
- return {
1616
- deviceCode: res.device_code,
1617
- qrUrl: res.verification_uri_complete,
1618
- userCode: res.user_code,
1619
- interval: res.interval ?? DEFAULT_POLL_INTERVAL,
1620
- expireIn: res.expire_in ?? 300
1621
- };
1622
- }
1623
- async function pollRegistration(domain, deviceCode, interval, deadline, onLog, signal) {
1624
- let currentInterval = interval;
1625
- let currentDomain = domain;
1626
- while (Date.now() < deadline) {
1627
- if (signal?.aborted) return null;
1628
- let res;
1629
- try {
1630
- res = await postRegistration(currentDomain, {
1631
- action: "poll",
1632
- device_code: deviceCode
1633
- });
1634
- } catch {
1635
- await sleep(currentInterval * 1e3);
1636
- continue;
1637
- }
1638
- if (res.user_info?.tenant_brand === "lark" && currentDomain === "feishu") {
1639
- currentDomain = "lark";
1640
- onLog?.("\u68C0\u6D4B\u5230 Lark \u8D26\u53F7\uFF0C\u5207\u6362\u57DF\u540D...");
1641
- continue;
1642
- }
1643
- if (res.client_id && res.client_secret) {
1644
- return {
1645
- appId: res.client_id,
1646
- appSecret: res.client_secret,
1647
- openId: res.user_info?.open_id,
1648
- domain: currentDomain
1649
- };
1650
- }
1651
- if (res.error) {
1652
- if (res.error === "authorization_pending") {
1653
- } else if (res.error === "slow_down") {
1654
- currentInterval += 5;
1655
- } else if (res.error === "access_denied") {
1656
- throw new Error("\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743");
1657
- } else if (res.error === "expired_token") {
1658
- throw new Error("\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F");
1659
- } else {
1660
- throw new Error(`\u98DE\u4E66\u6CE8\u518C\u9519\u8BEF: ${res.error} \u2014 ${res.error_description ?? ""}`);
1661
- }
1662
- }
1663
- await sleep(currentInterval * 1e3);
1664
- }
1665
- return null;
1666
- }
1667
- async function registerFeishuApp(options = {}) {
1668
- const domain = options.domain ?? "feishu";
1669
- const timeoutSec = options.timeoutSec ?? 300;
1670
- const log = options.onLog ?? (() => {
1671
- });
1672
- log("\u68C0\u67E5\u98DE\u4E66\u73AF\u5883...");
1673
- await initRegistration(domain);
1674
- log("\u751F\u6210\u4E8C\u7EF4\u7801...");
1675
- const { deviceCode, qrUrl, interval, expireIn } = await beginRegistration(domain);
1676
- options.onQrCode?.(qrUrl);
1677
- log(`\u8BF7\u7528\u98DE\u4E66 App \u626B\u63CF\u4E8C\u7EF4\u7801\uFF08${expireIn}\u79D2\u540E\u8FC7\u671F\uFF09...`);
1678
- const deadline = Date.now() + Math.min(expireIn, timeoutSec) * 1e3;
1679
- const result = await pollRegistration(domain, deviceCode, interval, deadline, log, options.signal);
1680
- if (result) {
1681
- log(`\u2705 \u6CE8\u518C\u6210\u529F\uFF01App ID: ${result.appId}`);
1682
- } else {
1683
- log("\u23F0 \u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F");
1684
- }
1685
- return result;
1686
- }
1687
- function sleep(ms) {
1688
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1689
- }
1690
-
1691
- // src/cli-init.ts
1692
- init_wechat();
1693
-
1694
- // src/qr-render.ts
1695
- import { createRequire } from "node:module";
1696
- var require2 = createRequire(import.meta.url);
1697
- async function renderQrTerminal(url, options) {
1698
- return new Promise((resolve2, reject) => {
1699
- try {
1700
- const qt = require2("qrcode-terminal");
1701
- qt.generate(url, { small: options?.small ?? true }, (output) => {
1702
- resolve2(output);
1703
- });
1704
- } catch (err) {
1705
- reject(err);
1706
- }
1707
- });
1708
- }
1709
-
1710
- // src/cli-init.ts
1711
1745
  var __filename = fileURLToPath(import.meta.url);
1712
1746
  var __dirname = path3.dirname(__filename);
1713
1747
  var SCHEMA_VERSION = 1;
@@ -1746,7 +1780,7 @@ Engine 7 \u2014 Self-hosted AI agent engine
1746
1780
 
1747
1781
  \u7528\u6CD5:
1748
1782
  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
1783
+ engine7 reconfig <\u76EE\u6807> [\u9009\u9879] \u91CD\u65B0\u914D\u7F6E\u5DF2\u88C5\u7684 agent\uFF08wechat/feishu/discord/llm/channels\uFF09
1750
1784
  engine7 start [--config <path>] \u542F\u52A8 Engine
1751
1785
  engine7 restart [--config <path>] \u91CD\u542F Engine\uFF08\u6740\u65E7\u8FDB\u7A0B+\u542F\u52A8\uFF09
1752
1786
  engine7 service install|uninstall|status \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
@@ -1842,7 +1876,7 @@ async function interactiveConfig(rl, defaults) {
1842
1876
  v.primaryProvider = providerMap[chosenProvider] || "dashscope";
1843
1877
  v.zhipuPlan = "";
1844
1878
  if (v.primaryProvider === "zhipu") {
1845
- const planOptions = ["coding-plan (GLM Coding \u8BA2\u9605)", "token-plan (\u666E\u901A\u6309\u91CF/TokenPlan)"];
1879
+ const planOptions = ["coding-plan (GLM Coding \u8BA2\u9605\uFF0C\u5305\u6708)", "api-pay (API \u6309\u91CF\u4ED8\u8D39)"];
1846
1880
  const chosenPlan = await askChoice(rl, "\u667A\u8C31\u8BA2\u9605\u7C7B\u578B:", planOptions, 0);
1847
1881
  v.zhipuPlan = chosenPlan.startsWith("coding") ? "coding" : "token";
1848
1882
  }
@@ -1856,7 +1890,7 @@ async function interactiveConfig(rl, defaults) {
1856
1890
  const modelMap = {
1857
1891
  dashscope: ["dashscope/qwen3.7-max", "dashscope/qwen3.7-plus"],
1858
1892
  minimax: ["minimax/MiniMax-M3", "minimax/MiniMax-M2.7"],
1859
- zhipu: ["zhipu/glm-5.1", "zhipu/glm-5v-turbo"],
1893
+ zhipu: ["zhipu/glm-5.3", "zhipu/glm-5v-turbo"],
1860
1894
  deepseek: ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"]
1861
1895
  };
1862
1896
  const models = modelMap[v.primaryProvider] || ["dashscope/qwen3.7-max"];
@@ -1866,7 +1900,7 @@ async function interactiveConfig(rl, defaults) {
1866
1900
  "dashscope/qwen3.7-plus": true,
1867
1901
  "minimax/MiniMax-M3": true,
1868
1902
  "zhipu/glm-5v-turbo": true,
1869
- "zhipu/glm-5.1": false,
1903
+ "zhipu/glm-5.3": false,
1870
1904
  "deepseek/deepseek-v4-pro": false,
1871
1905
  "deepseek/deepseek-v4-flash": false
1872
1906
  };
@@ -2089,8 +2123,8 @@ function generateConfig(v) {
2089
2123
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
2090
2124
  api: "openai-completions",
2091
2125
  models: [
2092
- { id: "glm-5.1", name: "GLM-5.1", reasoning: true, input: ["text"], contextWindow: 204800, maxTokens: 131072 },
2093
- { id: "glm-5v-turbo", name: "GLM-5V-Turbo", reasoning: true, input: ["text", "image"], contextWindow: 2e5, maxTokens: 128e3 }
2126
+ { id: "glm-5.3", name: "GLM-5.3 (\u6700\u65B0\u4E3B\u529B)", reasoning: true, input: ["text"], contextWindow: 204800, maxTokens: 131072 },
2127
+ { id: "glm-5v-turbo", name: "GLM-5V-Turbo (\u89C6\u89C9)", reasoning: true, input: ["text", "image"], contextWindow: 2e5, maxTokens: 128e3 }
2094
2128
  ]
2095
2129
  },
2096
2130
  deepseek: {
@@ -2110,7 +2144,8 @@ function generateConfig(v) {
2110
2144
  }
2111
2145
  config.models.providers[v.primaryProvider] = {
2112
2146
  ...def,
2113
- apiKey: v.primaryApiKey
2147
+ apiKey: v.primaryApiKey.trim(),
2148
+ ...def.baseUrl ? { baseUrl: def.baseUrl.trim() } : {}
2114
2149
  };
2115
2150
  }
2116
2151
  return config;
@@ -2272,15 +2307,115 @@ async function main() {
2272
2307
  printHelp();
2273
2308
  process.exit(0);
2274
2309
  }
2275
- if (subcommand === "addchannel") {
2276
- const channel = args[1] || "";
2310
+ if (subcommand === "reconfig" || subcommand === "addchannel") {
2311
+ const isLegacyAddchannel = subcommand === "addchannel";
2312
+ let target = args[1] || "";
2313
+ if (isLegacyAddchannel) target = target || "wechat";
2314
+ const channel = target;
2277
2315
  let stateDir = "";
2278
2316
  let configName = "";
2279
2317
  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];
2318
+ if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
2319
+ else if (args[i] === "--config" && args[i + 1]) configName = args[++i].trim();
2282
2320
  }
2283
2321
  if (!stateDir) stateDir = path3.resolve(process.cwd());
2322
+ if (!target && !isLegacyAddchannel) {
2323
+ console.log("\u{1F527} engine7 reconfig \u2014 \u91CD\u65B0\u914D\u7F6E\u5DF2\u88C5\u7684 agent");
2324
+ console.log(" \u7528\u6CD5: engine7 reconfig <\u76EE\u6807>");
2325
+ console.log("");
2326
+ console.log(" \u901A\u9053\u7C7B:");
2327
+ console.log(" wechat \u5FAE\u4FE1\u626B\u7801\u63A5\u5165\uFF08iLink\uFF0C1v1 \u79C1\u804A\uFF09");
2328
+ console.log(" feishu \u98DE\u4E66\u626B\u7801\u63A5\u5165\uFF08OAuth\uFF0C30\u79D2\uFF09");
2329
+ console.log(" discord Discord bot \u914D\u7F6E");
2330
+ console.log(" \u6A21\u578B\u7C7B:");
2331
+ console.log(" llm \u66F4\u6362 LLM provider / API key / \u7AEF\u70B9");
2332
+ console.log(" \u5176\u4ED6:");
2333
+ console.log(" channels \u67E5\u770B\u5F53\u524D\u901A\u9053\u72B6\u6001");
2334
+ console.log("");
2335
+ console.log(" \u9009\u9879: --state-dir <path> --config <file>");
2336
+ process.exit(0);
2337
+ }
2338
+ if (channel === "channels") {
2339
+ const configsDir = path3.join(stateDir, "configs");
2340
+ if (fs3.existsSync(configsDir)) {
2341
+ const files = fs3.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
2342
+ for (const f of files) {
2343
+ try {
2344
+ const j = JSON.parse(fs3.readFileSync(path3.join(configsDir, f), "utf8"));
2345
+ if (!j.channels) continue;
2346
+ console.log(`
2347
+ \u{1F4C4} ${f}:`);
2348
+ for (const [name, conf] of Object.entries(j.channels)) {
2349
+ const c = conf;
2350
+ console.log(` ${c.enabled ? "\u2705" : "\u26D4"} ${name}${c.appId ? " (" + c.appId + ")" : ""}${c.accountId ? " (" + c.accountId + ")" : ""}`);
2351
+ }
2352
+ } catch {
2353
+ }
2354
+ }
2355
+ } else {
2356
+ console.log(`\u26A0\uFE0F \u672A\u627E\u5230 configs \u76EE\u5F55: ${configsDir}`);
2357
+ }
2358
+ process.exit(0);
2359
+ }
2360
+ if (channel === "llm") {
2361
+ console.log("\u{1F527} LLM \u91CD\u914D\uFF08\u5F00\u53D1\u4E2D\uFF0Cv7.2 \u8BA1\u5212\uFF09");
2362
+ console.log(" \u76EE\u524D\u8BF7\u624B\u52A8\u6539 config \u7684 models.providers \u6BB5");
2363
+ process.exit(0);
2364
+ }
2365
+ if (channel === "discord") {
2366
+ console.log("\u{1F527} Discord \u91CD\u914D\uFF08\u5F00\u53D1\u4E2D\uFF09");
2367
+ console.log(" \u76EE\u524D\u8BF7\u624B\u52A8\u6539 config \u7684 channels.discord \u6BB5\uFF08token/userId\uFF09");
2368
+ process.exit(0);
2369
+ }
2370
+ if (channel === "feishu") {
2371
+ console.log("\u{1F527} \u98DE\u4E66\u626B\u7801\u63A5\u5165...");
2372
+ const { registerFeishuApp: registerFeishuApp2 } = await Promise.resolve().then(() => (init_feishu_quick_register(), feishu_quick_register_exports));
2373
+ const { renderQrTerminal: renderQrTerminal2 } = await Promise.resolve().then(() => (init_qr_render(), qr_render_exports));
2374
+ const result = await registerFeishuApp2({
2375
+ onLog: (msg) => console.log(` ${msg}`),
2376
+ onQrCode: async (url) => {
2377
+ try {
2378
+ console.log(await renderQrTerminal2(url, { small: true }));
2379
+ } catch {
2380
+ console.log(`
2381
+ \u6D4F\u89C8\u5668\u6253\u5F00\u626B\u7801: ${url}
2382
+ `);
2383
+ }
2384
+ }
2385
+ });
2386
+ if (!result) {
2387
+ console.error("\u274C \u626B\u7801\u8D85\u65F6\u6216\u5931\u8D25");
2388
+ process.exit(1);
2389
+ }
2390
+ const configsDir2 = path3.join(stateDir, "configs");
2391
+ let configFile2 = configName ? path3.join(configsDir2, configName) : "";
2392
+ if (!configFile2 || !fs3.existsSync(configFile2)) {
2393
+ if (fs3.existsSync(configsDir2)) {
2394
+ for (const f of fs3.readdirSync(configsDir2).filter((f2) => f2.endsWith(".json"))) {
2395
+ const full = path3.join(configsDir2, f);
2396
+ try {
2397
+ if (JSON.parse(fs3.readFileSync(full, "utf8")).channels) {
2398
+ configFile2 = full;
2399
+ break;
2400
+ }
2401
+ } catch {
2402
+ }
2403
+ }
2404
+ }
2405
+ }
2406
+ if (configFile2 && fs3.existsSync(configFile2)) {
2407
+ const j = JSON.parse(fs3.readFileSync(configFile2, "utf8"));
2408
+ j.channels = j.channels || {};
2409
+ j.channels.feishu = { enabled: true, appId: result.appId, appSecret: result.appSecret, connectionMode: "websocket", dmPolicy: "pairing", groupPolicy: "open" };
2410
+ fs3.writeFileSync(configFile2, JSON.stringify(j, null, 2), "utf8");
2411
+ console.log("\u2705 \u98DE\u4E66\u901A\u9053\u5DF2\u5199\u5165: " + configFile2);
2412
+ console.log("\u91CD\u542F\u751F\u6548: engine7 restart");
2413
+ } else {
2414
+ console.log("\u26A0\uFE0F \u6CA1\u627E\u5230 config\uFF0C\u624B\u52A8\u52A0\uFF1A");
2415
+ console.log(JSON.stringify({ feishu: { enabled: true, appId: result.appId, appSecret: result.appSecret, connectionMode: "websocket", dmPolicy: "pairing", groupPolicy: "open" } }, null, 2));
2416
+ }
2417
+ process.exit(0);
2418
+ }
2284
2419
  if (channel === "wechat") {
2285
2420
  console.log("\u{1F4F1} \u5FAE\u4FE1\u626B\u7801\u63A5\u5165\uFF08iLink \u4E2A\u4EBA\u5FAE\u4FE1\uFF0C1v1 \u79C1\u804A\uFF09");
2286
2421
  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");
@@ -2330,8 +2465,8 @@ async function main() {
2330
2465
  console.log("\n\u91CD\u542F engine \u751F\u6548: engine7 restart");
2331
2466
  process.exit(0);
2332
2467
  }
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>]");
2468
+ console.error(`\u672A\u77E5\u76EE\u6807: ${channel || "(\u7A7A)"}\u3002\u652F\u6301: wechat / feishu / discord / llm / channels`);
2469
+ console.error("\u7528\u6CD5: engine7 reconfig <\u76EE\u6807> [--state-dir <path>] [--config <file>]");
2335
2470
  process.exit(1);
2336
2471
  }
2337
2472
  if (subcommand === "export") {
@@ -2226,9 +2226,19 @@ var init_compact2 = __esm({
2226
2226
  // src/config/live.ts
2227
2227
  var live_exports = {};
2228
2228
  __export(live_exports, {
2229
- liveConfig: () => liveConfig
2229
+ consumeSelfWrite: () => consumeSelfWrite,
2230
+ liveConfig: () => liveConfig,
2231
+ markSelfWrite: () => markSelfWrite
2230
2232
  });
2231
- var LiveConfigClass, liveConfig;
2233
+ function markSelfWrite() {
2234
+ selfWrite.pending = true;
2235
+ }
2236
+ function consumeSelfWrite() {
2237
+ const was = selfWrite.pending;
2238
+ selfWrite.pending = false;
2239
+ return was;
2240
+ }
2241
+ var LiveConfigClass, liveConfig, selfWrite;
2232
2242
  var init_live = __esm({
2233
2243
  "src/config/live.ts"() {
2234
2244
  "use strict";
@@ -2289,6 +2299,7 @@ var init_live = __esm({
2289
2299
  o[keys[keys.length - 1]] = val;
2290
2300
  fs48.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
2291
2301
  console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
2302
+ markSelfWrite();
2292
2303
  } catch (e) {
2293
2304
  console.warn(`[liveConfig] persist failed (${dotPath}): ${e.message}`);
2294
2305
  }
@@ -2299,6 +2310,7 @@ var init_live = __esm({
2299
2310
  }
2300
2311
  };
2301
2312
  liveConfig = new LiveConfigClass();
2313
+ selfWrite = { pending: false };
2302
2314
  }
2303
2315
  });
2304
2316
 
@@ -31890,6 +31902,7 @@ function createMemorySideProvider(sideConfig, mainProvider, providers) {
31890
31902
  console.log(`[memory] Side provider: ${sideConfig.provider}/${sideConfig.model} (thinking=${sideConfig.thinking || "default"})`);
31891
31903
  return { provider: sideProvider, model: sideConfig.model, disableThinking };
31892
31904
  }
31905
+ var lastLoadedConfigRaw = null;
31893
31906
  async function doReloadConfig(config, deps, provider) {
31894
31907
  try {
31895
31908
  const savedConfigPath = config._configFilePath;
@@ -31904,7 +31917,17 @@ async function doReloadConfig(config, deps, provider) {
31904
31917
  reloadConfigPath = altPath;
31905
31918
  }
31906
31919
  }
31920
+ let configRaw = "";
31921
+ try {
31922
+ configRaw = fs47.readFileSync(reloadConfigPath, "utf-8");
31923
+ } catch {
31924
+ }
31925
+ if (lastLoadedConfigRaw !== null && configRaw.trim() === lastLoadedConfigRaw) {
31926
+ console.log(`[reload] config content unchanged, skipping reload`);
31927
+ return { ok: true, changes: ["unchanged (skipped)"] };
31928
+ }
31907
31929
  const newConfig = loadConfig(reloadConfigPath);
31930
+ lastLoadedConfigRaw = configRaw.trim();
31908
31931
  const changes = [];
31909
31932
  const oldProviderKey = JSON.stringify({
31910
31933
  providers: config.providers,
@@ -32055,9 +32078,22 @@ function startConfigWatcher(config, deps, provider) {
32055
32078
  return null;
32056
32079
  }
32057
32080
  let debounceTimer = null;
32081
+ try {
32082
+ lastLoadedConfigRaw = fs47.readFileSync(configPath, "utf-8").trim();
32083
+ } catch {
32084
+ }
32058
32085
  const watcher = fs47.watch(configPath, { persistent: true }, (eventType) => {
32059
32086
  if (debounceTimer) clearTimeout(debounceTimer);
32060
32087
  debounceTimer = setTimeout(async () => {
32088
+ if (consumeSelfWrite()) {
32089
+ console.log(`[config-watch] self-write (liveConfig.set, memory already synced), skipping reload`);
32090
+ try {
32091
+ fs47.appendFileSync(path49.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] SELF-WRITE SKIP (liveConfig.set, memory synced)
32092
+ `);
32093
+ } catch {
32094
+ }
32095
+ return;
32096
+ }
32061
32097
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
32062
32098
  try {
32063
32099
  fs47.appendFileSync(path49.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
package/dist/main.mjs CHANGED
@@ -34,9 +34,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
34
34
  // src/config/live.ts
35
35
  var live_exports = {};
36
36
  __export(live_exports, {
37
- liveConfig: () => liveConfig
37
+ consumeSelfWrite: () => consumeSelfWrite,
38
+ liveConfig: () => liveConfig,
39
+ markSelfWrite: () => markSelfWrite
38
40
  });
39
- var LiveConfigClass, liveConfig;
41
+ function markSelfWrite() {
42
+ selfWrite.pending = true;
43
+ }
44
+ function consumeSelfWrite() {
45
+ const was = selfWrite.pending;
46
+ selfWrite.pending = false;
47
+ return was;
48
+ }
49
+ var LiveConfigClass, liveConfig, selfWrite;
40
50
  var init_live = __esm({
41
51
  "src/config/live.ts"() {
42
52
  "use strict";
@@ -97,6 +107,7 @@ var init_live = __esm({
97
107
  o[keys[keys.length - 1]] = val;
98
108
  fs48.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
99
109
  console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
110
+ markSelfWrite();
100
111
  } catch (e) {
101
112
  console.warn(`[liveConfig] persist failed (${dotPath}): ${e.message}`);
102
113
  }
@@ -107,6 +118,7 @@ var init_live = __esm({
107
118
  }
108
119
  };
109
120
  liveConfig = new LiveConfigClass();
121
+ selfWrite = { pending: false };
110
122
  }
111
123
  });
112
124
 
@@ -31974,6 +31986,7 @@ function createMemorySideProvider(sideConfig, mainProvider, providers) {
31974
31986
  console.log(`[memory] Side provider: ${sideConfig.provider}/${sideConfig.model} (thinking=${sideConfig.thinking || "default"})`);
31975
31987
  return { provider: sideProvider, model: sideConfig.model, disableThinking };
31976
31988
  }
31989
+ var lastLoadedConfigRaw = null;
31977
31990
  async function doReloadConfig(config2, deps, provider) {
31978
31991
  try {
31979
31992
  const savedConfigPath = config2._configFilePath;
@@ -31988,7 +32001,17 @@ async function doReloadConfig(config2, deps, provider) {
31988
32001
  reloadConfigPath = altPath;
31989
32002
  }
31990
32003
  }
32004
+ let configRaw = "";
32005
+ try {
32006
+ configRaw = fs47.readFileSync(reloadConfigPath, "utf-8");
32007
+ } catch {
32008
+ }
32009
+ if (lastLoadedConfigRaw !== null && configRaw.trim() === lastLoadedConfigRaw) {
32010
+ console.log(`[reload] config content unchanged, skipping reload`);
32011
+ return { ok: true, changes: ["unchanged (skipped)"] };
32012
+ }
31991
32013
  const newConfig = loadConfig(reloadConfigPath);
32014
+ lastLoadedConfigRaw = configRaw.trim();
31992
32015
  const changes = [];
31993
32016
  const oldProviderKey = JSON.stringify({
31994
32017
  providers: config2.providers,
@@ -32139,9 +32162,22 @@ function startConfigWatcher(config2, deps, provider) {
32139
32162
  return null;
32140
32163
  }
32141
32164
  let debounceTimer = null;
32165
+ try {
32166
+ lastLoadedConfigRaw = fs47.readFileSync(configPath2, "utf-8").trim();
32167
+ } catch {
32168
+ }
32142
32169
  const watcher = fs47.watch(configPath2, { persistent: true }, (eventType) => {
32143
32170
  if (debounceTimer) clearTimeout(debounceTimer);
32144
32171
  debounceTimer = setTimeout(async () => {
32172
+ if (consumeSelfWrite()) {
32173
+ console.log(`[config-watch] self-write (liveConfig.set, memory already synced), skipping reload`);
32174
+ try {
32175
+ fs47.appendFileSync(path49.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] SELF-WRITE SKIP (liveConfig.set, memory synced)
32176
+ `);
32177
+ } catch {
32178
+ }
32179
+ return;
32180
+ }
32145
32181
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
32146
32182
  try {
32147
32183
  fs47.appendFileSync(path49.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engine7",
3
- "version": "7.1.42",
3
+ "version": "7.1.44",
4
4
  "type": "module",
5
5
  "description": "Engine 7 — 给助手一个家 / A home for your AI assistant",
6
6
  "main": "dist/main.mjs",