engine7 7.1.112 → 7.1.114

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/main.mjs CHANGED
@@ -575,7 +575,10 @@ function loadConfig(configPath2) {
575
575
  channels: agentDefaults.channels || [],
576
576
  extensions: agentDefaults.extensions,
577
577
  maxTurns: agentDefaults.maxTurns,
578
- devMode: agentDefaults.devMode === true
578
+ // ⚠️ **默认 true**(0928 翀哥拍板):开源项目,功能不按 license 分档。
579
+ // 要收起来才显式配 `agents.defaults.devMode: false`。
580
+ // 反过来写(=== true)时,任何没手配这一格的配置都会静默落进"基础引擎模式"。
581
+ devMode: agentDefaults.devMode !== false
579
582
  };
580
583
  const rawSession = raw.session || {};
581
584
  const sessionScope = Object.keys(rawSession).length > 0 ? { ...rawSession } : void 0;
@@ -3644,8 +3647,8 @@ function findShell() {
3644
3647
  timeout: 5e3,
3645
3648
  stdio: ["ignore", "pipe", "ignore"]
3646
3649
  });
3647
- const candidates = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
3648
- const nonWsl = candidates.find((p) => !isWslBash(p));
3650
+ const candidates2 = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
3651
+ const nonWsl = candidates2.find((p) => !isWslBash(p));
3649
3652
  if (nonWsl && fs6.existsSync(nonWsl)) {
3650
3653
  cachedShell = { shell: nonWsl, args: ["-c"] };
3651
3654
  return cachedShell;
@@ -4565,7 +4568,7 @@ var init_skill_gate = __esm({
4565
4568
  DEVELOPMENT_HINTS = /代码|源码|编译|构建|测试|报错|错误|bug|修复|排查|调试|实现|开发|接口|脚本|函数|类型|依赖|部署|代码 review|code|build|test|debug|implement|fix|compile|typescript|javascript|python|git/i;
4566
4569
  TECHNICAL_OBJECTS = /代码|源码|仓库|项目|模块|插件|接口|服务|引擎|engine|voice[- ]?chat|docker|容器|llm|模型|延迟|工具|脚本|函数|依赖|部署|分支|commit|api/i;
4567
4570
  TECHNICAL_ACTIONS = /优化|瘦身|提速|迁移|重构|接线|收尾|改动|加上|删掉|换成|裁剪|升级|降级|修|接入|拆分|合并|调试|排查/i;
4568
- USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy"]);
4571
+ USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy", "ccBridge"]);
4569
4572
  loadedBySession = /* @__PURE__ */ new Map();
4570
4573
  MAX_TRACKED_SESSIONS = 100;
4571
4574
  }
@@ -4582,20 +4585,20 @@ __export(paths_exports, {
4582
4585
  isAutoMemPath: () => isAutoMemPath,
4583
4586
  setMemoryDirOverride: () => setMemoryDirOverride
4584
4587
  });
4585
- import { join as join7, normalize as normalize2, sep as sep2, isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
4588
+ import { join as join7, normalize as normalize3, sep as sep2, isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
4586
4589
  function setMemoryDirOverride(dir) {
4587
4590
  _overrideMemoryDir = dir;
4588
4591
  }
4589
4592
  function getAutoMemPath(workspace) {
4590
4593
  const dir = _overrideMemoryDir ? isAbsolute2(_overrideMemoryDir) ? _overrideMemoryDir : resolve3(workspace, _overrideMemoryDir) : join7(workspace, AUTO_MEM_DIRNAME);
4591
- const normalized = normalize2(dir);
4594
+ const normalized = normalize3(dir);
4592
4595
  return (normalized + sep2).normalize("NFC");
4593
4596
  }
4594
4597
  function getAutoMemEntrypoint(workspace) {
4595
4598
  return join7(getAutoMemPath(workspace), ENTRYPOINT_NAME);
4596
4599
  }
4597
4600
  function isAutoMemPath(absolutePath, workspace) {
4598
- const normalizedPath = normalize2(absolutePath);
4601
+ const normalizedPath = normalize3(absolutePath);
4599
4602
  return normalizedPath.startsWith(getAutoMemPath(workspace));
4600
4603
  }
4601
4604
  async function ensureMemoryDirExists(memoryDir) {
@@ -7278,7 +7281,13 @@ var init_anthropic_provider = __esm({
7278
7281
  thinking: { type: "enabled", budget_tokens: thinkingBudget }
7279
7282
  } : params.disableThinking ? {
7280
7283
  // 显式关闭 thinking(智谱 GLM-5.1 需要传 disabled 才真正关闭)
7281
- thinking: { type: "disabled" },
7284
+ // 0928 修:MiniMax M3.1-Flash 系强制 adaptive,disabled 直接 400(码 2013,
7285
+ // 实证:proactive 后台调用换 M3.1 后 LLM UNREACHABLE)——该系降级发 adaptive
7286
+ ...params.model?.includes("M3.1-Flash") ? {
7287
+ thinking: { type: "adaptive" }
7288
+ } : {
7289
+ thinking: { type: "disabled" }
7290
+ },
7282
7291
  ...params.temperature !== void 0 ? { temperature: params.temperature } : {}
7283
7292
  } : {
7284
7293
  ...params.temperature !== void 0 ? { temperature: params.temperature } : {}
@@ -8108,11 +8117,11 @@ __export(license_exports, {
8108
8117
  import * as crypto2 from "node:crypto";
8109
8118
  import * as fs16 from "node:fs";
8110
8119
  import * as path15 from "node:path";
8111
- function loadLicense(stateDir, devMode) {
8120
+ function loadLicense(stateDir, devMode = true) {
8112
8121
  if (_licenseChecked) return _cachedLicense;
8113
8122
  _licenseChecked = true;
8114
8123
  if (devMode) {
8115
- console.log("[license] devMode enabled \u2014 all features active (internal development)");
8124
+ console.log("[license] \u5F00\u653E\u6A21\u5F0F \u2014 \u5168\u90E8 feature \u6FC0\u6D3B\uFF08\u8981\u5206\u6863\uFF1Aagents.defaults.devMode = false\uFF09");
8116
8125
  const allActive = {};
8117
8126
  for (const id of ALL_PAID_FEATURES) {
8118
8127
  allActive[id] = { active: true };
@@ -8122,7 +8131,7 @@ function loadLicense(stateDir, devMode) {
8122
8131
  }
8123
8132
  const licensePath = path15.join(stateDir, "license.json");
8124
8133
  if (!fs16.existsSync(licensePath)) {
8125
- console.log("[license] No license.json found, running basic engine only");
8134
+ console.log("[license] devMode=false \u4E14\u65E0 license.json \u2014\u2014 \u57FA\u7840\u5F15\u64CE\u6A21\u5F0F");
8126
8135
  return null;
8127
8136
  }
8128
8137
  try {
@@ -8175,25 +8184,21 @@ function isFeatureLicensed(featureId) {
8175
8184
  const f = _cachedLicense[featureId];
8176
8185
  return f?.active === true;
8177
8186
  }
8178
- function getLicenseStatus(stateDir) {
8179
- const licensePath = path15.join(stateDir, "license.json");
8180
- if (!fs16.existsSync(licensePath)) {
8181
- return { licensed: false, features: {} };
8182
- }
8187
+ function getLicenseStatus(stateDir, devMode = true) {
8188
+ const active = loadLicense(stateDir, devMode);
8189
+ let payload = {};
8183
8190
  try {
8184
- const raw = fs16.readFileSync(licensePath, "utf-8");
8185
- const license = JSON.parse(raw);
8186
- const active = loadLicense(stateDir);
8187
- return {
8188
- licensed: active !== null,
8189
- customer: license.customer,
8190
- licenseId: license.licenseId,
8191
- expiresAt: license.expiresAt,
8192
- features: active || {}
8193
- };
8191
+ payload = JSON.parse(fs16.readFileSync(path15.join(stateDir, "license.json"), "utf-8"));
8194
8192
  } catch {
8195
- return { licensed: false, features: {} };
8196
8193
  }
8194
+ return {
8195
+ licensed: active !== null,
8196
+ customer: payload.customer,
8197
+ licenseId: payload.licenseId,
8198
+ expiresAt: payload.expiresAt,
8199
+ openMode: devMode,
8200
+ features: active || {}
8201
+ };
8197
8202
  }
8198
8203
  function resetLicenseCache() {
8199
8204
  _cachedLicense = null;
@@ -10139,9 +10144,9 @@ Follow these principles when generating episodic memories:
10139
10144
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
10140
10145
  import { mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync7, existsSync as existsSync15, readdirSync as readdirSync2 } from "node:fs";
10141
10146
  import { join as join24 } from "node:path";
10142
- import { createHash } from "node:crypto";
10147
+ import { createHash as createHash2 } from "node:crypto";
10143
10148
  function contentHash(text) {
10144
- return createHash("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
10149
+ return createHash2("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
10145
10150
  }
10146
10151
  function mem7Root(stateDir) {
10147
10152
  return join24(stateDir, "mem7");
@@ -14319,8 +14324,8 @@ function mapOperation(raw) {
14319
14324
  }
14320
14325
  function extractJson2(text) {
14321
14326
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
14322
- const candidates = fenced ? [fenced[1], text] : [text];
14323
- for (const c of candidates) {
14327
+ const candidates2 = fenced ? [fenced[1], text] : [text];
14328
+ for (const c of candidates2) {
14324
14329
  const start = c.indexOf("{");
14325
14330
  const end = c.lastIndexOf("}");
14326
14331
  if (start === -1 || end <= start) continue;
@@ -14991,14 +14996,14 @@ async function runProactiveCycle(deps, now) {
14991
14996
  for (const act of persist.dueActions("9999-12-31T23:59:59Z", 1e3)) {
14992
14997
  if (act.status === "queued") existingTitles.add(normalizeTitle(act.title));
14993
14998
  }
14994
- const candidates = actionable.filter((s) => {
14999
+ const candidates2 = actionable.filter((s) => {
14995
15000
  if (queuedIntentIds.has(s.node.id)) return false;
14996
15001
  if (cooldownIntentIds.has(s.node.id)) return false;
14997
15002
  return true;
14998
15003
  });
14999
- if (candidates.length === 0) return 0;
15004
+ if (candidates2.length === 0) return 0;
15000
15005
  let enqueued = 0;
15001
- for (const scored of candidates) {
15006
+ for (const scored of candidates2) {
15002
15007
  const intent = scored.node;
15003
15008
  if (intent.data.status === "resolved") continue;
15004
15009
  store.updateNode(intent.id, { status: "action_scheduled" });
@@ -18456,25 +18461,43 @@ var init_tasks2 = __esm({
18456
18461
  }
18457
18462
  });
18458
18463
 
18459
- // src/startup/api-status.ts
18460
- var api_status_exports = {};
18461
- __export(api_status_exports, {
18462
- buildEngineStatus: () => buildEngineStatus,
18463
- readEngineVersion: () => readEngineVersion
18464
- });
18465
- import * as fs35 from "node:fs";
18466
- import * as path35 from "node:path";
18464
+ // src/version.ts
18465
+ import { readFileSync as readFileSync32 } from "node:fs";
18466
+ import { dirname as dirname10, join as join43 } from "node:path";
18467
18467
  import { fileURLToPath as fileURLToPath6 } from "node:url";
18468
- function readEngineVersion() {
18469
- const here = path35.dirname(fileURLToPath6(import.meta.url));
18470
- for (const p of [path35.join(here, "..", "package.json"), path35.join(here, "..", "..", "package.json")]) {
18468
+ function candidates() {
18469
+ if (_pkgDirOverride !== null) return [join43(_pkgDirOverride, "package.json")];
18470
+ const here = dirname10(fileURLToPath6(import.meta.url));
18471
+ return [join43(here, "..", "package.json"), join43(here, "..", "..", "package.json")];
18472
+ }
18473
+ function readFromDisk() {
18474
+ for (const p of candidates()) {
18471
18475
  try {
18472
- return JSON.parse(fs35.readFileSync(p, "utf8")).version || "unknown";
18476
+ return JSON.parse(readFileSync32(p, "utf-8")).version || "unknown";
18473
18477
  } catch {
18474
18478
  }
18475
18479
  }
18476
18480
  return "unknown";
18477
18481
  }
18482
+ function readEngineVersion() {
18483
+ if (_loaded === null) _loaded = readFromDisk();
18484
+ return _loaded;
18485
+ }
18486
+ var _loaded, _pkgDirOverride;
18487
+ var init_version = __esm({
18488
+ "src/version.ts"() {
18489
+ "use strict";
18490
+ _loaded = null;
18491
+ _pkgDirOverride = null;
18492
+ }
18493
+ });
18494
+
18495
+ // src/startup/api-status.ts
18496
+ var api_status_exports = {};
18497
+ __export(api_status_exports, {
18498
+ buildEngineStatus: () => buildEngineStatus,
18499
+ readEngineVersion: () => readEngineVersion
18500
+ });
18478
18501
  function hasUsable(v) {
18479
18502
  return !!v && !isUnresolvedEnvRef(v);
18480
18503
  }
@@ -18497,6 +18520,7 @@ var init_api_status = __esm({
18497
18520
  "src/startup/api-status.ts"() {
18498
18521
  "use strict";
18499
18522
  init_loader();
18523
+ init_version();
18500
18524
  }
18501
18525
  });
18502
18526
 
@@ -19073,8 +19097,8 @@ __export(voice_chat_tts_daemon_exports, {
19073
19097
  });
19074
19098
  import { spawn as spawn5 } from "child_process";
19075
19099
  import net2 from "net";
19076
- import path39 from "path";
19077
- import fs39 from "fs";
19100
+ import path38 from "path";
19101
+ import fs38 from "fs";
19078
19102
  function _log(msg2) {
19079
19103
  console.log(`[tts-daemon] ${msg2}`);
19080
19104
  }
@@ -19119,16 +19143,16 @@ async function startTtsDaemon(warmupCfg) {
19119
19143
  return;
19120
19144
  }
19121
19145
  const script = (() => {
19122
- const distDir = path39.dirname(new URL(import.meta.url).pathname);
19123
- const candidates = [
19124
- path39.join(distDir, "..", "python", "tts_daemon.py"),
19125
- path39.join(process.cwd(), "engine/src/python/tts_daemon.py"),
19146
+ const distDir = path38.dirname(new URL(import.meta.url).pathname);
19147
+ const candidates2 = [
19148
+ path38.join(distDir, "..", "python", "tts_daemon.py"),
19149
+ path38.join(process.cwd(), "engine/src/python/tts_daemon.py"),
19126
19150
  "/Users/chongzhang/work/twinsun-hearth/engine/src/python/tts_daemon.py"
19127
19151
  ];
19128
- for (const c of candidates) {
19129
- if (fs39.existsSync(c)) return c;
19152
+ for (const c of candidates2) {
19153
+ if (fs38.existsSync(c)) return c;
19130
19154
  }
19131
- return candidates[0];
19155
+ return candidates2[0];
19132
19156
  })();
19133
19157
  _log(`spawning ${script}`);
19134
19158
  const env = {
@@ -20595,7 +20619,7 @@ var init_builtInAgents = __esm({
20595
20619
  });
20596
20620
 
20597
20621
  // src/tools/AgentTool/forkSubagent.ts
20598
- import { randomUUID as randomUUID8 } from "crypto";
20622
+ import { randomUUID as randomUUID9 } from "crypto";
20599
20623
  function isForkSubagentEnabled() {
20600
20624
  return true;
20601
20625
  }
@@ -20634,7 +20658,7 @@ function buildForkedMessages(directive, parentMessages) {
20634
20658
  type: "tool_result",
20635
20659
  tool_use_id: block.id,
20636
20660
  content: FORK_PLACEHOLDER_RESULT,
20637
- id: `toolr_${randomUUID8().replace(/-/g, "").slice(0, 24)}`
20661
+ id: `toolr_${randomUUID9().replace(/-/g, "").slice(0, 24)}`
20638
20662
  }));
20639
20663
  const toolResultMessage = msg_user(
20640
20664
  buildChildMessage(directive),
@@ -20697,10 +20721,10 @@ var init_forkSubagent = __esm({
20697
20721
  });
20698
20722
 
20699
20723
  // src/tools/AgentTool/runAgent.ts
20700
- import { randomBytes as randomBytes6 } from "node:crypto";
20724
+ import { randomBytes as randomBytes7 } from "node:crypto";
20701
20725
  function generateAgentId() {
20702
20726
  const prefix = "a";
20703
- const bytes = randomBytes6(8);
20727
+ const bytes = randomBytes7(8);
20704
20728
  let id = prefix;
20705
20729
  for (let i = 0; i < 8; i++) {
20706
20730
  id += TASK_ID_ALPHABET2[bytes[i] % TASK_ID_ALPHABET2.length];
@@ -21831,7 +21855,7 @@ var init_TeamDeleteTool = __esm({
21831
21855
  },
21832
21856
  isEnabled: () => isAgentSwarmsEnabled(),
21833
21857
  handler: async (_args, ctx) => {
21834
- const teamName = ctx.teamName;
21858
+ const teamName = ctx.teamName || getLeaderTeamName() || void 0;
21835
21859
  if (teamName) {
21836
21860
  const teamFile = readTeamFile(teamName);
21837
21861
  if (teamFile) {
@@ -21915,7 +21939,7 @@ function getAgentName(ctx) {
21915
21939
  return ctx.agentName || TEAM_LEAD_NAME;
21916
21940
  }
21917
21941
  function getTeamName(ctx) {
21918
- return ctx.teamName;
21942
+ return ctx.teamName || getLeaderTeamName() || void 0;
21919
21943
  }
21920
21944
  async function handleMessage(recipientName, content, summary, ctx) {
21921
21945
  const teamName = getTeamName(ctx);
@@ -23247,17 +23271,33 @@ ${lines.join("\n\n")}`;
23247
23271
  init_loader();
23248
23272
 
23249
23273
  // src/config/secrets-env.ts
23274
+ import { createHash } from "node:crypto";
23250
23275
  import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
23251
- import { basename, dirname, join as join2 } from "node:path";
23276
+ import { basename, dirname, join as join2, normalize } from "node:path";
23252
23277
  function secretsHome() {
23253
23278
  return process.env.HOME || process.env.USERPROFILE || ".";
23254
23279
  }
23280
+ function normalizeStateDir(stateDir) {
23281
+ return normalize(stateDir).split("\\").join("/").replace(/\/+$/, "").toLowerCase();
23282
+ }
23283
+ function stateKey(stateDir) {
23284
+ return createHash("sha256").update(normalizeStateDir(stateDir)).digest("hex").slice(0, 8);
23285
+ }
23286
+ function isDefaultStateDir(stateDir) {
23287
+ return normalizeStateDir(stateDir) === normalizeStateDir(join2(secretsHome(), ".engine7"));
23288
+ }
23255
23289
  function secretsCandidates(stateDir, configName) {
23256
- return [
23257
- join2(secretsHome(), ".engine7-secrets", `${configName}.env`),
23290
+ const shared = join2(secretsHome(), ".engine7-secrets", `${configName}.env`);
23291
+ const inState = [
23258
23292
  join2(stateDir, "configs", `.env.${configName}`),
23259
23293
  join2(stateDir, "configs", ".env")
23260
23294
  ];
23295
+ if (isDefaultStateDir(stateDir)) return [shared, ...inState];
23296
+ return [
23297
+ join2(secretsHome(), ".engine7-secrets", `${configName}-${stateKey(stateDir)}.env`),
23298
+ shared,
23299
+ ...inState
23300
+ ];
23261
23301
  }
23262
23302
  function loadSecretsEnv(stateDir, configName) {
23263
23303
  for (const p of secretsCandidates(stateDir, configName)) {
@@ -23819,8 +23859,8 @@ function startConfigWatcher(config2, deps, provider) {
23819
23859
  }
23820
23860
  if (!fs10.existsSync(configPath2)) {
23821
23861
  const __filename2 = fileURLToPath(import.meta.url);
23822
- const __dirname22 = path10.dirname(__filename2);
23823
- configPath2 = path10.resolve(__dirname22, "..", raw);
23862
+ const __dirname2 = path10.dirname(__filename2);
23863
+ configPath2 = path10.resolve(__dirname2, "..", raw);
23824
23864
  }
23825
23865
  if (!fs10.existsSync(configPath2)) {
23826
23866
  console.warn(`[config-watch] config path invalid: ${configPath2}, watcher disabled`);
@@ -25911,9 +25951,9 @@ function readDbEmbeddings(dbPath) {
25911
25951
  db2.close();
25912
25952
  }
25913
25953
  }
25914
- async function llmRerank(query, candidates, topK, ollamaChatUrl, model) {
25915
- if (candidates.length <= topK) return candidates;
25916
- const manifest = candidates.map(
25954
+ async function llmRerank(query, candidates2, topK, ollamaChatUrl, model) {
25955
+ if (candidates2.length <= topK) return candidates2;
25956
+ const manifest = candidates2.map(
25917
25957
  (c, i) => `${i + 1}. ${c.filename}
25918
25958
  ${c.description || c.name}`
25919
25959
  ).join("\n");
@@ -25945,7 +25985,7 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
25945
25985
  });
25946
25986
  if (!resp.ok) {
25947
25987
  console.warn(`[memdir] rerank: ollama chat failed: ${resp.status}`);
25948
- return candidates.slice(0, topK);
25988
+ return candidates2.slice(0, topK);
25949
25989
  }
25950
25990
  const data = await resp.json();
25951
25991
  const text = data?.message?.content ?? "";
@@ -25954,19 +25994,19 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
25954
25994
  try {
25955
25995
  const parsed = JSON.parse(jsonMatch[0]);
25956
25996
  if (parsed.selected && Array.isArray(parsed.selected)) {
25957
- const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates.length).map((n) => n - 1);
25958
- const result = indices.map((i) => candidates[i]);
25959
- console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
25997
+ const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates2.length).map((n) => n - 1);
25998
+ const result = indices.map((i) => candidates2[i]);
25999
+ console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates2.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
25960
26000
  return result.slice(0, topK);
25961
26001
  }
25962
26002
  } catch {
25963
26003
  }
25964
26004
  }
25965
26005
  console.warn(`[memdir] rerank: no valid JSON in response (${text.length} chars), falling back to vector order`);
25966
- return candidates.slice(0, topK);
26006
+ return candidates2.slice(0, topK);
25967
26007
  } catch (e) {
25968
26008
  console.warn(`[memdir] rerank: error: ${e?.message ?? e}, falling back to vector order`);
25969
- return candidates.slice(0, topK);
26009
+ return candidates2.slice(0, topK);
25970
26010
  }
25971
26011
  }
25972
26012
  async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
@@ -26015,17 +26055,17 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
26015
26055
  }
26016
26056
  }
26017
26057
  scored.sort((a, b) => b.score - a.score);
26018
- const candidates = scored.slice(0, CANDIDATE_POOL);
26058
+ const candidates2 = scored.slice(0, CANDIDATE_POOL);
26019
26059
  const vectorMs = Date.now() - t0;
26020
26060
  console.log(
26021
- `[memdir] vector recall: ${candidates.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
26061
+ `[memdir] vector recall: ${candidates2.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
26022
26062
  );
26023
- if (candidates.length > 0) {
26024
- console.log(`[memdir] vector top scores: ${candidates.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates.length > 5 ? "..." : ""}`);
26063
+ if (candidates2.length > 0) {
26064
+ console.log(`[memdir] vector top scores: ${candidates2.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates2.length > 5 ? "..." : ""}`);
26025
26065
  }
26026
- if (candidates.length === 0) return [];
26066
+ if (candidates2.length === 0) return [];
26027
26067
  const rerankT0 = Date.now();
26028
- const reranked = await llmRerank(query, candidates, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
26068
+ const reranked = await llmRerank(query, candidates2, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
26029
26069
  const rerankMs = Date.now() - rerankT0;
26030
26070
  const totalMs = Date.now() - t0;
26031
26071
  console.log(`[memdir] recall total: ${totalMs}ms (vector=${vectorMs}ms, rerank=${rerankMs}ms)`);
@@ -30793,6 +30833,8 @@ function startInboxPoller(ctx) {
30793
30833
  const INBOX_POLL_INTERVAL_MS = 2e3;
30794
30834
  const INBOX_IDLE_INTERVAL_MS = 3e4;
30795
30835
  let pollCycleCount = 0;
30836
+ let lastStatusLine = "";
30837
+ let warnedNoPreview = false;
30796
30838
  let inboxPollerTimer = null;
30797
30839
  let inboxPollerStandby = false;
30798
30840
  function runInboxPollCycle() {
@@ -30829,7 +30871,11 @@ function startInboxPoller(ctx) {
30829
30871
  ).join(" ");
30830
30872
  const isMainActive = dispatcher.isActive("main");
30831
30873
  const mainPending = dispatcher.pendingCount("main");
30832
- console.log(`[inbox-poller] #${pollCycleCount} teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`);
30874
+ const statusLine = `teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`;
30875
+ if (statusLine !== lastStatusLine || pollCycleCount % 15 === 0) {
30876
+ lastStatusLine = statusLine;
30877
+ console.log(`[inbox-poller] #${pollCycleCount} ${statusLine}`);
30878
+ }
30833
30879
  const now = Date.now();
30834
30880
  const allTeamNames = new Set(teammates.filter((t) => t.status !== "killed").map((t) => t.identity.teamName));
30835
30881
  const teams = [...allTeamNames];
@@ -30907,7 +30953,6 @@ function startInboxPoller(ctx) {
30907
30953
  ${notifications}
30908
30954
  [/teammate-status]${taskSummary}`;
30909
30955
  const inboxTarget = "";
30910
- const inboxPreview = null;
30911
30956
  const submitted = dispatcher.submitMessage({
30912
30957
  text: queryContent,
30913
30958
  sessionId: "main",
@@ -30917,21 +30962,12 @@ ${notifications}
30917
30962
  priority: "next",
30918
30963
  // 对齐 CC: idle 时立即提交,不是 'later' 排队
30919
30964
  callbacks: {
30920
- onText: (text) => {
30921
- if (inboxPreview) {
30922
- inboxPreview.appendText(text);
30923
- } else {
30924
- console.warn(`[inbox-poller:onText] no preview target, Discord output dropped (${text.length} chars)`);
30925
- }
30965
+ onText: (_text) => {
30966
+ if (warnedNoPreview) return;
30967
+ warnedNoPreview = true;
30968
+ console.warn("[inbox-poller] teammate-status \u7684\u8F93\u51FA\u4E0D\u6295\u9012\u5230\u5916\u90E8\u9891\u9053\uFF08\u8BBE\u8BA1\u5982\u6B64\uFF09\uFF0C\u4EC5\u5904\u7406\u526F\u4F5C\u7528");
30926
30969
  },
30927
- onResult: async (content) => {
30928
- if (inboxPreview) {
30929
- const delivered = await inboxPreview.finish(content.trim() || "(\u5904\u7406\u5B8C\u6210)");
30930
- if (!delivered && inboxTarget) {
30931
- channelManager.send("discord", inboxTarget, content.trim() || "(\u5904\u7406\u5B8C\u6210)").catch(() => {
30932
- });
30933
- }
30934
- }
30970
+ onResult: async (_content) => {
30935
30971
  }
30936
30972
  },
30937
30973
  deps
@@ -31625,8 +31661,8 @@ var VoiceChatPlugin = class {
31625
31661
  // src/memory/everos/plugin.ts
31626
31662
  import { spawn as spawn4 } from "node:child_process";
31627
31663
  import net from "node:net";
31628
- import path36 from "node:path";
31629
- import fs36 from "node:fs";
31664
+ import path35 from "node:path";
31665
+ import fs35 from "node:fs";
31630
31666
 
31631
31667
  // src/memory/everos/config.ts
31632
31668
  var DEFAULTS4 = {
@@ -31860,8 +31896,8 @@ var EverosPlugin = class {
31860
31896
  }, 3e5);
31861
31897
  }
31862
31898
  async startEveros() {
31863
- const pythonDir = path36.dirname(this.config.lancedbPath);
31864
- const configPath2 = path36.join(pythonDir, "config.toml");
31899
+ const pythonDir = path35.dirname(this.config.lancedbPath);
31900
+ const configPath2 = path35.join(pythonDir, "config.toml");
31865
31901
  await this.ensureFcntlCompat();
31866
31902
  const venvPython = this.findVenvPython();
31867
31903
  const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
@@ -31951,19 +31987,19 @@ var EverosPlugin = class {
31951
31987
  return child;
31952
31988
  }
31953
31989
  findVenvPython() {
31954
- const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31990
+ const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31955
31991
  if (process.platform === "win32") {
31956
- return path36.join(stateDir, "everos-venv", "Scripts", "python.exe");
31992
+ return path35.join(stateDir, "everos-venv", "Scripts", "python.exe");
31957
31993
  }
31958
- return path36.join(stateDir, "everos-venv", "bin", "python");
31994
+ return path35.join(stateDir, "everos-venv", "bin", "python");
31959
31995
  }
31960
31996
  /** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
31961
31997
  async ensureVenv() {
31962
31998
  const venvPython = this.findVenvPython();
31963
- if (fs36.existsSync(venvPython)) return;
31964
- const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31965
- const venvDir = path36.join(stateDir, "everos-venv");
31966
- const everosSrc = path36.join(stateDir, "workspace", "research", "EverOS");
31999
+ if (fs35.existsSync(venvPython)) return;
32000
+ const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
32001
+ const venvDir = path35.join(stateDir, "everos-venv");
32002
+ const everosSrc = path35.join(stateDir, "workspace", "research", "EverOS");
31967
32003
  console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
31968
32004
  console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
31969
32005
  const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
@@ -31985,9 +32021,9 @@ var EverosPlugin = class {
31985
32021
  console.log(`[everos] Creating venv with ${sysPython}...`);
31986
32022
  const { execSync: execSync3 } = await import("node:child_process");
31987
32023
  execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
31988
- const pip = process.platform === "win32" ? path36.join(venvDir, "Scripts", "pip.exe") : path36.join(venvDir, "bin", "pip");
31989
- const everosReq = path36.join(this.getPythonDir(), "requirements.txt");
31990
- if (fs36.existsSync(everosReq)) {
32024
+ const pip = process.platform === "win32" ? path35.join(venvDir, "Scripts", "pip.exe") : path35.join(venvDir, "bin", "pip");
32025
+ const everosReq = path35.join(this.getPythonDir(), "requirements.txt");
32026
+ if (fs35.existsSync(everosReq)) {
31991
32027
  console.log(`[everos] Installing from requirements.txt...`);
31992
32028
  execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
31993
32029
  } else {
@@ -32002,29 +32038,29 @@ var EverosPlugin = class {
32002
32038
  }
32003
32039
  getPythonDir() {
32004
32040
  const dir = import.meta.dirname;
32005
- const candidates = [
32006
- path36.join(dir, "python"),
32007
- path36.resolve(dir, "..", "src", "memory", "everos", "python"),
32008
- path36.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
32041
+ const candidates2 = [
32042
+ path35.join(dir, "python"),
32043
+ path35.resolve(dir, "..", "src", "memory", "everos", "python"),
32044
+ path35.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
32009
32045
  ];
32010
- for (const candidate of candidates) {
32011
- if (fs36.existsSync(path36.join(candidate, "agentic_server.py"))) {
32046
+ for (const candidate of candidates2) {
32047
+ if (fs35.existsSync(path35.join(candidate, "agentic_server.py"))) {
32012
32048
  return candidate;
32013
32049
  }
32014
32050
  }
32015
- return candidates[0];
32051
+ return candidates2[0];
32016
32052
  }
32017
32053
  async ensureFcntlCompat() {
32018
32054
  if (process.platform !== "win32") return;
32019
32055
  const venvPython = this.findVenvPython();
32020
- const venvDir = path36.dirname(path36.dirname(venvPython));
32021
- const sitePackages = path36.join(venvDir, "Lib", "site-packages");
32022
- const target = path36.join(sitePackages, "fcntl.py");
32023
- if (fs36.existsSync(target)) return;
32024
- const source = path36.join(this.getPythonDir(), "fcntl_compat.py");
32025
- if (fs36.existsSync(source)) {
32056
+ const venvDir = path35.dirname(path35.dirname(venvPython));
32057
+ const sitePackages = path35.join(venvDir, "Lib", "site-packages");
32058
+ const target = path35.join(sitePackages, "fcntl.py");
32059
+ if (fs35.existsSync(target)) return;
32060
+ const source = path35.join(this.getPythonDir(), "fcntl_compat.py");
32061
+ if (fs35.existsSync(source)) {
32026
32062
  try {
32027
- fs36.copyFileSync(source, target);
32063
+ fs35.copyFileSync(source, target);
32028
32064
  console.log(`[everos] Installed fcntl compat shim to ${target}`);
32029
32065
  } catch (err) {
32030
32066
  console.warn(`[everos] Failed to install fcntl shim: ${err.message}`);
@@ -32633,8 +32669,8 @@ function createVisualBoot(ctx) {
32633
32669
  }
32634
32670
 
32635
32671
  // src/startup/message-pipeline.ts
32636
- import * as fs37 from "node:fs";
32637
- import * as path37 from "node:path";
32672
+ import * as fs36 from "node:fs";
32673
+ import * as path36 from "node:path";
32638
32674
 
32639
32675
  // src/commands/registry.ts
32640
32676
  var commands = /* @__PURE__ */ new Map();
@@ -33525,7 +33561,7 @@ function setupMessagePipeline(ctx) {
33525
33561
  console.log(`[vision] Downloading image: ${att.filename}`);
33526
33562
  let rawBuffer;
33527
33563
  if (att.url.startsWith("file://")) {
33528
- rawBuffer = fs37.readFileSync(decodeURIComponent(att.url.slice(7)));
33564
+ rawBuffer = fs36.readFileSync(decodeURIComponent(att.url.slice(7)));
33529
33565
  } else {
33530
33566
  rawBuffer = await downloadImage2(att.url, attachDispatcher);
33531
33567
  }
@@ -33533,8 +33569,8 @@ function setupMessagePipeline(ctx) {
33533
33569
  const ext = detected.split("/")[1] || "png";
33534
33570
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
33535
33571
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
33536
- const savedPath = path37.join(config2.mediaDir, `${imageId}.${ext}`);
33537
- fs37.writeFileSync(savedPath, resized.buffer);
33572
+ const savedPath = path36.join(config2.mediaDir, `${imageId}.${ext}`);
33573
+ fs36.writeFileSync(savedPath, resized.buffer);
33538
33574
  savedPaths.push(savedPath);
33539
33575
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
33540
33576
  imageBlocks.push({
@@ -33564,8 +33600,8 @@ ${pathStr}` }];
33564
33600
  }
33565
33601
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
33566
33602
  if (nonImageAttachments && nonImageAttachments.length > 0) {
33567
- const outDir = path37.join(config2.mediaDir, sessionId);
33568
- fs37.mkdirSync(outDir, { recursive: true });
33603
+ const outDir = path36.join(config2.mediaDir, sessionId);
33604
+ fs36.mkdirSync(outDir, { recursive: true });
33569
33605
  const resolved = [];
33570
33606
  for (const att of nonImageAttachments) {
33571
33607
  console.log(`[file] Downloading: ${att.filename} (${att.contentType}, ${att.size}B)`);
@@ -33573,9 +33609,9 @@ ${pathStr}` }];
33573
33609
  const resp = attachDispatcher ? await fetch(att.url, { dispatcher: attachDispatcher, signal: AbortSignal.timeout(3e4) }) : await fetch(att.url, { signal: AbortSignal.timeout(3e4) });
33574
33610
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
33575
33611
  const buffer = Buffer.from(await resp.arrayBuffer());
33576
- const safeName2 = path37.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
33577
- const savedPath = path37.join(outDir, safeName2);
33578
- fs37.writeFileSync(savedPath, buffer);
33612
+ const safeName2 = path36.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
33613
+ const savedPath = path36.join(outDir, safeName2);
33614
+ fs36.writeFileSync(savedPath, buffer);
33579
33615
  resolved.push(savedPath);
33580
33616
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
33581
33617
  } catch (err) {
@@ -33824,6 +33860,7 @@ ${pathStr}` }];
33824
33860
  init_live();
33825
33861
 
33826
33862
  // src/provision-free.ts
33863
+ import { randomBytes as randomBytes4, randomUUID as randomUUID6 } from "node:crypto";
33827
33864
  import { chmodSync as chmodSync2, existsSync as existsSync24, mkdirSync as mkdirSync16, readFileSync as readFileSync35, writeFileSync as writeFileSync13 } from "node:fs";
33828
33865
  import { dirname as dirname11, join as join46 } from "node:path";
33829
33866
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -33832,19 +33869,68 @@ var IDENTITY_FILE = "install-identity.json";
33832
33869
  function readContactEmailOf(parsed) {
33833
33870
  return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
33834
33871
  }
33835
- function readInstallIdentity(stateDir) {
33836
- try {
33837
- const parsed = JSON.parse(readFileSync35(join46(stateDir, IDENTITY_FILE), "utf8"));
33838
- if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
33872
+ function loadOrCreateInstallIdentity(stateDir) {
33873
+ const file2 = join46(stateDir, IDENTITY_FILE);
33874
+ if (existsSync24(file2)) {
33875
+ let parsed;
33876
+ try {
33877
+ parsed = JSON.parse(readFileSync35(file2, "utf8"));
33878
+ } catch (e) {
33879
+ throw new Error(
33880
+ `${IDENTITY_FILE} \u5B58\u5728\u4F46\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e.message}\uFF09\u2014\u2014\u5220\u6389\u5B83\u91CD\u8DD1\u4F1A\u9886\u4E00\u628A\u65B0\u4EE4\u724C\uFF0C\u65E7\u7684\u90A3\u628A\u53D6\u4E0D\u56DE\u6765`
33881
+ );
33882
+ }
33883
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
33884
+ throw new Error(`${IDENTITY_FILE} \u7F3A installId / installSecret \u5B57\u6BB5`);
33885
+ }
33839
33886
  const email = readContactEmailOf(parsed);
33840
33887
  return {
33841
33888
  installId: parsed.installId,
33842
33889
  installSecret: parsed.installSecret,
33843
33890
  ...email ? { contactEmail: email } : {}
33844
33891
  };
33892
+ }
33893
+ const identity = {
33894
+ // 服务端校验 installId 长度为 8..36、只允许字母数字和连字符;uuid 正好符合
33895
+ installId: randomUUID6(),
33896
+ // 服务端校验 installSecret 为 32..64 位十六进制
33897
+ installSecret: randomBytes4(32).toString("hex")
33898
+ };
33899
+ mkdirSync16(stateDir, { recursive: true });
33900
+ writeFileSync13(file2, JSON.stringify(identity, null, 2) + "\n", { mode: 384 });
33901
+ try {
33902
+ chmodSync2(file2, 384);
33845
33903
  } catch {
33846
- return null;
33847
33904
  }
33905
+ return identity;
33906
+ }
33907
+ function readInstallIdentityDetailed(stateDir) {
33908
+ const file2 = join46(stateDir, IDENTITY_FILE);
33909
+ if (!existsSync24(file2)) return { ok: false, reason: "missing" };
33910
+ let text;
33911
+ try {
33912
+ text = readFileSync35(file2, "utf8");
33913
+ } catch (e) {
33914
+ return { ok: false, reason: "corrupt", detail: `\u5B58\u5728\u4F46\u8BFB\u4E0D\u4E86\uFF08${e?.message ?? e}\uFF09` };
33915
+ }
33916
+ let parsed;
33917
+ try {
33918
+ parsed = JSON.parse(text);
33919
+ } catch (e) {
33920
+ return { ok: false, reason: "corrupt", detail: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e?.message ?? e}\uFF09` };
33921
+ }
33922
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
33923
+ return { ok: false, reason: "corrupt", detail: "\u7F3A installId / installSecret \u5B57\u6BB5" };
33924
+ }
33925
+ const email = readContactEmailOf(parsed);
33926
+ return {
33927
+ ok: true,
33928
+ identity: {
33929
+ installId: parsed.installId,
33930
+ installSecret: parsed.installSecret,
33931
+ ...email ? { contactEmail: email } : {}
33932
+ }
33933
+ };
33848
33934
  }
33849
33935
 
33850
33936
  // src/optional/reporting/state.ts
@@ -33871,10 +33957,10 @@ function writeReportState(stateDir, s) {
33871
33957
  }
33872
33958
 
33873
33959
  // src/optional/reporting/payload.ts
33874
- import { createHash as createHash2 } from "node:crypto";
33960
+ import { createHash as createHash3 } from "node:crypto";
33875
33961
  import * as os3 from "node:os";
33876
33962
  function installKeyOf(installId, installSecret) {
33877
- return createHash2("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33963
+ return createHash3("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33878
33964
  }
33879
33965
  function buildReportPayload(opts) {
33880
33966
  const p = {
@@ -34020,13 +34106,23 @@ function startReporting(opts) {
34020
34106
  const log = opts.log ?? (() => {
34021
34107
  });
34022
34108
  const startedAt = nowFn3();
34023
- const maybeIdentity = readInstallIdentity(stateDir);
34024
- if (!maybeIdentity) {
34025
- log("[reporting] \u6CA1\u6709\u53EF\u7528\u7684\u88C5\u673A\u8EAB\u4EFD\uFF0C\u4E0A\u62A5\u5173\u95ED");
34026
- return { stop() {
34027
- }, async beatNow() {
34028
- } };
34109
+ const identityRead = readInstallIdentityDetailed(stateDir);
34110
+ let maybeIdentity = null;
34111
+ if (identityRead.ok) {
34112
+ maybeIdentity = identityRead.identity;
34113
+ } else if (identityRead.reason === "missing") {
34114
+ try {
34115
+ maybeIdentity = loadOrCreateInstallIdentity(stateDir);
34116
+ log("[reporting] \u672C\u673A\u6CA1\u6709\u88C5\u673A\u8EAB\u4EFD\uFF080921 \u524D\u88C5\u7684 / \u4EE4\u724C\u662F\u4ECE\u522B\u5904\u62F7\u6765\u7684\uFF09\u2014\u2014 \u5DF2\u5C31\u5730\u8865\u5EFA\uFF0C\u6B64\u540E\u5F00\u59CB\u4E0A\u62A5");
34117
+ } catch (e) {
34118
+ log(`[reporting] \u8865\u5EFA\u88C5\u673A\u8EAB\u4EFD\u5931\u8D25\uFF1A${e?.message ?? e} \u2014\u2014 \u4E0A\u62A5\u5173\u95ED`);
34119
+ }
34120
+ } else {
34121
+ log(`[reporting] install-identity.json \u635F\u574F\uFF08${identityRead.detail}\uFF09\u2014\u2014 \u4E0A\u62A5\u5173\u95ED\u3002\u523B\u610F\u4E0D\u91CD\u5EFA\uFF1A\u91CD\u5EFA\u7B49\u4E8E\u6362\u4E00\u53F0\u673A\u5668\uFF0C\u65E7\u4EE4\u724C\u518D\u4E5F\u53D6\u4E0D\u56DE\u6765`);
34029
34122
  }
34123
+ if (!maybeIdentity) return { stop() {
34124
+ }, async beatNow() {
34125
+ } };
34030
34126
  const identity = maybeIdentity;
34031
34127
  const bootState = readReportState(stateDir);
34032
34128
  restoreCounters(bootState);
@@ -35309,8 +35405,8 @@ var FeishuAdapter = class _FeishuAdapter {
35309
35405
 
35310
35406
  // src/channels/wechat.ts
35311
35407
  import * as crypto5 from "crypto";
35312
- import * as fs38 from "fs";
35313
- import * as path38 from "path";
35408
+ import * as fs37 from "fs";
35409
+ import * as path37 from "path";
35314
35410
  import * as os4 from "os";
35315
35411
  import { promises as dns } from "dns";
35316
35412
  function sleep3(ms) {
@@ -35534,7 +35630,7 @@ function guessChatType(msg2, accountId) {
35534
35630
  return isGroup ? { type: "group", chatId: roomId || toId || String(msg2.from_user_id || "") } : { type: "dm", chatId: String(msg2.from_user_id || "") };
35535
35631
  }
35536
35632
  function mimeFromFilename(filename) {
35537
- const ext = path38.extname(filename).toLowerCase();
35633
+ const ext = path37.extname(filename).toLowerCase();
35538
35634
  const map = {
35539
35635
  ".jpg": "image/jpeg",
35540
35636
  ".jpeg": "image/jpeg",
@@ -35601,15 +35697,15 @@ var ContextTokenStore = class {
35601
35697
  cache = /* @__PURE__ */ new Map();
35602
35698
  filePath;
35603
35699
  constructor(stateDir, accountId) {
35604
- this.filePath = path38.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
35700
+ this.filePath = path37.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
35605
35701
  }
35606
35702
  k(a, p) {
35607
35703
  return `${a}:${p}`;
35608
35704
  }
35609
35705
  restore() {
35610
35706
  try {
35611
- if (fs38.existsSync(this.filePath)) {
35612
- for (const [k, v] of Object.entries(JSON.parse(fs38.readFileSync(this.filePath, "utf-8"))))
35707
+ if (fs37.existsSync(this.filePath)) {
35708
+ for (const [k, v] of Object.entries(JSON.parse(fs37.readFileSync(this.filePath, "utf-8"))))
35613
35709
  this.cache.set(k, String(v));
35614
35710
  }
35615
35711
  } catch {
@@ -35617,11 +35713,11 @@ var ContextTokenStore = class {
35617
35713
  }
35618
35714
  save() {
35619
35715
  try {
35620
- const dir = path38.dirname(this.filePath);
35621
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
35716
+ const dir = path37.dirname(this.filePath);
35717
+ if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
35622
35718
  const obj = {};
35623
35719
  this.cache.forEach((v, k) => obj[k] = v);
35624
- fs38.writeFileSync(this.filePath, JSON.stringify(obj));
35720
+ fs37.writeFileSync(this.filePath, JSON.stringify(obj));
35625
35721
  } catch {
35626
35722
  }
35627
35723
  }
@@ -35641,18 +35737,18 @@ var ContextTokenStore = class {
35641
35737
  };
35642
35738
  function loadSyncBuf(stateDir, accountId) {
35643
35739
  try {
35644
- const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
35645
- if (fs38.existsSync(fp)) return String(JSON.parse(fs38.readFileSync(fp, "utf-8")).sync_buf || "");
35740
+ const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
35741
+ if (fs37.existsSync(fp)) return String(JSON.parse(fs37.readFileSync(fp, "utf-8")).sync_buf || "");
35646
35742
  } catch {
35647
35743
  }
35648
35744
  return "";
35649
35745
  }
35650
35746
  function saveSyncBuf(stateDir, accountId, buf) {
35651
35747
  try {
35652
- const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
35653
- const dir = path38.dirname(fp);
35654
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
35655
- fs38.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
35748
+ const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
35749
+ const dir = path37.dirname(fp);
35750
+ if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
35751
+ fs37.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
35656
35752
  } catch {
35657
35753
  }
35658
35754
  }
@@ -35694,8 +35790,8 @@ var WechatAdapter = class {
35694
35790
  this.config = config2;
35695
35791
  this.baseUrl = config2.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
35696
35792
  this.cdnBaseUrl = config2.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
35697
- this.stateDir = config2.stateDir || path38.join(os4.homedir?.() || "/tmp", ".engine7");
35698
- if (!fs38.existsSync(this.stateDir)) fs38.mkdirSync(this.stateDir, { recursive: true });
35793
+ this.stateDir = config2.stateDir || path37.join(os4.homedir?.() || "/tmp", ".engine7");
35794
+ if (!fs37.existsSync(this.stateDir)) fs37.mkdirSync(this.stateDir, { recursive: true });
35699
35795
  }
35700
35796
  // --- ChannelAdapter interface ---
35701
35797
  onMessage(handler2) {
@@ -35748,8 +35844,8 @@ var WechatAdapter = class {
35748
35844
  }
35749
35845
  async sendFile(target, message, attachment) {
35750
35846
  const filePath = attachment.path;
35751
- if (!fs38.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
35752
- const plaintext = fs38.readFileSync(filePath);
35847
+ if (!fs37.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
35848
+ const plaintext = fs37.readFileSync(filePath);
35753
35849
  const mediaType = this.guessMediaType(filePath, attachment.mimeType);
35754
35850
  const filekey = crypto5.randomBytes(16).toString("hex");
35755
35851
  const aesKey = crypto5.randomBytes(16);
@@ -35781,7 +35877,7 @@ var WechatAdapter = class {
35781
35877
  aesKeyForApi,
35782
35878
  ciphertextSize: ciphertext.length,
35783
35879
  plaintextSize: rawsize,
35784
- filename: path38.basename(filePath),
35880
+ filename: path37.basename(filePath),
35785
35881
  rawfilemd5,
35786
35882
  voiceDurationSec: attachment.voiceDurationSec
35787
35883
  });
@@ -36040,10 +36136,10 @@ var WechatAdapter = class {
36040
36136
  mediaRef.full_url,
36041
36137
  timeoutSec
36042
36138
  );
36043
- const tmpDir = path38.join(os4.tmpdir(), "weixin-media");
36044
- if (!fs38.existsSync(tmpDir)) fs38.mkdirSync(tmpDir, { recursive: true });
36045
- const tmpPath = path38.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
36046
- fs38.writeFileSync(tmpPath, data);
36139
+ const tmpDir = path37.join(os4.tmpdir(), "weixin-media");
36140
+ if (!fs37.existsSync(tmpDir)) fs37.mkdirSync(tmpDir, { recursive: true });
36141
+ const tmpPath = path37.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
36142
+ fs37.writeFileSync(tmpPath, data);
36047
36143
  return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
36048
36144
  }
36049
36145
  // --- Internal: send helpers ---
@@ -36093,7 +36189,7 @@ var WechatAdapter = class {
36093
36189
  throw lastError;
36094
36190
  }
36095
36191
  guessMediaType(filePath, mimeType) {
36096
- const ext = path38.extname(filePath).toLowerCase();
36192
+ const ext = path37.extname(filePath).toLowerCase();
36097
36193
  const mime = mimeType || mimeFromFilename(filePath);
36098
36194
  if (mime.startsWith("image/")) return MEDIA_IMAGE;
36099
36195
  if (mime.startsWith("video/")) return MEDIA_VIDEO;
@@ -36139,12 +36235,12 @@ init_voice_chat_tts_daemon();
36139
36235
  import { execFile as execFile2 } from "node:child_process";
36140
36236
  import { createInterface as createInterface2 } from "node:readline";
36141
36237
  import { promisify as promisify2 } from "node:util";
36142
- import * as fs40 from "node:fs";
36143
- import * as path40 from "node:path";
36238
+ import * as fs39 from "node:fs";
36239
+ import * as path39 from "node:path";
36144
36240
  import * as os5 from "node:os";
36145
36241
  import net3 from "net";
36146
36242
  var execFileAsync2 = promisify2(execFile2);
36147
- var VOICE_DIR = path40.join(os5.tmpdir(), "engine-voice");
36243
+ var VOICE_DIR = path39.join(os5.tmpdir(), "engine-voice");
36148
36244
  function wavDurationSec(buf) {
36149
36245
  if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
36150
36246
  throw new Error("wavDurationSec: not a RIFF/WAVE file");
@@ -36193,17 +36289,17 @@ function resolvePython(configuredPath) {
36193
36289
  }
36194
36290
  if (_pyCache) return _pyCache;
36195
36291
  const localAppData = process.env.LOCALAPPDATA;
36196
- const candidates = [
36292
+ const candidates2 = [
36197
36293
  "python3",
36198
36294
  "python",
36199
36295
  // LOCALAPPDATA 缺席(非 Windows / 精简环境)时**不加这两个候选**:
36200
36296
  // 拼出来会是相对路径 'Microsoft/WindowsApps/python.exe',那可能撞上 cwd 里的东西。
36201
36297
  ...localAppData ? [
36202
- path40.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
36203
- path40.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
36298
+ path39.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
36299
+ path39.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
36204
36300
  ] : []
36205
36301
  ];
36206
- for (const c of candidates) {
36302
+ for (const c of candidates2) {
36207
36303
  try {
36208
36304
  __require("child_process").execFileSync(c, ["-c", "1"], { timeout: 5e3, stdio: "ignore" });
36209
36305
  _pyCache = c;
@@ -36220,8 +36316,8 @@ async function ttsCosyvoice(text, apiKey, model, voice, workspaceId, instruction
36220
36316
  }
36221
36317
  async function ttsCosyvoiceStream(text, cfg, onChunk) {
36222
36318
  const { apiKey, model, voice, workspaceId, instruction } = cfg;
36223
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36224
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36319
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36320
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36225
36321
  const script = `
36226
36322
  import sys, json, base64, threading
36227
36323
  import dashscope
@@ -36306,7 +36402,7 @@ print('EOS')
36306
36402
  header.writeUInt16LE(16, 34);
36307
36403
  header.write("data", 36);
36308
36404
  header.writeUInt32LE(pcm.length, 40);
36309
- fs40.writeFileSync(output, Buffer.concat([header, pcm]));
36405
+ fs39.writeFileSync(output, Buffer.concat([header, pcm]));
36310
36406
  return output;
36311
36407
  }
36312
36408
  async function ttsCosyvoiceWorker(text, cfg, onChunk) {
@@ -36416,9 +36512,9 @@ async function ttsQwen3Opus(text, cfg) {
36416
36512
  const ogg = Buffer.from(await res.arrayBuffer());
36417
36513
  if (ogg.length < 20) throw new Error("qwen3-3060 opus: empty output");
36418
36514
  const durationMs = parseInt(res.headers.get("x-duration-ms") || "0", 10);
36419
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36420
- const oggPath = path40.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
36421
- fs40.writeFileSync(oggPath, ogg);
36515
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36516
+ const oggPath = path39.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
36517
+ fs39.writeFileSync(oggPath, ogg);
36422
36518
  return { oggPath, durationMs };
36423
36519
  }
36424
36520
  async function ttsGptsovits(text, c) {
@@ -36430,8 +36526,8 @@ async function ttsGptsovits(text, c) {
36430
36526
  "gptsovits \u672A\u914D\u7F6E\uFF1A\u9700\u8981 tools.my_voice \u91CC\u7684 gptsovitsUrl / gptsovitsRefWav / gptsovitsRefText\u3002\u53C2\u8003\u97F3\u9891\u4E0E\u53C2\u8003\u6587\u672C**\u5FC5\u987B\u9010\u5B57\u5BF9\u5E94**\uFF0C\u5426\u5219\u5408\u6210\u51FA\u6765\u7684\u58F0\u97F3\u4F1A\u5E26\u6742\u97F3\uFF080925 \u8E29\u8FC7\u88C1\u574F ref.wav \u7684\u5751\uFF09\u3002"
36431
36527
  );
36432
36528
  }
36433
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36434
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36529
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36530
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36435
36531
  const params = new URLSearchParams({
36436
36532
  text,
36437
36533
  text_language: "zh",
@@ -36442,13 +36538,13 @@ async function ttsGptsovits(text, c) {
36442
36538
  const res = await fetch(`${url}/?${params}`);
36443
36539
  if (!res.ok) throw new Error(`GPT-SoVITS API ${res.status}`);
36444
36540
  const buf = Buffer.from(await res.arrayBuffer());
36445
- fs40.writeFileSync(output, buf);
36541
+ fs39.writeFileSync(output, buf);
36446
36542
  return output;
36447
36543
  }
36448
36544
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
36449
36545
  async function ttsEdge(text) {
36450
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36451
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
36546
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36547
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
36452
36548
  const script = `
36453
36549
  import asyncio, edge_tts, sys
36454
36550
  async def main():
@@ -36493,8 +36589,8 @@ async function synthQwen3(text, _sink) {
36493
36589
  const pcmChunks = [];
36494
36590
  await ttsQwen3Local(text, { url: c.url, voice: c.voice, lang: c.lang }, (pcm2) => pcmChunks.push(pcm2));
36495
36591
  const pcm = Buffer.concat(pcmChunks);
36496
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36497
- const wavPath = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36592
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36593
+ const wavPath = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36498
36594
  const header = Buffer.alloc(44);
36499
36595
  header.write("RIFF", 0);
36500
36596
  header.writeUInt32LE(36 + pcm.length, 4);
@@ -36509,7 +36605,7 @@ async function synthQwen3(text, _sink) {
36509
36605
  header.writeUInt16LE(16, 34);
36510
36606
  header.write("data", 36);
36511
36607
  header.writeUInt32LE(pcm.length, 40);
36512
- fs40.writeFileSync(wavPath, Buffer.concat([header, pcm]));
36608
+ fs39.writeFileSync(wavPath, Buffer.concat([header, pcm]));
36513
36609
  return { path: wavPath, format: "wav", unit: unit(text), voice: c.voice };
36514
36610
  }
36515
36611
  }
@@ -36828,8 +36924,365 @@ var DeskBuddyAdapter = class {
36828
36924
  }
36829
36925
  };
36830
36926
 
36927
+ // src/channels/cc-bridge.ts
36928
+ init_live();
36929
+ import fs40 from "node:fs";
36930
+ import path40 from "node:path";
36931
+ import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
36932
+ var DEFAULT_PORT = 16996;
36933
+ var CcBridgeAdapter = class {
36934
+ name = "ccBridge";
36935
+ config;
36936
+ wss = null;
36937
+ clients = /* @__PURE__ */ new Set();
36938
+ messageHandler = null;
36939
+ seq = 0;
36940
+ constructor(config2) {
36941
+ this.config = config2;
36942
+ }
36943
+ /** 工具流水默认不推(见 CcBridgeConfig.showToolDisplay) */
36944
+ get suppressToolDisplay() {
36945
+ return this.config.showToolDisplay !== true;
36946
+ }
36947
+ /** 实际监听的端口。配 `port: 0` 时由内核分配 —— 测试用它拿真端口,不用赌某个数字没被占 */
36948
+ get boundPort() {
36949
+ const a = this.wss?.address();
36950
+ return a && typeof a === "object" ? a.port : null;
36951
+ }
36952
+ onMessage(handler2) {
36953
+ this.messageHandler = handler2;
36954
+ }
36955
+ // ── outbox(持久队列)──
36956
+ //
36957
+ // 每次现读 stateDir,不缓存 —— stateDir 的 effect 是 restart,缓存下来的
36958
+ // 只会在热加载后变成指向旧目录的幽灵路径(配置访问铁律)。
36959
+ outboxFile() {
36960
+ const sd = liveConfig.get("stateDir") || process.cwd();
36961
+ return path40.join(sd, "state", "cc-bridge", "outbox.jsonl");
36962
+ }
36963
+ /**
36964
+ * 读队列。**只有 ENOENT 才等于"空队列"。**
36965
+ *
36966
+ * ⚠️ 这里原来写的是 `catch { return [] }` —— 那是**会丢数据的**:
36967
+ * 读失败(Windows 上 AV/同步盘/搜索索引的瞬时共享锁 EBUSY,或 EPERM/EACCES)
36968
+ * 返回空数组,紧接着 `enqueue` 拿这份空数组**整份覆写**,待投递的消息全没,
36969
+ * 而且一句警告都没有。而"读不了"和"不存在"是完全不同的两件事 ——
36970
+ * 这个区分正是"宁可重复,不可丢失"能不能成立的支点。
36971
+ */
36972
+ readOutbox() {
36973
+ let raw;
36974
+ try {
36975
+ raw = fs40.readFileSync(this.outboxFile(), "utf8");
36976
+ } catch (e) {
36977
+ if (e?.code === "ENOENT") return [];
36978
+ throw e;
36979
+ }
36980
+ const out = [];
36981
+ for (const line of raw.split("\n")) {
36982
+ if (!line.trim()) continue;
36983
+ try {
36984
+ out.push(JSON.parse(line));
36985
+ } catch {
36986
+ }
36987
+ }
36988
+ return out;
36989
+ }
36990
+ /**
36991
+ * 写队列:**先写 .tmp 再 rename**。
36992
+ *
36993
+ * `writeFileSync(file, ...)` 是原地截断(`w` 模式先清零再写)——
36994
+ * 在这中间被 taskkill / OOM / 断电,文件就是空的或半截,
36995
+ * 下次读回来是 [] 且无从分辨,最多 maxQueue 条待投递消息整体消失。
36996
+ * rename 到已存在文件走的是替换语义,落盘要么全旧要么全新。
36997
+ *
36998
+ * **失败要抛**:调用方(cron/heartbeat 经 `manager.send`)必须知道没存住 ——
36999
+ * 吞成 warning 的话,磁盘满/只读时客户端在线还能收到实时那条,
37000
+ * 客户端离线就是真丢,而这恰恰是这个通道要保的场景。
37001
+ */
37002
+ writeOutbox(items) {
37003
+ const file2 = this.outboxFile();
37004
+ const tmp = `${file2}.tmp`;
37005
+ fs40.mkdirSync(path40.dirname(file2), { recursive: true });
37006
+ fs40.writeFileSync(tmp, items.map((i) => JSON.stringify(i)).join("\n") + (items.length ? "\n" : ""), "utf8");
37007
+ fs40.renameSync(tmp, file2);
37008
+ }
37009
+ /** 入队(dedupe 不做 —— id 是新的,重复投递由 ack 收敛)。读/写失败都往外抛,不吞。 */
37010
+ enqueue(item) {
37011
+ const items = this.readOutbox();
37012
+ items.push(item);
37013
+ const cap = this.config.maxQueue ?? 500;
37014
+ if (items.length > cap) {
37015
+ const dropped = items.length - cap;
37016
+ items.splice(0, dropped);
37017
+ console.warn(`[ccBridge] outbox \u8D85\u8FC7 ${cap} \u6761\uFF0C\u4E22\u4E86\u6700\u8001\u7684 ${dropped} \u6761 \u2014\u2014 CC \u592A\u4E45\u6CA1\u6765\u6536`);
37018
+ }
37019
+ this.writeOutbox(items);
37020
+ }
37021
+ /**
37022
+ * ack:从队列删掉客户端确认过的。
37023
+ *
37024
+ * 读失败时**返回 0 而不是抛** —— 这里宁可"删不掉"也不能"删错":
37025
+ * ack 的语义是收敛,失败留给下一轮再收敛,代价只是重复投递。
37026
+ */
37027
+ ack(ids) {
37028
+ if (!ids.length) return 0;
37029
+ const set = new Set(ids);
37030
+ let items;
37031
+ try {
37032
+ items = this.readOutbox();
37033
+ } catch (e) {
37034
+ console.warn(`[ccBridge] ack \u8BFB\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF08\u6D88\u606F\u4F1A\u91CD\u590D\u6295\u9012\uFF09\uFF1A${e.message}`);
37035
+ return 0;
37036
+ }
37037
+ const kept = items.filter((i) => !set.has(i.id));
37038
+ const removed = items.length - kept.length;
37039
+ if (removed) {
37040
+ try {
37041
+ this.writeOutbox(kept);
37042
+ } catch (e) {
37043
+ console.warn(`[ccBridge] ack \u5199\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF1A${e.message}`);
37044
+ return 0;
37045
+ }
37046
+ }
37047
+ return removed;
37048
+ }
37049
+ /** 队列条数(读不到就算 0 —— 只用于日志/握手,不能用来做删除决策) */
37050
+ queuedCount() {
37051
+ try {
37052
+ return this.readOutbox().length;
37053
+ } catch {
37054
+ return 0;
37055
+ }
37056
+ }
37057
+ /** 读队列但**不因读失败而中断**(握手补发、日志这类"读不到也能继续"的场合) */
37058
+ safeReadOutbox(where) {
37059
+ try {
37060
+ return this.readOutbox();
37061
+ } catch (e) {
37062
+ console.warn(`[ccBridge] ${where}\uFF1A\u8BFB\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u5F53\u4F5C\u7A7A\uFF08**\u672A\u5220\u9664\u4EFB\u4F55\u4E1C\u897F**\uFF09\uFF1A${e.message}`);
37063
+ return [];
37064
+ }
37065
+ }
37066
+ /**
37067
+ * 升级握手校验。见 `connect()` 里那段注释 —— 绑回环挡不住浏览器。
37068
+ * 返回 false 时 `ws` 会直接拒绝升级(HTTP 401),连接根本建立不起来。
37069
+ */
37070
+ verifyUpgrade(info) {
37071
+ const h = info?.req?.headers ?? {};
37072
+ if (h.origin) {
37073
+ console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u5E26 Origin \u7684\u8FDE\u63A5\uFF1A${h.origin}\uFF08\u7591\u4F3C\u7F51\u9875\u53D1\u8D77\uFF09`);
37074
+ return false;
37075
+ }
37076
+ const host = String(h.host || "").replace(/:\d+$/, "").toLowerCase();
37077
+ const ok = host === "127.0.0.1" || host === "localhost" || host === "[::1]" || host === "::1" || host === "";
37078
+ if (!ok) console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u975E\u56DE\u73AF Host\uFF1A${h.host}\uFF08\u7591\u4F3C DNS rebinding\uFF09`);
37079
+ return ok;
37080
+ }
37081
+ // ── 连接 ──
37082
+ async connect() {
37083
+ const port = this.config.port ?? DEFAULT_PORT;
37084
+ try {
37085
+ fs40.mkdirSync(path40.dirname(this.outboxFile()), { recursive: true });
37086
+ } catch {
37087
+ }
37088
+ this.wss = new WebSocketServer2({
37089
+ port,
37090
+ host: "127.0.0.1",
37091
+ maxPayload: 4 * 1024 * 1024,
37092
+ // ⛔ **绑 127.0.0.1 不是安全边界。** WebSocket 不受同源策略约束,
37093
+ // 用户浏览器里**任意** http 页面都能 `new WebSocket('ws://127.0.0.1:16996')`
37094
+ // 连上来 —— 而这里连上就能以任意 `from` 往 agent 主会话注入消息
37095
+ // (实测过:伪造 Origin/Host 能成功注入一条 `from: 翀哥` 的消息)。
37096
+ // 所以必须自己校验:
37097
+ // · 带 `Origin` 头 = 网页发起的 → 直接拒(`scripts/cc-inbox.mjs` 是 Node
37098
+ // 客户端,本来就不发这个头,不受影响)
37099
+ // · `Host` 必须是本机回环 → 挡 DNS rebinding(域名解析到 127.0.0.1 时
37100
+ // Host 会是那个域名,而不是 127.0.0.1)
37101
+ verifyClient: (info) => this.verifyUpgrade(info)
37102
+ });
37103
+ this.wss.on("connection", (ws) => {
37104
+ this.clients.add(ws);
37105
+ console.log(`[ccBridge] CC connected (${this.clients.size} online)`);
37106
+ const pending2 = this.safeReadOutbox("\u63E1\u624B\u8865\u53D1");
37107
+ this.sendTo(ws, { type: "hello", channel: this.name, queued: pending2.length });
37108
+ if (pending2.length) {
37109
+ console.log(`[ccBridge] \u8865\u53D1 ${pending2.length} \u6761\u79EF\u538B`);
37110
+ this.sendTo(ws, { type: "backlog", messages: pending2 });
37111
+ }
37112
+ ws.on("message", (data) => this.handleFrame(ws, data));
37113
+ ws.on("close", () => {
37114
+ this.clients.delete(ws);
37115
+ console.log(`[ccBridge] CC disconnected (${this.clients.size} online)`);
37116
+ });
37117
+ ws.on("error", () => this.clients.delete(ws));
37118
+ });
37119
+ this.wss.on("error", (e) => {
37120
+ console.error(`[ccBridge] server error on 127.0.0.1:${port}: ${e.message}`);
37121
+ });
37122
+ await new Promise((resolve12, reject) => {
37123
+ const wss = this.wss;
37124
+ const ok = () => {
37125
+ wss.off("error", bad);
37126
+ resolve12();
37127
+ };
37128
+ const bad = (e) => {
37129
+ wss.off("listening", ok);
37130
+ reject(e);
37131
+ };
37132
+ wss.once("listening", ok);
37133
+ wss.once("error", bad);
37134
+ });
37135
+ console.log(`[ccBridge] listening on 127.0.0.1:${this.boundPort} (${this.queuedCount()} queued)`);
37136
+ }
37137
+ handleFrame(ws, data) {
37138
+ let msg2;
37139
+ try {
37140
+ msg2 = JSON.parse(String(data));
37141
+ } catch {
37142
+ this.sendTo(ws, { type: "error", error: "bad json" });
37143
+ return;
37144
+ }
37145
+ if (msg2.type === "ping") {
37146
+ this.sendTo(ws, { type: "pong" });
37147
+ return;
37148
+ }
37149
+ if (msg2.type === "ack") {
37150
+ const ids = Array.isArray(msg2.ids) ? msg2.ids.map(String) : [];
37151
+ const n = this.ack(ids);
37152
+ this.sendTo(ws, { type: "ack-ok", removed: n, queued: this.queuedCount() });
37153
+ return;
37154
+ }
37155
+ if (msg2.type === "chat") {
37156
+ const text = String(msg2.text ?? "").trim();
37157
+ if (!text) {
37158
+ this.sendTo(ws, { type: "error", error: "empty text" });
37159
+ return;
37160
+ }
37161
+ const from = String(msg2.from || this.config.userId || "cc");
37162
+ const inbound = {
37163
+ content: text,
37164
+ from,
37165
+ fromName: String(msg2.fromName || from),
37166
+ channel_id: this.name,
37167
+ channel: this.name,
37168
+ channelType: "dm",
37169
+ isBot: false,
37170
+ messageId: `ccb-${Date.now()}-${++this.seq}`,
37171
+ recvAt: Date.now()
37172
+ };
37173
+ console.log(`[ccBridge] \u2190 ${inbound.fromName}: ${text.slice(0, 80)}`);
37174
+ this.messageHandler?.(inbound);
37175
+ return;
37176
+ }
37177
+ this.sendTo(ws, { type: "error", error: `unknown type: ${msg2.type}` });
37178
+ }
37179
+ sendTo(ws, payload) {
37180
+ if (ws.readyState !== WebSocket2.OPEN) return;
37181
+ try {
37182
+ ws.send(JSON.stringify(payload));
37183
+ } catch {
37184
+ }
37185
+ }
37186
+ /** 广播给出站消息之外的控制帧(握手/pong 走 sendTo 单播) */
37187
+ broadcast(payload) {
37188
+ const s = JSON.stringify(payload);
37189
+ let n = 0;
37190
+ for (const ws of [...this.clients]) {
37191
+ if (ws.readyState !== WebSocket2.OPEN) {
37192
+ this.clients.delete(ws);
37193
+ continue;
37194
+ }
37195
+ try {
37196
+ ws.send(s);
37197
+ n++;
37198
+ } catch {
37199
+ this.clients.delete(ws);
37200
+ }
37201
+ }
37202
+ return n;
37203
+ }
37204
+ async disconnect() {
37205
+ for (const ws of [...this.clients]) {
37206
+ try {
37207
+ ws.close();
37208
+ } catch {
37209
+ }
37210
+ }
37211
+ this.clients.clear();
37212
+ this.wss?.close();
37213
+ this.wss = null;
37214
+ }
37215
+ // ── 出站 ──
37216
+ /**
37217
+ * 发消息给 CC。
37218
+ *
37219
+ * **先落 outbox 再推实时** —— 顺序反过来的话,推送成功但 CC 恰好这时断开,
37220
+ * 这条消息就既不在队列里也没送达,静默消失。宁可重复,不可丢失:
37221
+ * 重复只是多读一遍,丢失是 agent 主动说了话而没人知道。
37222
+ */
37223
+ async send(_target, message, _options) {
37224
+ if (!message.trim()) return;
37225
+ const item = {
37226
+ id: `m${Date.now()}-${++this.seq}`,
37227
+ at: (/* @__PURE__ */ new Date()).toISOString(),
37228
+ text: message,
37229
+ agent: this.config.agentName || "agent"
37230
+ };
37231
+ this.enqueue(item);
37232
+ const n = this.broadcast({ type: "message", message: item });
37233
+ if (n === 0) console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u5DF2\u5165\u961F\uFF08${this.queuedCount()} \u6761\u5F85\u6536\uFF09`);
37234
+ }
37235
+ /**
37236
+ * 发文件。
37237
+ *
37238
+ * **只送路径,不送内容**(`mode: 'path-ref'`)。这个通道两端都在 127.0.0.1 上
37239
+ * (引擎与 CC 同机),把文件 base64 塞进 WS 帧纯属浪费 —— 大文件还会把 outbox 撑爆。
37240
+ *
37241
+ * ⚠️ **但"只送路径"是把解析推迟到接收端,推迟不等于可以不做。**
37242
+ * 它有一个很难看的退化形态:发送端返回「已发送」(路径形式上没问题),
37243
+ * 接收端拿着路径在**它自己的机器上**找不到 —— **失败发生在接收端,
37244
+ * 而发送端日志一切正常**。(张小T 0928 指出的,她是对的。)
37245
+ *
37246
+ * 所以这里补两道:
37247
+ * 1. **入队前 `statSync`** —— 路径打错/文件被挪走,**在本机就报错**,
37248
+ * 而不是等接收端去找一个不存在的路径
37249
+ * 2. 帧里带 `size`/`mtimeMs` + `mode: 'path-ref'` —— 接收端知道这是**引用不是内容**,
37250
+ * 且能对一下"拿到的是不是你说的那份"
37251
+ *
37252
+ * 客户端那侧对应地**读不到就不 ack**(见 `scripts/cc-inbox.mjs`)——
37253
+ * ack 的语义是"我拿到了",拿到一半不能算。
37254
+ */
37255
+ async sendFile(_target, message, attachment) {
37256
+ let st;
37257
+ try {
37258
+ st = fs40.statSync(attachment.path);
37259
+ } catch (e) {
37260
+ throw new Error(`\u9644\u4EF6\u4E0D\u5B58\u5728\uFF0C\u672A\u53D1\u9001\uFF1A${attachment.path}\uFF08${e?.code || e?.message}\uFF09`);
37261
+ }
37262
+ const file2 = {
37263
+ mode: "path-ref",
37264
+ path: attachment.path,
37265
+ filename: attachment.filename || path40.basename(attachment.path),
37266
+ mimeType: attachment.mimeType || "application/octet-stream",
37267
+ size: st.size,
37268
+ mtimeMs: Math.round(st.mtimeMs)
37269
+ };
37270
+ const item = {
37271
+ id: `f${Date.now()}-${++this.seq}`,
37272
+ at: (/* @__PURE__ */ new Date()).toISOString(),
37273
+ text: message || "",
37274
+ agent: this.config.agentName || "agent",
37275
+ file: file2
37276
+ };
37277
+ this.enqueue(item);
37278
+ if (this.broadcast({ type: "message", message: item }) === 0) {
37279
+ console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u9644\u4EF6\u5DF2\u5165\u961F\uFF1A${file2.filename}`);
37280
+ }
37281
+ }
37282
+ };
37283
+
36831
37284
  // src/channels/manager.ts
36832
- var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy"];
37285
+ var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"];
36833
37286
  var defaultFactories = {
36834
37287
  discord: (block) => {
36835
37288
  const accounts = block.accounts || {};
@@ -36891,6 +37344,9 @@ var defaultFactories = {
36891
37344
  },
36892
37345
  deskBuddy: (block) => {
36893
37346
  return new DeskBuddyAdapter(block);
37347
+ },
37348
+ ccBridge: (block) => {
37349
+ return new CcBridgeAdapter(block);
36894
37350
  }
36895
37351
  };
36896
37352
  var ChannelManager = class {
@@ -38842,7 +39298,7 @@ Examples:
38842
39298
  properties: {
38843
39299
  to: { type: "string", description: "\u63A5\u6536\u8005ID\u3002\u586B channel_id \u65F6\u81EA\u52A8 @\u5BF9\u65B9\uFF1B\u4E0D\u586B channel_id \u65F6\u53D1 DM\u3002\u591A\u4E2A\u6536\u4EF6\u4EBA\u7528\u9017\u53F7\u5206\u9694\u3002" },
38844
39300
  channel_id: { type: "string", description: "\u9891\u9053 ID\uFF08\u53EF\u9009\uFF09\u3002\u4E0D\u586B\u5219\u53D1 DM \u7ED9 to\u3002\u90FD\u4E0D\u586B\u65F6\u56DE\u590D\u6765\u6E90\u9891\u9053\u3002" },
38845
- source: { type: "string", description: "\u6765\u6E90\u901A\u9053\uFF1Adiscord / feishu / wechat / deskBuddy\uFF08\u684C\u9762\u5C0F\u96EF\u8BED\u97F3\uFF09\u3002\u4E0D\u586B\u65F6\u4F7F\u7528\u5F53\u524D\u6765\u6E90\u901A\u9053\u3002", enum: ["discord", "feishu", "wechat", "deskBuddy"] },
39301
+ source: { type: "string", description: "\u6765\u6E90\u901A\u9053\uFF1Adiscord / feishu / wechat / deskBuddy\uFF08\u684C\u9762\u5C0F\u96EF\u8BED\u97F3\uFF09/ ccBridge\uFF08CC\uFF0C\u672C\u673A\u5F00\u53D1\u901A\u9053\uFF09\u3002\u4E0D\u586B\u65F6\u4F7F\u7528\u5F53\u524D\u6765\u6E90\u901A\u9053\u3002", enum: ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"] },
38846
39302
  content: { type: "string", description: "\u6D88\u606F\u6B63\u6587" }
38847
39303
  },
38848
39304
  required: ["content", "to"]
@@ -42610,22 +43066,22 @@ ${lines.join("\n")}`);
42610
43066
  await ctx.reply(wasOverride ? `Reset to default: **${defaultRef}** (auto-routing restored, fallback cooldowns cleared)` : `Already using default: **${defaultRef}**`);
42611
43067
  return;
42612
43068
  }
42613
- const candidates = matchModels(input, aliases, available);
42614
- if (candidates.length === 0) {
43069
+ const candidates2 = matchModels(input, aliases, available);
43070
+ if (candidates2.length === 0) {
42615
43071
  await ctx.reply(`No model matching "${input}". Use \`/model\` to see list.`);
42616
43072
  return;
42617
43073
  }
42618
- if (candidates.length > 1) {
42619
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42620
- const more = candidates.length > 10 ? `
42621
- ... and ${candidates.length - 10} more` : "";
43074
+ if (candidates2.length > 1) {
43075
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43076
+ const more = candidates2.length > 10 ? `
43077
+ ... and ${candidates2.length - 10} more` : "";
42622
43078
  await ctx.reply(`Multiple matches for "${input}":
42623
43079
  ${lines.join("\n")}${more}
42624
43080
 
42625
- Use full ref like \`/model ${candidates[0].ref}\``);
43081
+ Use full ref like \`/model ${candidates2[0].ref}\``);
42626
43082
  return;
42627
43083
  }
42628
- const target = candidates[0].ref;
43084
+ const target = candidates2[0].ref;
42629
43085
  let targetDeps = deps.modelDepsCache.get(target);
42630
43086
  if (!targetDeps) {
42631
43087
  targetDeps = deps.createModelDeps(target);
@@ -42663,8 +43119,8 @@ Use full ref like \`/model ${candidates[0].ref}\``);
42663
43119
  const writer = deps.sessions.getWriter(sid);
42664
43120
  writer.writeModelChange(pid, mid);
42665
43121
  }
42666
- console.log(`[model] Switched to ${target} (${candidates[0].name})`);
42667
- await ctx.reply(`Switched to **${target}** (${candidates[0].name})
43122
+ console.log(`[model] Switched to ${target} (${candidates2[0].name})`);
43123
+ await ctx.reply(`Switched to **${target}** (${candidates2[0].name})
42668
43124
  Auto-routing disabled \u2014 all messages use this model.
42669
43125
  \`/model auto\` to restore default.`);
42670
43126
  }
@@ -42687,20 +43143,20 @@ Usage: \`/primary provider/model\` to persist (restart required)`);
42687
43143
  return;
42688
43144
  }
42689
43145
  const { aliases, available } = collectModels(deps.config);
42690
- const candidates = matchModels(input, aliases, available);
42691
- if (candidates.length === 0) {
43146
+ const candidates2 = matchModels(input, aliases, available);
43147
+ if (candidates2.length === 0) {
42692
43148
  await ctx.reply(`No model matching "${input}".`);
42693
43149
  return;
42694
43150
  }
42695
- if (candidates.length > 1) {
42696
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43151
+ if (candidates2.length > 1) {
43152
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42697
43153
  await ctx.reply(`Multiple matches:
42698
43154
  ${lines.join("\n")}
42699
43155
 
42700
- Use full ref like \`/primary ${candidates[0].ref}\``);
43156
+ Use full ref like \`/primary ${candidates2[0].ref}\``);
42701
43157
  return;
42702
43158
  }
42703
- const target = candidates[0].ref;
43159
+ const target = candidates2[0].ref;
42704
43160
  if (!configPath2 || !writePath) {
42705
43161
  await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
42706
43162
  return;
@@ -42715,7 +43171,7 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
42715
43171
  cfg.agents.defaults.model.primary = target;
42716
43172
  await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42717
43173
  console.log(`[primary] Persisted primary=${target} to ${writePath}`);
42718
- await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates[0].name})
43174
+ await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates2[0].name})
42719
43175
  Written to config, hot-reloading provider chain now.`);
42720
43176
  } catch (err) {
42721
43177
  await ctx.reply(`\u26A0\uFE0F Failed to write config: ${err.message}`);
@@ -42741,20 +43197,20 @@ Usage: \`/vision-primary provider/model\` to persist (hot-reload)
42741
43197
  return;
42742
43198
  }
42743
43199
  const { aliases, available } = collectModels(deps.config);
42744
- const candidates = matchModels(input, aliases, available);
42745
- if (candidates.length === 0) {
43200
+ const candidates2 = matchModels(input, aliases, available);
43201
+ if (candidates2.length === 0) {
42746
43202
  await ctx.reply(`No model matching "${input}".`);
42747
43203
  return;
42748
43204
  }
42749
- if (candidates.length > 1) {
42750
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43205
+ if (candidates2.length > 1) {
43206
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42751
43207
  await ctx.reply(`Multiple matches:
42752
43208
  ${lines.join("\n")}
42753
43209
 
42754
- Use full ref like \`/vision-primary ${candidates[0].ref}\``);
43210
+ Use full ref like \`/vision-primary ${candidates2[0].ref}\``);
42755
43211
  return;
42756
43212
  }
42757
- const target = candidates[0].ref;
43213
+ const target = candidates2[0].ref;
42758
43214
  if (!configPath2 || !writePath) {
42759
43215
  await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
42760
43216
  return;
@@ -42775,7 +43231,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
42775
43231
  cfg.agents.defaults.model.vision = target;
42776
43232
  await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42777
43233
  console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
42778
- await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates[0].name})
43234
+ await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates2[0].name})
42779
43235
  Written to config. Hot-reload will apply.`);
42780
43236
  }
42781
43237
  } catch (err) {
@@ -42807,20 +43263,20 @@ Available models: use \`/model\` to see full list
42807
43263
  return;
42808
43264
  }
42809
43265
  const { aliases, available } = collectModels(deps.config);
42810
- const candidates = matchModels(input, aliases, available);
42811
- if (candidates.length === 0) {
43266
+ const candidates2 = matchModels(input, aliases, available);
43267
+ if (candidates2.length === 0) {
42812
43268
  await ctx.reply(`No model matching "${input}".`);
42813
43269
  return;
42814
43270
  }
42815
- if (candidates.length > 1) {
42816
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43271
+ if (candidates2.length > 1) {
43272
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42817
43273
  await ctx.reply(`Multiple matches:
42818
43274
  ${lines.join("\n")}
42819
43275
 
42820
- Use full ref like \`/vision-model ${candidates[0].ref}\``);
43276
+ Use full ref like \`/vision-model ${candidates2[0].ref}\``);
42821
43277
  return;
42822
43278
  }
42823
- const target = candidates[0].ref;
43279
+ const target = candidates2[0].ref;
42824
43280
  let targetDeps = deps.modelDepsCache.get(`vision:${target}`);
42825
43281
  if (!targetDeps) {
42826
43282
  targetDeps = deps.createModelDeps(target);
@@ -42833,7 +43289,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
42833
43289
  deps.setVisionOverride(target);
42834
43290
  deps.setVisionDeps(targetDeps);
42835
43291
  console.log(`[vision-model] Override: ${target}`);
42836
- await ctx.reply(`Vision switched to **${target}** (${candidates[0].name}) (immediate)
43292
+ await ctx.reply(`Vision switched to **${target}** (${candidates2[0].name}) (immediate)
42837
43293
  \`/vision-model auto\` to restore default.`);
42838
43294
  }
42839
43295
  });
@@ -43652,7 +44108,7 @@ async function startEngine(config2, opts) {
43652
44108
  };
43653
44109
  const startReportingNow = () => {
43654
44110
  stopReporting();
43655
- reportingHandle = startReporting({ stateDir: config2.stateDir });
44111
+ reportingHandle = startReporting({ stateDir: config2.stateDir, log: (m) => console.log(m) });
43656
44112
  };
43657
44113
  const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
43658
44114
  reloadOrchestrator.register({
@@ -43710,11 +44166,7 @@ async function startEngine(config2, opts) {
43710
44166
  }
43711
44167
 
43712
44168
  // src/main.ts
43713
- import { readFileSync as readFileSync48 } from "node:fs";
43714
- import { fileURLToPath as fileURLToPath9 } from "node:url";
43715
- import { dirname as dirname16, join as join66 } from "node:path";
43716
- var __dirname2 = dirname16(fileURLToPath9(import.meta.url));
43717
- var pkg = JSON.parse(readFileSync48(join66(__dirname2, "..", "package.json"), "utf-8"));
44169
+ init_version();
43718
44170
  var epipeSeen = false;
43719
44171
  process.on("uncaughtException", (err) => {
43720
44172
  const code = err?.code ?? "";
@@ -43757,10 +44209,12 @@ if (args[0] === "features") {
43757
44209
  const status = getLicenseStatus2(stateDir);
43758
44210
  const allFeatures = listFeatures2();
43759
44211
  console.log("");
43760
- if (status.licensed) {
44212
+ if (status.licenseId) {
43761
44213
  console.log(`\u{1F4C4} License: ${status.licenseId} | ${status.customer} | \u5230\u671F: ${status.expiresAt}`);
44214
+ } else if (status.openMode) {
44215
+ console.log("\u{1F4C4} License: \u65E0 \u2014\u2014 **\u5F00\u653E\u6A21\u5F0F**\uFF0C\u5168\u90E8 feature \u6FC0\u6D3B");
43762
44216
  } else {
43763
- console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F)");
44217
+ console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F \u2014 \u914D\u4E86 devMode=false)");
43764
44218
  }
43765
44219
  console.log("");
43766
44220
  for (const f of allFeatures) {
@@ -43773,15 +44227,10 @@ if (args[0] === "features") {
43773
44227
  } else if (info && !info.active) {
43774
44228
  console.log(`\u23F0 ${f.name} \u2014 \u5DF2\u8FC7\u671F${info.expiresAt ? ` (${info.expiresAt})` : ""}`);
43775
44229
  } else {
43776
- console.log(`\u274C ${f.name} \u2014 \u672A\u5F00\u901A (\u8054\u7CFB\u5F00\u901A)`);
44230
+ console.log(`\u274C ${f.name} \u2014 \u672A\u542F\u7528 (devMode=false \u4E14\u65E0 license)`);
43777
44231
  }
43778
44232
  } else {
43779
- const enabled = false;
43780
- if (enabled) {
43781
- console.log(`\u2705 ${f.name} (always)`);
43782
- } else {
43783
- console.log(`\u2B1C ${f.name} \u2014 \u672A\u542F\u7528`);
43784
- }
44233
+ console.log(`\u26AA ${f.name} \u2014 \u4E0E license \u65E0\u5173\uFF08\u5F00\u4E0D\u5F00\u770B config\uFF09`);
43785
44234
  }
43786
44235
  }
43787
44236
  console.log("");
@@ -43790,7 +44239,7 @@ if (args[0] === "features") {
43790
44239
  var secretsPath = configPath ? loadSecretsEnvForConfig(configPath) : null;
43791
44240
  console.log(secretsPath ? `[secrets] Loaded API keys from ${secretsPath}` : "[secrets] \u672A\u627E\u5230 .env \u2014\u2014 \u82E5 config \u7528\u7684\u662F env:XXX_API_KEY \u5360\u4F4D\u7B26\uFF0C\u5C06\u89E3\u6790\u5931\u8D25\uFF08401\uFF09");
43792
44241
  var config = loadConfig(configPath);
43793
- console.log(`=== Engine 7 v${pkg.version} ===`);
44242
+ console.log(`=== Engine 7 v${readEngineVersion()} ===`);
43794
44243
  console.log(`PID: ${process.pid}`);
43795
44244
  console.log(configSummary(config));
43796
44245
  startEngine(config, { cliMode: true }).catch((err) => {