engine7 7.1.94 → 7.1.95

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
@@ -27228,12 +27228,17 @@ var init_cli_doctor = __esm({
27228
27228
  var provision_free_exports = {};
27229
27229
  __export(provision_free_exports, {
27230
27230
  loadOrCreateInstallIdentity: () => loadOrCreateInstallIdentity,
27231
- provisionFreeToken: () => provisionFreeToken
27231
+ provisionFreeToken: () => provisionFreeToken,
27232
+ readInstallIdentity: () => readInstallIdentity,
27233
+ writeIdentityContactEmail: () => writeIdentityContactEmail
27232
27234
  });
27233
27235
  import { randomBytes as randomBytes6, randomUUID as randomUUID9 } from "node:crypto";
27234
27236
  import { chmodSync as chmodSync2, existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync39, writeFileSync as writeFileSync20 } from "node:fs";
27235
27237
  import { dirname as dirname12, join as join49 } from "node:path";
27236
27238
  import { fileURLToPath as fileURLToPath6 } from "node:url";
27239
+ function readContactEmailOf(parsed) {
27240
+ return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
27241
+ }
27237
27242
  function loadOrCreateInstallIdentity(stateDir) {
27238
27243
  const file2 = join49(stateDir, IDENTITY_FILE);
27239
27244
  if (existsSync31(file2)) {
@@ -27248,7 +27253,12 @@ function loadOrCreateInstallIdentity(stateDir) {
27248
27253
  if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
27249
27254
  throw new Error(`${IDENTITY_FILE} \u7F3A installId / installSecret \u5B57\u6BB5`);
27250
27255
  }
27251
- return { installId: parsed.installId, installSecret: parsed.installSecret };
27256
+ const email = readContactEmailOf(parsed);
27257
+ return {
27258
+ installId: parsed.installId,
27259
+ installSecret: parsed.installSecret,
27260
+ ...email ? { contactEmail: email } : {}
27261
+ };
27252
27262
  }
27253
27263
  const identity = {
27254
27264
  // 服务端校验 installId 长度为 8..36、只允许字母数字和连字符;uuid 正好符合
@@ -27264,6 +27274,32 @@ function loadOrCreateInstallIdentity(stateDir) {
27264
27274
  }
27265
27275
  return identity;
27266
27276
  }
27277
+ function readInstallIdentity(stateDir) {
27278
+ try {
27279
+ const parsed = JSON.parse(readFileSync39(join49(stateDir, IDENTITY_FILE), "utf8"));
27280
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
27281
+ const email = readContactEmailOf(parsed);
27282
+ return {
27283
+ installId: parsed.installId,
27284
+ installSecret: parsed.installSecret,
27285
+ ...email ? { contactEmail: email } : {}
27286
+ };
27287
+ } catch {
27288
+ return null;
27289
+ }
27290
+ }
27291
+ function writeIdentityContactEmail(stateDir, email) {
27292
+ const identity = loadOrCreateInstallIdentity(stateDir);
27293
+ const trimmed = email.trim();
27294
+ const next = { ...identity, contactEmail: trimmed };
27295
+ const file2 = join49(stateDir, IDENTITY_FILE);
27296
+ mkdirSync19(stateDir, { recursive: true });
27297
+ writeFileSync20(file2, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
27298
+ try {
27299
+ chmodSync2(file2, 384);
27300
+ } catch {
27301
+ }
27302
+ }
27267
27303
  function readEngineVersion() {
27268
27304
  try {
27269
27305
  return JSON.parse(readFileSync39(join49(dirname12(__filename), "..", "package.json"), "utf8")).version || "unknown";
@@ -27278,7 +27314,14 @@ async function provisionFreeToken(opts) {
27278
27314
  const res = await doFetch(url, {
27279
27315
  method: "POST",
27280
27316
  headers: { "Content-Type": "application/json" },
27281
- body: JSON.stringify({ ...identity, engineVersion: readEngineVersion() })
27317
+ // 显式列字段,**不要用 `...identity` 展开**:InstallIdentity 加了 contactEmail
27318
+ // 之后,展开会把邮箱也发到发号端点——协议悄悄变了,而服务端只认这三个字段。
27319
+ // (provision-free.test.ts 里有一条断言守这个。)
27320
+ body: JSON.stringify({
27321
+ installId: identity.installId,
27322
+ installSecret: identity.installSecret,
27323
+ engineVersion: readEngineVersion()
27324
+ })
27282
27325
  });
27283
27326
  const text = await res.text();
27284
27327
  let body;
@@ -28165,6 +28208,13 @@ import * as fs2 from "node:fs";
28165
28208
  // src/cli-init-config.ts
28166
28209
  import * as path2 from "node:path";
28167
28210
  var SCHEMA_VERSION = 1;
28211
+ var FREE_TIER_RELAY_URL = "https://www.twinsun.top:8866";
28212
+ function isFreeTier(v) {
28213
+ return v.freeTier === true || v.primaryProvider === "7yi";
28214
+ }
28215
+ function resolveRelayUrl(relayArg, env = process.env) {
28216
+ return (relayArg || "").trim() || (env.E7_RELAY_URL || "").trim() || FREE_TIER_RELAY_URL;
28217
+ }
28168
28218
  var DEFAULT_PRIMARY_MODEL = {
28169
28219
  dashscope: "dashscope/qwen3.8-max",
28170
28220
  minimax: "minimax/MiniMax-M3",
@@ -28245,7 +28295,13 @@ var PROVIDER_DEFS = {
28245
28295
  // 引擎免费档的中转站。baseUrl 是兜底值——真正的权威值来自领号响应
28246
28296
  // (服务端按请求 Host 自报地址),由安装器作为 customBaseUrl 下发。
28247
28297
  // 这样挪机器、换域名只改服务端,不用重发安装包。
28248
- baseUrl: "https://www.twinsun.top/v1",
28298
+ //
28299
+ // ⚠️ 0921 修:这里原本写的是 `https://www.twinsun.top/v1`(443),而 **443 上不是
28300
+ // 中转站**(实测回的是另一个服务的 FastAPI 404)。安装器那条路每次都会下发
28301
+ // customBaseUrl 把它盖掉,所以正式装机撞不到;但**终端向导选 7yi 时不问 baseUrl**,
28302
+ // 直接落到这个兜底值——于是命令行用户拿到的是「号领到了、引擎起来了、一发消息就废」。
28303
+ // 用常量拼,别再手写一遍(漏端口就是这个坑)。
28304
+ baseUrl: `${FREE_TIER_RELAY_URL}/v1`,
28249
28305
  api: "anthropic",
28250
28306
  models: [
28251
28307
  // ⚠️ contextWindow **不能照抄 1000000**。
@@ -28365,9 +28421,9 @@ function generateConfig(v) {
28365
28421
  // 自拍(§4.3):免费档必须显式写 provider:'relay'。不写的话 getProvider()
28366
28422
  // 会落回默认值 'fal',而免费装机没有 fal key——第一次自拍就失败。
28367
28423
  // 非免费档只在用户真给了形象时才写这个块,且不替他决定 provider。
28368
- ...v.freeTier || v.selfiePhotoRelPath || v.selfiePortraitDesc ? {
28424
+ ...isFreeTier(v) || v.selfiePhotoRelPath || v.selfiePortraitDesc ? {
28369
28425
  my_selfie: {
28370
- ...v.freeTier ? { provider: "relay" } : {},
28426
+ ...isFreeTier(v) ? { provider: "relay" } : {},
28371
28427
  ...v.selfiePhotoRelPath ? { references: [{ name: "portrait", path: v.selfiePhotoRelPath }] } : {},
28372
28428
  // 只在没有照片时才写描述——两个都给的话以照片为准,写进去会让人以为
28373
28429
  // 描述也生效了(冷启读的就是这个键)
@@ -28415,7 +28471,7 @@ function generateConfig(v) {
28415
28471
  debounceMs: 5e3,
28416
28472
  // 免费档的装机目标是「能对话、能看图、能自拍」,这两个 flag 默认 false
28417
28473
  // (src/config/features.ts)。显式打开,它们是声明这两件事的地方。
28418
- ...v.freeTier ? { selfie: true, eyes: true } : {}
28474
+ ...isFreeTier(v) ? { selfie: true, eyes: true } : {}
28419
28475
  }
28420
28476
  }
28421
28477
  },
@@ -28581,6 +28637,13 @@ function validate(a) {
28581
28637
  if (a.timezone !== void 0 && !isValidTimezone(a.timezone)) {
28582
28638
  throw new Error(`timezone \u975E\u6CD5: ${a.timezone}\uFF08\u8981 IANA \u540D\uFF0C\u5982 Asia/Shanghai\u3001Europe/Malta\uFF09`);
28583
28639
  }
28640
+ if (a.contactEmail !== void 0 && a.contactEmail !== null && a.contactEmail !== "") {
28641
+ if (typeof a.contactEmail !== "string") throw new Error("contactEmail \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
28642
+ const t = a.contactEmail.trim();
28643
+ if (t && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) {
28644
+ throw new Error(`contactEmail \u4E0D\u662F\u5408\u6CD5\u90AE\u7BB1: ${t}`);
28645
+ }
28646
+ }
28584
28647
  if (a.feishu?.mode !== void 0 && !VALID_CHANNEL_MODES.includes(a.feishu.mode)) {
28585
28648
  throw new Error(`feishu.mode \u975E\u6CD5: ${a.feishu.mode}\uFF08\u53EF\u9009: ${VALID_CHANNEL_MODES.join(" / ")}\uFF09`);
28586
28649
  }
@@ -28609,6 +28672,11 @@ function loadInitAnswers(filePath) {
28609
28672
  throw new Error(`answers \u6587\u4EF6\u4E0D\u662F\u5408\u6CD5 JSON: ${e.message}`);
28610
28673
  }
28611
28674
  validate(parsed);
28675
+ if (typeof parsed.contactEmail === "string") {
28676
+ const t = parsed.contactEmail.trim();
28677
+ if (t) parsed.contactEmail = t;
28678
+ else delete parsed.contactEmail;
28679
+ }
28612
28680
  return parsed;
28613
28681
  }
28614
28682
  function buildConfigValuesFromAnswers(stateDir, a, existing = {}) {
@@ -29399,16 +29467,16 @@ async function main() {
29399
29467
  }
29400
29468
  if (subcommand === "provision-free") {
29401
29469
  let relay = "";
29470
+ let relayFromArg = false;
29402
29471
  let stateDir = "";
29403
29472
  for (let i = 1; i < args.length; i++) {
29404
- if (args[i] === "--relay" && args[i + 1]) relay = args[++i].trim();
29405
- else if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
29406
- }
29407
- if (!relay) {
29408
- console.error("\u7528\u6CD5\uFF1Aengine7 provision-free --relay <\u4E2D\u8F6C\u7AD9\u5730\u5740> --state-dir <\u76EE\u5F55>");
29409
- console.error(" --relay \u662F\u5B89\u88C5\u5668\u4E0B\u53D1\u7684\u9886\u53F7\u5730\u5740\uFF0C\u4E0D\u662F\u6A21\u578B baseUrl\uFF08\u540E\u8005\u4EE5\u9886\u53F7\u54CD\u5E94\u4E3A\u51C6\uFF09");
29410
- process.exit(1);
29473
+ if (args[i] === "--relay" && args[i + 1]) {
29474
+ relay = args[++i].trim();
29475
+ relayFromArg = true;
29476
+ } else if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
29411
29477
  }
29478
+ relay = resolveRelayUrl(relay, process.env);
29479
+ if (!relayFromArg) console.error(` \u4E2D\u8F6C\u7AD9: ${relay}\uFF08\u8981\u6362\u5730\u5740\u7528 --relay <\u5730\u5740>\uFF09`);
29412
29480
  const { resolveStateDirFromArgs: resolveStateDirFromArgs2 } = await Promise.resolve().then(() => (init_cli_doctor(), cli_doctor_exports));
29413
29481
  stateDir = stateDir ? path33.resolve(stateDir) : resolveStateDirFromArgs2(args.slice(1));
29414
29482
  try {
@@ -30225,11 +30293,17 @@ WantedBy=default.target`;
30225
30293
  values = await interactiveConfig(rl, getDefaultValues(opts.stateDir));
30226
30294
  rl.close();
30227
30295
  }
30228
- if (values.freeTier) {
30296
+ {
30297
+ const { loadOrCreateInstallIdentity: loadOrCreateInstallIdentity2, writeIdentityContactEmail: writeIdentityContactEmail2 } = await Promise.resolve().then(() => (init_provision_free(), provision_free_exports));
30298
+ loadOrCreateInstallIdentity2(opts.stateDir);
30299
+ const contactEmail = (answers?.contactEmail || "").trim();
30300
+ if (contactEmail) writeIdentityContactEmail2(opts.stateDir, contactEmail);
30301
+ }
30302
+ if (isFreeTier(values)) {
30229
30303
  const envName = values.primaryProvider.toUpperCase().replace(/[^A-Z0-9_]/g, "_") + "_API_KEY";
30230
30304
  if (!readSecretsValue(opts.stateDir, envName)) {
30231
30305
  throw new Error(
30232
- `\u514D\u8D39\u6863\u7F3A\u5C11\u4EE4\u724C\uFF1Asecrets \u91CC\u6CA1\u6709 ${envName}\u3002\u8BF7\u5148\u6267\u884C engine7 provision-free --relay <\u4E2D\u8F6C\u7AD9\u5730\u5740> --state-dir ${opts.stateDir} \u9886\u53F7\uFF0C\u518D\u8DD1 init\u3002`
30306
+ `\u514D\u8D39\u6863\u7F3A\u5C11\u4EE4\u724C\uFF1Asecrets \u91CC\u6CA1\u6709 ${envName}\u3002\u8BF7\u5148\u6267\u884C engine7 provision-free --state-dir ${opts.stateDir} \u9886\u53F7\uFF0C\u518D\u8DD1 init\u3002`
30233
30307
  );
30234
30308
  }
30235
30309
  }
@@ -8077,26 +8077,26 @@ async function writeToMailbox(recipientName, message, teamName) {
8077
8077
  await writeFile2(inboxPath, "[]", { encoding: "utf-8", flag: "wx" });
8078
8078
  } catch {
8079
8079
  }
8080
- const release2 = await acquireLock(inboxPath);
8080
+ const release3 = await acquireLock(inboxPath);
8081
8081
  try {
8082
8082
  const messages = await readMailbox(recipientName, teamName);
8083
8083
  messages.push({ ...message, read: false });
8084
8084
  await writeFile2(inboxPath, JSON.stringify(messages, null, 2), "utf-8");
8085
8085
  console.log(`[mailbox] Wrote message to ${recipientName} from ${message.from} (team=${teamName || "default"}, inbox=${inboxPath})`);
8086
8086
  } finally {
8087
- await release2();
8087
+ await release3();
8088
8088
  }
8089
8089
  }
8090
8090
  async function markMessagesAsRead(agentName, teamName) {
8091
8091
  const inboxPath = getInboxPath(agentName, teamName);
8092
- const release2 = await acquireLock(inboxPath);
8092
+ const release3 = await acquireLock(inboxPath);
8093
8093
  try {
8094
8094
  const messages = await readMailbox(agentName, teamName);
8095
8095
  if (messages.length === 0) return;
8096
8096
  for (const m of messages) m.read = true;
8097
8097
  await writeFile2(inboxPath, JSON.stringify(messages, null, 2), "utf-8");
8098
8098
  } finally {
8099
- await release2();
8099
+ await release3();
8100
8100
  }
8101
8101
  }
8102
8102
  async function clearMailbox(agentName, teamName) {
@@ -9927,7 +9927,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
9927
9927
  }
9928
9928
  // ── md 读(proactive 消费):开窗预测(全 owner 扫描,end>=今天)──
9929
9929
  getOpenPredictions(now, limit = 50) {
9930
- const today = todayKey(now);
9930
+ const today2 = todayKey(now);
9931
9931
  const out = [];
9932
9932
  const usersDir = join22(this.root, "users");
9933
9933
  if (!existsSync12(usersDir)) return out;
@@ -9950,7 +9950,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
9950
9950
  const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
9951
9951
  const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
9952
9952
  if (!end || !start || !text) continue;
9953
- if (end < today) continue;
9953
+ if (end < today2) continue;
9954
9954
  out.push({ ownerId: owner.name, startDate: start, endDate: end, content: text.replace(/\n+/g, " ").slice(0, 200), evidence: evidence.replace(/\n+/g, " ").slice(0, 120) });
9955
9955
  if (out.length >= limit) return out;
9956
9956
  }
@@ -13491,7 +13491,7 @@ function loadForesightEntries(everosRoot, now, userId, ownerFilter) {
13491
13491
  return entries2.slice();
13492
13492
  }
13493
13493
  function parseForesightEntries(everosRoot, now, userId) {
13494
- const today = todayKey(now);
13494
+ const today2 = todayKey(now);
13495
13495
  let files;
13496
13496
  if (userId) {
13497
13497
  const ownDir = join29(everosRoot, userId, "default_project", "users", userId, ".foresights");
@@ -13516,7 +13516,7 @@ function parseForesightEntries(everosRoot, now, userId) {
13516
13516
  const end = (block.match(/\*\*end_time\*\*:\s*(\d{4}-\d{2}-\d{2})/) ?? [])[1];
13517
13517
  const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
13518
13518
  if (!start || !end || !text) continue;
13519
- if (end < today) continue;
13519
+ if (end < today2) continue;
13520
13520
  const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
13521
13521
  const blockOwner = (block.match(/\*\*owner_id\*\*:\s*(\S+)/) ?? [])[1] ?? fileOwner;
13522
13522
  const key = text.replace(/\s+/g, "");
@@ -21454,7 +21454,7 @@ function isPermittedRedirect(original, redirect) {
21454
21454
  async function fetchWithRedirects(url, signal, depth = 0) {
21455
21455
  if (depth > MAX_REDIRECTS) throw new Error(`\u91CD\u5B9A\u5411\u6B21\u6570\u8FC7\u591A (>${MAX_REDIRECTS})`);
21456
21456
  const controller = new AbortController();
21457
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
21457
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
21458
21458
  const onSignalAbort = () => controller.abort();
21459
21459
  signal.addEventListener("abort", onSignalAbort, { once: true });
21460
21460
  try {
@@ -21497,14 +21497,14 @@ function getTurndown() {
21497
21497
  }
21498
21498
  return td;
21499
21499
  }
21500
- var MAX_URL_LENGTH, MAX_CONTENT_LENGTH, FETCH_TIMEOUT_MS, MAX_REDIRECTS, td;
21500
+ var MAX_URL_LENGTH, MAX_CONTENT_LENGTH, FETCH_TIMEOUT_MS2, MAX_REDIRECTS, td;
21501
21501
  var init_web_fetch = __esm({
21502
21502
  "src/tools/web-fetch.ts"() {
21503
21503
  "use strict";
21504
21504
  init_registry();
21505
21505
  MAX_URL_LENGTH = 2e3;
21506
21506
  MAX_CONTENT_LENGTH = 10 * 1024 * 1024;
21507
- FETCH_TIMEOUT_MS = 6e4;
21507
+ FETCH_TIMEOUT_MS2 = 6e4;
21508
21508
  MAX_REDIRECTS = 10;
21509
21509
  registry.register({
21510
21510
  name: "web_fetch",
@@ -21575,7 +21575,7 @@ var init_web_fetch = __esm({
21575
21575
 
21576
21576
  // src/tools/wechat/wx-query.ts
21577
21577
  var wx_query_exports = {};
21578
- import { join as join54 } from "node:path";
21578
+ import { join as join56 } from "node:path";
21579
21579
  function getDescription() {
21580
21580
  return `\u67E5\u8BE2\u7FC0\u54E5\u7684\u5FAE\u4FE1\u6D88\u606F\uFF08\u7F13\u5B58\u89E3\u5BC6\u540E\u7684\u672C\u5730\u6570\u636E\u5E93\uFF09\u3002
21581
21581
 
@@ -21610,8 +21610,8 @@ var init_wx_query = __esm({
21610
21610
  "use strict";
21611
21611
  init_registry();
21612
21612
  init_live();
21613
- STATE_DIR = process.env.ENGINE7_STATE_DIR || join54(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
21614
- SCRIPT = join54("/Users/chongzhang/work/twinsun-hearth/engine", "src", "tools", "wechat", "wx_query.py");
21613
+ STATE_DIR = process.env.ENGINE7_STATE_DIR || join56(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
21614
+ SCRIPT = join56("/Users/chongzhang/work/twinsun-hearth/engine", "src", "tools", "wechat", "wx_query.py");
21615
21615
  PYTHON = "python3";
21616
21616
  registry.register({
21617
21617
  name: "wx_query",
@@ -22532,8 +22532,8 @@ async function doReloadConfigInner(config, deps, provider, force = false) {
22532
22532
  const savedConfigPath = config._configFilePath;
22533
22533
  let reloadConfigPath = savedConfigPath;
22534
22534
  if (!fs10.existsSync(reloadConfigPath)) {
22535
- const __filename = fileURLToPath(import.meta.url);
22536
- const __dirname = path10.dirname(__filename);
22535
+ const __filename2 = fileURLToPath(import.meta.url);
22536
+ const __dirname = path10.dirname(__filename2);
22537
22537
  const engineConfigsDir = path10.resolve(__dirname, "../configs");
22538
22538
  const altPath = path10.join(engineConfigsDir, path10.basename(savedConfigPath));
22539
22539
  if (fs10.existsSync(altPath)) {
@@ -22847,20 +22847,20 @@ function acquireEngineLock(stateDir) {
22847
22847
  } catch (err) {
22848
22848
  console.warn(`[pid-lock] write lock failed: ${err.message}`);
22849
22849
  }
22850
- const release2 = () => {
22850
+ const release3 = () => {
22851
22851
  try {
22852
22852
  const cur = fs11.existsSync(lockFile) ? fs11.readFileSync(lockFile, "utf-8").trim() : "";
22853
22853
  if (cur === String(myPid)) fs11.unlinkSync(lockFile);
22854
22854
  } catch {
22855
22855
  }
22856
22856
  };
22857
- process.on("exit", release2);
22857
+ process.on("exit", release3);
22858
22858
  process.on("SIGINT", () => {
22859
- release2();
22859
+ release3();
22860
22860
  process.exit(0);
22861
22861
  });
22862
22862
  process.on("SIGTERM", () => {
22863
- release2();
22863
+ release3();
22864
22864
  process.exit(0);
22865
22865
  });
22866
22866
  }
@@ -28389,10 +28389,10 @@ var US_HALF_LIFE_DAYS = 10;
28389
28389
  var US_MAX_LINES = 60;
28390
28390
  function readRecentMemory(workspace) {
28391
28391
  const dir = path30.join(workspace, "memory");
28392
- const today = dailyFileKeyFor();
28392
+ const today2 = dailyFileKeyFor();
28393
28393
  const yesterday = dailyFileKeyFor(Date.now() - 864e5);
28394
28394
  return {
28395
- today: readIfExists(path30.join(dir, `${today}.md`)),
28395
+ today: readIfExists(path30.join(dir, `${today2}.md`)),
28396
28396
  yesterday: readIfExists(path30.join(dir, `${yesterday}.md`))
28397
28397
  };
28398
28398
  }
@@ -32329,6 +32329,200 @@ ${pathStr}` }];
32329
32329
  channelManager.startAll();
32330
32330
  }
32331
32331
 
32332
+ // src/optional/reporting/reporter.ts
32333
+ init_live();
32334
+
32335
+ // src/provision-free.ts
32336
+ import { chmodSync, existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync33, writeFileSync as writeFileSync11 } from "node:fs";
32337
+ import { dirname as dirname9, join as join44 } from "node:path";
32338
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
32339
+ var __filename = fileURLToPath7(import.meta.url);
32340
+ var IDENTITY_FILE = "install-identity.json";
32341
+ function readContactEmailOf(parsed) {
32342
+ return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
32343
+ }
32344
+ function readInstallIdentity(stateDir) {
32345
+ try {
32346
+ const parsed = JSON.parse(readFileSync33(join44(stateDir, IDENTITY_FILE), "utf8"));
32347
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
32348
+ const email = readContactEmailOf(parsed);
32349
+ return {
32350
+ installId: parsed.installId,
32351
+ installSecret: parsed.installSecret,
32352
+ ...email ? { contactEmail: email } : {}
32353
+ };
32354
+ } catch {
32355
+ return null;
32356
+ }
32357
+ }
32358
+
32359
+ // src/optional/reporting/state.ts
32360
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync34, writeFileSync as writeFileSync12 } from "node:fs";
32361
+ import { join as join45 } from "node:path";
32362
+ var STATE_FILE2 = "report-state.json";
32363
+ function readReportState(stateDir) {
32364
+ try {
32365
+ const p = join45(stateDir, STATE_FILE2);
32366
+ if (!existsSync22(p)) return {};
32367
+ const parsed = JSON.parse(readFileSync34(p, "utf8"));
32368
+ if (!parsed || typeof parsed !== "object") return {};
32369
+ return parsed;
32370
+ } catch {
32371
+ return {};
32372
+ }
32373
+ }
32374
+ function writeReportState(stateDir, s) {
32375
+ try {
32376
+ mkdirSync14(stateDir, { recursive: true });
32377
+ writeFileSync12(join45(stateDir, STATE_FILE2), JSON.stringify(s, null, 2) + "\n");
32378
+ } catch {
32379
+ }
32380
+ }
32381
+
32382
+ // src/optional/reporting/payload.ts
32383
+ import { createHash as createHash2 } from "node:crypto";
32384
+ import * as os3 from "node:os";
32385
+ function installKeyOf(installId, installSecret) {
32386
+ return createHash2("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
32387
+ }
32388
+ function buildReportPayload(opts) {
32389
+ const p = {
32390
+ installId: opts.identity.installId,
32391
+ installKey: installKeyOf(opts.identity.installId, opts.identity.installSecret),
32392
+ engineVersion: opts.engineVersion,
32393
+ platform: process.platform,
32394
+ osVersion: typeof os3.release === "function" ? os3.release() : "",
32395
+ arch: process.arch,
32396
+ stateDir: opts.stateDir,
32397
+ startedAt: opts.startedAt,
32398
+ // 时钟回拨时别报个负数上去
32399
+ uptimeSec: Math.max(0, Math.floor((opts.now - opts.startedAt) / 1e3)),
32400
+ msgsToday: opts.counters.msgsToday,
32401
+ msgsTotal: opts.counters.msgsTotal
32402
+ };
32403
+ if (opts.identity.contactEmail) p.contactEmail = opts.identity.contactEmail;
32404
+ return p;
32405
+ }
32406
+
32407
+ // src/core/activity-counter.ts
32408
+ init_clock();
32409
+ var USER_SOURCES = /* @__PURE__ */ new Set(["user", "inbox"]);
32410
+ var nowFn = () => /* @__PURE__ */ new Date();
32411
+ var today = "";
32412
+ var msgsToday = 0;
32413
+ var msgsTotal = 0;
32414
+ function rolloverIfNeeded(now) {
32415
+ const d = todayKey(now);
32416
+ if (d !== today) {
32417
+ today = d;
32418
+ msgsToday = 0;
32419
+ }
32420
+ }
32421
+ function noteUserMessage(source) {
32422
+ if (!source || !USER_SOURCES.has(source)) return;
32423
+ const now = nowFn();
32424
+ rolloverIfNeeded(now);
32425
+ msgsToday++;
32426
+ msgsTotal++;
32427
+ }
32428
+ function snapshotCounters() {
32429
+ rolloverIfNeeded(nowFn());
32430
+ return { day: today, msgsToday, msgsTotal };
32431
+ }
32432
+ function restoreCounters(s) {
32433
+ today = todayKey(nowFn());
32434
+ msgsToday = s.day === today && typeof s.msgsToday === "number" && s.msgsToday > 0 ? s.msgsToday : 0;
32435
+ msgsTotal = typeof s.msgsTotal === "number" && s.msgsTotal > 0 ? s.msgsTotal : 0;
32436
+ }
32437
+
32438
+ // src/optional/reporting/reporter.ts
32439
+ init_api_status();
32440
+ var DEFAULT_REPORT_URL = "https://www.twinsun.top:8866/api/engine7/report";
32441
+ var DEFAULT_INTERVAL_MS2 = 5 * 60 * 1e3;
32442
+ var DEFAULT_INITIAL_DELAY_MS = 10 * 1e3;
32443
+ var FETCH_TIMEOUT_MS = 10 * 1e3;
32444
+ function acceptReportUrl(v) {
32445
+ if (typeof v !== "string") return null;
32446
+ const t = v.trim();
32447
+ return t.startsWith("https://") ? t : null;
32448
+ }
32449
+ function startReporting(opts) {
32450
+ const { stateDir } = opts;
32451
+ const doFetch = opts.fetchImpl ?? fetch;
32452
+ const nowFn2 = opts.now ?? (() => Date.now());
32453
+ const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
32454
+ const initialDelayMs = opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
32455
+ const log = opts.log ?? (() => {
32456
+ });
32457
+ const startedAt = nowFn2();
32458
+ const maybeIdentity = readInstallIdentity(stateDir);
32459
+ if (!maybeIdentity) {
32460
+ log("[reporting] \u6CA1\u6709\u53EF\u7528\u7684\u88C5\u673A\u8EAB\u4EFD\uFF0C\u4E0A\u62A5\u5173\u95ED");
32461
+ return { stop() {
32462
+ }, async beatNow() {
32463
+ } };
32464
+ }
32465
+ const identity = maybeIdentity;
32466
+ restoreCounters(readReportState(stateDir));
32467
+ const engineVersion = readEngineVersion();
32468
+ let timer = null;
32469
+ let stopped = false;
32470
+ async function beatOnce() {
32471
+ const state2 = readReportState(stateDir);
32472
+ const enabled = liveConfig.get("reporting.enabled") !== false;
32473
+ const counters = snapshotCounters();
32474
+ if (!enabled) {
32475
+ writeReportState(stateDir, { ...counters, reportUrl: state2.reportUrl });
32476
+ return;
32477
+ }
32478
+ const fromServer = acceptReportUrl(state2.reportUrl);
32479
+ const fromConfig = (liveConfig.get("reporting.url") || "").trim();
32480
+ const url = fromServer ?? (fromConfig || DEFAULT_REPORT_URL);
32481
+ const payload = buildReportPayload({
32482
+ identity,
32483
+ stateDir,
32484
+ engineVersion,
32485
+ startedAt,
32486
+ now: nowFn2(),
32487
+ counters
32488
+ });
32489
+ let nextUrl = state2.reportUrl;
32490
+ try {
32491
+ const res = await doFetch(url, {
32492
+ method: "POST",
32493
+ headers: { "Content-Type": "application/json" },
32494
+ body: JSON.stringify(payload),
32495
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
32496
+ });
32497
+ const body = JSON.parse(await res.text());
32498
+ const adopted = acceptReportUrl(body?.reportUrl);
32499
+ if (adopted) nextUrl = adopted;
32500
+ } catch {
32501
+ }
32502
+ writeReportState(stateDir, { ...counters, reportUrl: nextUrl });
32503
+ }
32504
+ function schedule(delay) {
32505
+ if (stopped) return;
32506
+ timer = setTimeout(() => {
32507
+ void beatOnce().finally(() => schedule(intervalMs));
32508
+ }, delay);
32509
+ if (typeof timer.unref === "function") timer.unref();
32510
+ }
32511
+ schedule(initialDelayMs);
32512
+ return {
32513
+ stop() {
32514
+ stopped = true;
32515
+ if (timer) {
32516
+ clearTimeout(timer);
32517
+ timer = null;
32518
+ }
32519
+ },
32520
+ async beatNow() {
32521
+ await beatOnce();
32522
+ }
32523
+ };
32524
+ }
32525
+
32332
32526
  // src/engine-startup.ts
32333
32527
  init_provider_factory();
32334
32528
 
@@ -33499,7 +33693,7 @@ var FeishuAdapter = class _FeishuAdapter {
33499
33693
  import * as crypto4 from "crypto";
33500
33694
  import * as fs38 from "fs";
33501
33695
  import * as path38 from "path";
33502
- import * as os3 from "os";
33696
+ import * as os4 from "os";
33503
33697
  import { promises as dns } from "dns";
33504
33698
  function sleep3(ms) {
33505
33699
  return new Promise((r) => setTimeout(r, ms));
@@ -33882,7 +34076,7 @@ var WechatAdapter = class {
33882
34076
  this.config = config;
33883
34077
  this.baseUrl = config.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
33884
34078
  this.cdnBaseUrl = config.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
33885
- this.stateDir = config.stateDir || path38.join(os3.homedir?.() || "/tmp", ".engine7");
34079
+ this.stateDir = config.stateDir || path38.join(os4.homedir?.() || "/tmp", ".engine7");
33886
34080
  if (!fs38.existsSync(this.stateDir)) fs38.mkdirSync(this.stateDir, { recursive: true });
33887
34081
  }
33888
34082
  // --- ChannelAdapter interface ---
@@ -34228,7 +34422,7 @@ var WechatAdapter = class {
34228
34422
  mediaRef.full_url,
34229
34423
  timeoutSec
34230
34424
  );
34231
- const tmpDir = path38.join(os3.tmpdir(), "weixin-media");
34425
+ const tmpDir = path38.join(os4.tmpdir(), "weixin-media");
34232
34426
  if (!fs38.existsSync(tmpDir)) fs38.mkdirSync(tmpDir, { recursive: true });
34233
34427
  const tmpPath = path38.join(tmpDir, `${crypto4.randomUUID().replace(/-/g, "")}-${filename}`);
34234
34428
  fs38.writeFileSync(tmpPath, data);
@@ -34490,10 +34684,10 @@ import { createInterface as createInterface2 } from "node:readline";
34490
34684
  import { promisify as promisify2 } from "node:util";
34491
34685
  import * as fs41 from "node:fs";
34492
34686
  import * as path40 from "node:path";
34493
- import * as os4 from "node:os";
34687
+ import * as os5 from "node:os";
34494
34688
  import net4 from "net";
34495
34689
  var execFileAsync2 = promisify2(execFile2);
34496
- var VOICE_DIR = path40.join(os4.tmpdir(), "engine-voice");
34690
+ var VOICE_DIR = path40.join(os5.tmpdir(), "engine-voice");
34497
34691
  var _pyCache = null;
34498
34692
  function resolvePython(configuredPath) {
34499
34693
  if (configuredPath) {
@@ -35137,10 +35331,10 @@ var DeskBuddyAdapter = class {
35137
35331
  appendHistory(entry) {
35138
35332
  try {
35139
35333
  if (!this.historyFile) {
35140
- const os5 = __require("os");
35334
+ const os6 = __require("os");
35141
35335
  const fs57 = __require("fs");
35142
35336
  const path58 = __require("path");
35143
- const dir = path58.join(os5.homedir(), "xiaowen", "workspace", ".desktopBuddy");
35337
+ const dir = path58.join(os6.homedir(), "xiaowen", "workspace", ".desktopBuddy");
35144
35338
  fs57.mkdirSync(dir, { recursive: true });
35145
35339
  this.historyFile = path58.join(dir, "history.json");
35146
35340
  this._fs = fs57;
@@ -37466,6 +37660,7 @@ var MessageDispatcher = class {
37466
37660
  displayText = imgCount > 0 ? `${txt} [\u56FE\u7247\xD7${imgCount}]`.trim() : txt;
37467
37661
  }
37468
37662
  console.log(`[dispatcher] Executing (session=${msg2.sessionId}, source=${msg2.source}, gen=${generation}): ${displayText.slice(0, 200).replace(/\n/g, " ")}`);
37663
+ noteUserMessage(msg2.source);
37469
37664
  try {
37470
37665
  await handleQuery(
37471
37666
  msg2.text,
@@ -40919,7 +41114,7 @@ ${err.stack}`);
40919
41114
  // src/commands/model.ts
40920
41115
  import * as fs54 from "node:fs";
40921
41116
  import * as path54 from "node:path";
40922
- import { fileURLToPath as fileURLToPath7 } from "node:url";
41117
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
40923
41118
  function collectModels(config) {
40924
41119
  const aliases = config.modelAliases || {};
40925
41120
  const aliasByRef = /* @__PURE__ */ new Map();
@@ -40951,7 +41146,7 @@ function resolveConfigWritePath(config, label) {
40951
41146
  if (!configPath) return void 0;
40952
41147
  let writePath = configPath;
40953
41148
  if (configPath && !fs54.existsSync(configPath)) {
40954
- const __pFile = fileURLToPath7(import.meta.url);
41149
+ const __pFile = fileURLToPath8(import.meta.url);
40955
41150
  const __pDir = path54.dirname(__pFile);
40956
41151
  const altPath = path54.join(path54.resolve(__pDir, "../configs"), path54.basename(configPath));
40957
41152
  if (fs54.existsSync(altPath)) {
@@ -42044,6 +42239,33 @@ async function startEngine(config, opts) {
42044
42239
  // 常开 entry,stop 不触发
42045
42240
  });
42046
42241
  }
42242
+ {
42243
+ let reportingHandle;
42244
+ const stopReporting = () => {
42245
+ try {
42246
+ reportingHandle?.stop();
42247
+ } catch {
42248
+ }
42249
+ reportingHandle = void 0;
42250
+ };
42251
+ const startReportingNow = () => {
42252
+ stopReporting();
42253
+ reportingHandle = startReporting({ stateDir: config.stateDir });
42254
+ };
42255
+ const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
42256
+ reloadOrchestrator.register({
42257
+ name: "reporting",
42258
+ // url 也进 key:运维手改地址要能热生效,不用重启
42259
+ select: (cfg) => ({ enabled: cfg.reporting?.enabled !== false, url: cfg.reporting?.url ?? "" }),
42260
+ start: async () => {
42261
+ startReportingNow();
42262
+ },
42263
+ stop: async () => {
42264
+ stopReporting();
42265
+ }
42266
+ }, reportingEnabled());
42267
+ if (reportingEnabled()) startReportingNow();
42268
+ }
42047
42269
  const shutdownPromise = new Promise((resolve12) => {
42048
42270
  const shutdown = (signal) => {
42049
42271
  console.log(`[engine] ${signal} received, shutting down...`);
package/dist/main.mjs CHANGED
@@ -8215,26 +8215,26 @@ async function writeToMailbox(recipientName, message, teamName) {
8215
8215
  await writeFile2(inboxPath, "[]", { encoding: "utf-8", flag: "wx" });
8216
8216
  } catch {
8217
8217
  }
8218
- const release2 = await acquireLock(inboxPath);
8218
+ const release3 = await acquireLock(inboxPath);
8219
8219
  try {
8220
8220
  const messages = await readMailbox(recipientName, teamName);
8221
8221
  messages.push({ ...message, read: false });
8222
8222
  await writeFile2(inboxPath, JSON.stringify(messages, null, 2), "utf-8");
8223
8223
  console.log(`[mailbox] Wrote message to ${recipientName} from ${message.from} (team=${teamName || "default"}, inbox=${inboxPath})`);
8224
8224
  } finally {
8225
- await release2();
8225
+ await release3();
8226
8226
  }
8227
8227
  }
8228
8228
  async function markMessagesAsRead(agentName, teamName) {
8229
8229
  const inboxPath = getInboxPath(agentName, teamName);
8230
- const release2 = await acquireLock(inboxPath);
8230
+ const release3 = await acquireLock(inboxPath);
8231
8231
  try {
8232
8232
  const messages = await readMailbox(agentName, teamName);
8233
8233
  if (messages.length === 0) return;
8234
8234
  for (const m of messages) m.read = true;
8235
8235
  await writeFile2(inboxPath, JSON.stringify(messages, null, 2), "utf-8");
8236
8236
  } finally {
8237
- await release2();
8237
+ await release3();
8238
8238
  }
8239
8239
  }
8240
8240
  async function clearMailbox(agentName, teamName) {
@@ -10065,7 +10065,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
10065
10065
  }
10066
10066
  // ── md 读(proactive 消费):开窗预测(全 owner 扫描,end>=今天)──
10067
10067
  getOpenPredictions(now, limit = 50) {
10068
- const today = todayKey(now);
10068
+ const today2 = todayKey(now);
10069
10069
  const out = [];
10070
10070
  const usersDir = join23(this.root, "users");
10071
10071
  if (!existsSync13(usersDir)) return out;
@@ -10088,7 +10088,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
10088
10088
  const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
10089
10089
  const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
10090
10090
  if (!end || !start || !text) continue;
10091
- if (end < today) continue;
10091
+ if (end < today2) continue;
10092
10092
  out.push({ ownerId: owner.name, startDate: start, endDate: end, content: text.replace(/\n+/g, " ").slice(0, 200), evidence: evidence.replace(/\n+/g, " ").slice(0, 120) });
10093
10093
  if (out.length >= limit) return out;
10094
10094
  }
@@ -13629,7 +13629,7 @@ function loadForesightEntries(everosRoot, now, userId, ownerFilter) {
13629
13629
  return entries2.slice();
13630
13630
  }
13631
13631
  function parseForesightEntries(everosRoot, now, userId) {
13632
- const today = todayKey(now);
13632
+ const today2 = todayKey(now);
13633
13633
  let files;
13634
13634
  if (userId) {
13635
13635
  const ownDir = join30(everosRoot, userId, "default_project", "users", userId, ".foresights");
@@ -13654,7 +13654,7 @@ function parseForesightEntries(everosRoot, now, userId) {
13654
13654
  const end = (block.match(/\*\*end_time\*\*:\s*(\d{4}-\d{2}-\d{2})/) ?? [])[1];
13655
13655
  const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
13656
13656
  if (!start || !end || !text) continue;
13657
- if (end < today) continue;
13657
+ if (end < today2) continue;
13658
13658
  const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
13659
13659
  const blockOwner = (block.match(/\*\*owner_id\*\*:\s*(\S+)/) ?? [])[1] ?? fileOwner;
13660
13660
  const key = text.replace(/\s+/g, "");
@@ -21592,7 +21592,7 @@ function isPermittedRedirect(original, redirect) {
21592
21592
  async function fetchWithRedirects(url, signal, depth = 0) {
21593
21593
  if (depth > MAX_REDIRECTS) throw new Error(`\u91CD\u5B9A\u5411\u6B21\u6570\u8FC7\u591A (>${MAX_REDIRECTS})`);
21594
21594
  const controller = new AbortController();
21595
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
21595
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
21596
21596
  const onSignalAbort = () => controller.abort();
21597
21597
  signal.addEventListener("abort", onSignalAbort, { once: true });
21598
21598
  try {
@@ -21635,14 +21635,14 @@ function getTurndown() {
21635
21635
  }
21636
21636
  return td;
21637
21637
  }
21638
- var MAX_URL_LENGTH, MAX_CONTENT_LENGTH, FETCH_TIMEOUT_MS, MAX_REDIRECTS, td;
21638
+ var MAX_URL_LENGTH, MAX_CONTENT_LENGTH, FETCH_TIMEOUT_MS2, MAX_REDIRECTS, td;
21639
21639
  var init_web_fetch = __esm({
21640
21640
  "src/tools/web-fetch.ts"() {
21641
21641
  "use strict";
21642
21642
  init_registry();
21643
21643
  MAX_URL_LENGTH = 2e3;
21644
21644
  MAX_CONTENT_LENGTH = 10 * 1024 * 1024;
21645
- FETCH_TIMEOUT_MS = 6e4;
21645
+ FETCH_TIMEOUT_MS2 = 6e4;
21646
21646
  MAX_REDIRECTS = 10;
21647
21647
  registry.register({
21648
21648
  name: "web_fetch",
@@ -21713,7 +21713,7 @@ var init_web_fetch = __esm({
21713
21713
 
21714
21714
  // src/tools/wechat/wx-query.ts
21715
21715
  var wx_query_exports = {};
21716
- import { join as join55 } from "node:path";
21716
+ import { join as join57 } from "node:path";
21717
21717
  function getDescription() {
21718
21718
  return `\u67E5\u8BE2\u7FC0\u54E5\u7684\u5FAE\u4FE1\u6D88\u606F\uFF08\u7F13\u5B58\u89E3\u5BC6\u540E\u7684\u672C\u5730\u6570\u636E\u5E93\uFF09\u3002
21719
21719
 
@@ -21748,8 +21748,8 @@ var init_wx_query = __esm({
21748
21748
  "use strict";
21749
21749
  init_registry();
21750
21750
  init_live();
21751
- STATE_DIR = process.env.ENGINE7_STATE_DIR || join55(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
21752
- SCRIPT = join55("/Users/chongzhang/work/twinsun-hearth/engine", "src", "tools", "wechat", "wx_query.py");
21751
+ STATE_DIR = process.env.ENGINE7_STATE_DIR || join57(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
21752
+ SCRIPT = join57("/Users/chongzhang/work/twinsun-hearth/engine", "src", "tools", "wechat", "wx_query.py");
21753
21753
  PYTHON = "python3";
21754
21754
  registry.register({
21755
21755
  name: "wx_query",
@@ -22924,8 +22924,8 @@ async function doReloadConfigInner(config2, deps, provider, force = false) {
22924
22924
  const savedConfigPath = config2._configFilePath;
22925
22925
  let reloadConfigPath = savedConfigPath;
22926
22926
  if (!fs10.existsSync(reloadConfigPath)) {
22927
- const __filename = fileURLToPath(import.meta.url);
22928
- const __dirname = path10.dirname(__filename);
22927
+ const __filename2 = fileURLToPath(import.meta.url);
22928
+ const __dirname = path10.dirname(__filename2);
22929
22929
  const engineConfigsDir = path10.resolve(__dirname, "../configs");
22930
22930
  const altPath = path10.join(engineConfigsDir, path10.basename(savedConfigPath));
22931
22931
  if (fs10.existsSync(altPath)) {
@@ -23239,20 +23239,20 @@ function acquireEngineLock(stateDir) {
23239
23239
  } catch (err) {
23240
23240
  console.warn(`[pid-lock] write lock failed: ${err.message}`);
23241
23241
  }
23242
- const release2 = () => {
23242
+ const release3 = () => {
23243
23243
  try {
23244
23244
  const cur = fs11.existsSync(lockFile) ? fs11.readFileSync(lockFile, "utf-8").trim() : "";
23245
23245
  if (cur === String(myPid)) fs11.unlinkSync(lockFile);
23246
23246
  } catch {
23247
23247
  }
23248
23248
  };
23249
- process.on("exit", release2);
23249
+ process.on("exit", release3);
23250
23250
  process.on("SIGINT", () => {
23251
- release2();
23251
+ release3();
23252
23252
  process.exit(0);
23253
23253
  });
23254
23254
  process.on("SIGTERM", () => {
23255
- release2();
23255
+ release3();
23256
23256
  process.exit(0);
23257
23257
  });
23258
23258
  }
@@ -28686,10 +28686,10 @@ var US_HALF_LIFE_DAYS = 10;
28686
28686
  var US_MAX_LINES = 60;
28687
28687
  function readRecentMemory(workspace) {
28688
28688
  const dir = path30.join(workspace, "memory");
28689
- const today = dailyFileKeyFor();
28689
+ const today2 = dailyFileKeyFor();
28690
28690
  const yesterday = dailyFileKeyFor(Date.now() - 864e5);
28691
28691
  return {
28692
- today: readIfExists(path30.join(dir, `${today}.md`)),
28692
+ today: readIfExists(path30.join(dir, `${today2}.md`)),
28693
28693
  yesterday: readIfExists(path30.join(dir, `${yesterday}.md`))
28694
28694
  };
28695
28695
  }
@@ -32626,6 +32626,200 @@ ${pathStr}` }];
32626
32626
  channelManager.startAll();
32627
32627
  }
32628
32628
 
32629
+ // src/optional/reporting/reporter.ts
32630
+ init_live();
32631
+
32632
+ // src/provision-free.ts
32633
+ import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync34, writeFileSync as writeFileSync12 } from "node:fs";
32634
+ import { dirname as dirname10, join as join45 } from "node:path";
32635
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
32636
+ var __filename = fileURLToPath7(import.meta.url);
32637
+ var IDENTITY_FILE = "install-identity.json";
32638
+ function readContactEmailOf(parsed) {
32639
+ return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
32640
+ }
32641
+ function readInstallIdentity(stateDir) {
32642
+ try {
32643
+ const parsed = JSON.parse(readFileSync34(join45(stateDir, IDENTITY_FILE), "utf8"));
32644
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
32645
+ const email = readContactEmailOf(parsed);
32646
+ return {
32647
+ installId: parsed.installId,
32648
+ installSecret: parsed.installSecret,
32649
+ ...email ? { contactEmail: email } : {}
32650
+ };
32651
+ } catch {
32652
+ return null;
32653
+ }
32654
+ }
32655
+
32656
+ // src/optional/reporting/state.ts
32657
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync13 } from "node:fs";
32658
+ import { join as join46 } from "node:path";
32659
+ var STATE_FILE2 = "report-state.json";
32660
+ function readReportState(stateDir) {
32661
+ try {
32662
+ const p = join46(stateDir, STATE_FILE2);
32663
+ if (!existsSync23(p)) return {};
32664
+ const parsed = JSON.parse(readFileSync35(p, "utf8"));
32665
+ if (!parsed || typeof parsed !== "object") return {};
32666
+ return parsed;
32667
+ } catch {
32668
+ return {};
32669
+ }
32670
+ }
32671
+ function writeReportState(stateDir, s) {
32672
+ try {
32673
+ mkdirSync15(stateDir, { recursive: true });
32674
+ writeFileSync13(join46(stateDir, STATE_FILE2), JSON.stringify(s, null, 2) + "\n");
32675
+ } catch {
32676
+ }
32677
+ }
32678
+
32679
+ // src/optional/reporting/payload.ts
32680
+ import { createHash as createHash2 } from "node:crypto";
32681
+ import * as os3 from "node:os";
32682
+ function installKeyOf(installId, installSecret) {
32683
+ return createHash2("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
32684
+ }
32685
+ function buildReportPayload(opts) {
32686
+ const p = {
32687
+ installId: opts.identity.installId,
32688
+ installKey: installKeyOf(opts.identity.installId, opts.identity.installSecret),
32689
+ engineVersion: opts.engineVersion,
32690
+ platform: process.platform,
32691
+ osVersion: typeof os3.release === "function" ? os3.release() : "",
32692
+ arch: process.arch,
32693
+ stateDir: opts.stateDir,
32694
+ startedAt: opts.startedAt,
32695
+ // 时钟回拨时别报个负数上去
32696
+ uptimeSec: Math.max(0, Math.floor((opts.now - opts.startedAt) / 1e3)),
32697
+ msgsToday: opts.counters.msgsToday,
32698
+ msgsTotal: opts.counters.msgsTotal
32699
+ };
32700
+ if (opts.identity.contactEmail) p.contactEmail = opts.identity.contactEmail;
32701
+ return p;
32702
+ }
32703
+
32704
+ // src/core/activity-counter.ts
32705
+ init_clock();
32706
+ var USER_SOURCES = /* @__PURE__ */ new Set(["user", "inbox"]);
32707
+ var nowFn = () => /* @__PURE__ */ new Date();
32708
+ var today = "";
32709
+ var msgsToday = 0;
32710
+ var msgsTotal = 0;
32711
+ function rolloverIfNeeded(now) {
32712
+ const d = todayKey(now);
32713
+ if (d !== today) {
32714
+ today = d;
32715
+ msgsToday = 0;
32716
+ }
32717
+ }
32718
+ function noteUserMessage(source) {
32719
+ if (!source || !USER_SOURCES.has(source)) return;
32720
+ const now = nowFn();
32721
+ rolloverIfNeeded(now);
32722
+ msgsToday++;
32723
+ msgsTotal++;
32724
+ }
32725
+ function snapshotCounters() {
32726
+ rolloverIfNeeded(nowFn());
32727
+ return { day: today, msgsToday, msgsTotal };
32728
+ }
32729
+ function restoreCounters(s) {
32730
+ today = todayKey(nowFn());
32731
+ msgsToday = s.day === today && typeof s.msgsToday === "number" && s.msgsToday > 0 ? s.msgsToday : 0;
32732
+ msgsTotal = typeof s.msgsTotal === "number" && s.msgsTotal > 0 ? s.msgsTotal : 0;
32733
+ }
32734
+
32735
+ // src/optional/reporting/reporter.ts
32736
+ init_api_status();
32737
+ var DEFAULT_REPORT_URL = "https://www.twinsun.top:8866/api/engine7/report";
32738
+ var DEFAULT_INTERVAL_MS2 = 5 * 60 * 1e3;
32739
+ var DEFAULT_INITIAL_DELAY_MS = 10 * 1e3;
32740
+ var FETCH_TIMEOUT_MS = 10 * 1e3;
32741
+ function acceptReportUrl(v) {
32742
+ if (typeof v !== "string") return null;
32743
+ const t = v.trim();
32744
+ return t.startsWith("https://") ? t : null;
32745
+ }
32746
+ function startReporting(opts) {
32747
+ const { stateDir } = opts;
32748
+ const doFetch = opts.fetchImpl ?? fetch;
32749
+ const nowFn2 = opts.now ?? (() => Date.now());
32750
+ const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
32751
+ const initialDelayMs = opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
32752
+ const log = opts.log ?? (() => {
32753
+ });
32754
+ const startedAt = nowFn2();
32755
+ const maybeIdentity = readInstallIdentity(stateDir);
32756
+ if (!maybeIdentity) {
32757
+ log("[reporting] \u6CA1\u6709\u53EF\u7528\u7684\u88C5\u673A\u8EAB\u4EFD\uFF0C\u4E0A\u62A5\u5173\u95ED");
32758
+ return { stop() {
32759
+ }, async beatNow() {
32760
+ } };
32761
+ }
32762
+ const identity = maybeIdentity;
32763
+ restoreCounters(readReportState(stateDir));
32764
+ const engineVersion = readEngineVersion();
32765
+ let timer = null;
32766
+ let stopped = false;
32767
+ async function beatOnce() {
32768
+ const state2 = readReportState(stateDir);
32769
+ const enabled = liveConfig.get("reporting.enabled") !== false;
32770
+ const counters = snapshotCounters();
32771
+ if (!enabled) {
32772
+ writeReportState(stateDir, { ...counters, reportUrl: state2.reportUrl });
32773
+ return;
32774
+ }
32775
+ const fromServer = acceptReportUrl(state2.reportUrl);
32776
+ const fromConfig = (liveConfig.get("reporting.url") || "").trim();
32777
+ const url = fromServer ?? (fromConfig || DEFAULT_REPORT_URL);
32778
+ const payload = buildReportPayload({
32779
+ identity,
32780
+ stateDir,
32781
+ engineVersion,
32782
+ startedAt,
32783
+ now: nowFn2(),
32784
+ counters
32785
+ });
32786
+ let nextUrl = state2.reportUrl;
32787
+ try {
32788
+ const res = await doFetch(url, {
32789
+ method: "POST",
32790
+ headers: { "Content-Type": "application/json" },
32791
+ body: JSON.stringify(payload),
32792
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
32793
+ });
32794
+ const body = JSON.parse(await res.text());
32795
+ const adopted = acceptReportUrl(body?.reportUrl);
32796
+ if (adopted) nextUrl = adopted;
32797
+ } catch {
32798
+ }
32799
+ writeReportState(stateDir, { ...counters, reportUrl: nextUrl });
32800
+ }
32801
+ function schedule(delay) {
32802
+ if (stopped) return;
32803
+ timer = setTimeout(() => {
32804
+ void beatOnce().finally(() => schedule(intervalMs));
32805
+ }, delay);
32806
+ if (typeof timer.unref === "function") timer.unref();
32807
+ }
32808
+ schedule(initialDelayMs);
32809
+ return {
32810
+ stop() {
32811
+ stopped = true;
32812
+ if (timer) {
32813
+ clearTimeout(timer);
32814
+ timer = null;
32815
+ }
32816
+ },
32817
+ async beatNow() {
32818
+ await beatOnce();
32819
+ }
32820
+ };
32821
+ }
32822
+
32629
32823
  // src/engine-startup.ts
32630
32824
  init_provider_factory();
32631
32825
 
@@ -33796,7 +33990,7 @@ var FeishuAdapter = class _FeishuAdapter {
33796
33990
  import * as crypto4 from "crypto";
33797
33991
  import * as fs38 from "fs";
33798
33992
  import * as path38 from "path";
33799
- import * as os3 from "os";
33993
+ import * as os4 from "os";
33800
33994
  import { promises as dns } from "dns";
33801
33995
  function sleep3(ms) {
33802
33996
  return new Promise((r) => setTimeout(r, ms));
@@ -34179,7 +34373,7 @@ var WechatAdapter = class {
34179
34373
  this.config = config2;
34180
34374
  this.baseUrl = config2.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
34181
34375
  this.cdnBaseUrl = config2.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
34182
- this.stateDir = config2.stateDir || path38.join(os3.homedir?.() || "/tmp", ".engine7");
34376
+ this.stateDir = config2.stateDir || path38.join(os4.homedir?.() || "/tmp", ".engine7");
34183
34377
  if (!fs38.existsSync(this.stateDir)) fs38.mkdirSync(this.stateDir, { recursive: true });
34184
34378
  }
34185
34379
  // --- ChannelAdapter interface ---
@@ -34525,7 +34719,7 @@ var WechatAdapter = class {
34525
34719
  mediaRef.full_url,
34526
34720
  timeoutSec
34527
34721
  );
34528
- const tmpDir = path38.join(os3.tmpdir(), "weixin-media");
34722
+ const tmpDir = path38.join(os4.tmpdir(), "weixin-media");
34529
34723
  if (!fs38.existsSync(tmpDir)) fs38.mkdirSync(tmpDir, { recursive: true });
34530
34724
  const tmpPath = path38.join(tmpDir, `${crypto4.randomUUID().replace(/-/g, "")}-${filename}`);
34531
34725
  fs38.writeFileSync(tmpPath, data);
@@ -34787,10 +34981,10 @@ import { createInterface as createInterface2 } from "node:readline";
34787
34981
  import { promisify as promisify2 } from "node:util";
34788
34982
  import * as fs41 from "node:fs";
34789
34983
  import * as path40 from "node:path";
34790
- import * as os4 from "node:os";
34984
+ import * as os5 from "node:os";
34791
34985
  import net4 from "net";
34792
34986
  var execFileAsync2 = promisify2(execFile2);
34793
- var VOICE_DIR = path40.join(os4.tmpdir(), "engine-voice");
34987
+ var VOICE_DIR = path40.join(os5.tmpdir(), "engine-voice");
34794
34988
  var _pyCache = null;
34795
34989
  function resolvePython(configuredPath) {
34796
34990
  if (configuredPath) {
@@ -35434,10 +35628,10 @@ var DeskBuddyAdapter = class {
35434
35628
  appendHistory(entry) {
35435
35629
  try {
35436
35630
  if (!this.historyFile) {
35437
- const os5 = __require("os");
35631
+ const os6 = __require("os");
35438
35632
  const fs57 = __require("fs");
35439
35633
  const path58 = __require("path");
35440
- const dir = path58.join(os5.homedir(), "xiaowen", "workspace", ".desktopBuddy");
35634
+ const dir = path58.join(os6.homedir(), "xiaowen", "workspace", ".desktopBuddy");
35441
35635
  fs57.mkdirSync(dir, { recursive: true });
35442
35636
  this.historyFile = path58.join(dir, "history.json");
35443
35637
  this._fs = fs57;
@@ -37763,6 +37957,7 @@ var MessageDispatcher = class {
37763
37957
  displayText = imgCount > 0 ? `${txt} [\u56FE\u7247\xD7${imgCount}]`.trim() : txt;
37764
37958
  }
37765
37959
  console.log(`[dispatcher] Executing (session=${msg2.sessionId}, source=${msg2.source}, gen=${generation}): ${displayText.slice(0, 200).replace(/\n/g, " ")}`);
37960
+ noteUserMessage(msg2.source);
37766
37961
  try {
37767
37962
  await handleQuery(
37768
37963
  msg2.text,
@@ -41039,7 +41234,7 @@ ${err.stack}`);
41039
41234
  // src/commands/model.ts
41040
41235
  import * as fs54 from "node:fs";
41041
41236
  import * as path54 from "node:path";
41042
- import { fileURLToPath as fileURLToPath7 } from "node:url";
41237
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
41043
41238
  function collectModels(config2) {
41044
41239
  const aliases = config2.modelAliases || {};
41045
41240
  const aliasByRef = /* @__PURE__ */ new Map();
@@ -41071,7 +41266,7 @@ function resolveConfigWritePath(config2, label) {
41071
41266
  if (!configPath2) return void 0;
41072
41267
  let writePath = configPath2;
41073
41268
  if (configPath2 && !fs54.existsSync(configPath2)) {
41074
- const __pFile = fileURLToPath7(import.meta.url);
41269
+ const __pFile = fileURLToPath8(import.meta.url);
41075
41270
  const __pDir = path54.dirname(__pFile);
41076
41271
  const altPath = path54.join(path54.resolve(__pDir, "../configs"), path54.basename(configPath2));
41077
41272
  if (fs54.existsSync(altPath)) {
@@ -42164,6 +42359,33 @@ async function startEngine(config2, opts) {
42164
42359
  // 常开 entry,stop 不触发
42165
42360
  });
42166
42361
  }
42362
+ {
42363
+ let reportingHandle;
42364
+ const stopReporting = () => {
42365
+ try {
42366
+ reportingHandle?.stop();
42367
+ } catch {
42368
+ }
42369
+ reportingHandle = void 0;
42370
+ };
42371
+ const startReportingNow = () => {
42372
+ stopReporting();
42373
+ reportingHandle = startReporting({ stateDir: config2.stateDir });
42374
+ };
42375
+ const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
42376
+ reloadOrchestrator.register({
42377
+ name: "reporting",
42378
+ // url 也进 key:运维手改地址要能热生效,不用重启
42379
+ select: (cfg) => ({ enabled: cfg.reporting?.enabled !== false, url: cfg.reporting?.url ?? "" }),
42380
+ start: async () => {
42381
+ startReportingNow();
42382
+ },
42383
+ stop: async () => {
42384
+ stopReporting();
42385
+ }
42386
+ }, reportingEnabled());
42387
+ if (reportingEnabled()) startReportingNow();
42388
+ }
42167
42389
  const shutdownPromise = new Promise((resolve12) => {
42168
42390
  const shutdown = (signal) => {
42169
42391
  console.log(`[engine] ${signal} received, shutting down...`);
@@ -42206,11 +42428,11 @@ async function startEngine(config2, opts) {
42206
42428
  }
42207
42429
 
42208
42430
  // src/main.ts
42209
- import { readFileSync as readFileSync45 } from "node:fs";
42210
- import { fileURLToPath as fileURLToPath8 } from "node:url";
42211
- import { dirname as dirname15, join as join61 } from "node:path";
42212
- var __dirname2 = dirname15(fileURLToPath8(import.meta.url));
42213
- var pkg = JSON.parse(readFileSync45(join61(__dirname2, "..", "package.json"), "utf-8"));
42431
+ import { readFileSync as readFileSync47 } from "node:fs";
42432
+ import { fileURLToPath as fileURLToPath9 } from "node:url";
42433
+ import { dirname as dirname16, join as join63 } from "node:path";
42434
+ var __dirname2 = dirname16(fileURLToPath9(import.meta.url));
42435
+ var pkg = JSON.parse(readFileSync47(join63(__dirname2, "..", "package.json"), "utf-8"));
42214
42436
  var epipeSeen = false;
42215
42437
  process.on("uncaughtException", (err) => {
42216
42438
  const code = err?.code ?? "";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "engine7",
3
3
  "license": "Apache-2.0",
4
- "version": "7.1.94",
4
+ "version": "7.1.95",
5
5
  "type": "module",
6
6
  "description": "Engine 7 — 给助手一个家 / A home for your AI assistant",
7
7
  "main": "dist/main.mjs",