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.
@@ -3083,8 +3083,8 @@ function findShell() {
3083
3083
  timeout: 5e3,
3084
3084
  stdio: ["ignore", "pipe", "ignore"]
3085
3085
  });
3086
- const candidates = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
3087
- const nonWsl = candidates.find((p) => !isWslBash(p));
3086
+ const candidates2 = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
3087
+ const nonWsl = candidates2.find((p) => !isWslBash(p));
3088
3088
  if (nonWsl && fs5.existsSync(nonWsl)) {
3089
3089
  cachedShell = { shell: nonWsl, args: ["-c"] };
3090
3090
  return cachedShell;
@@ -4004,7 +4004,7 @@ var init_skill_gate = __esm({
4004
4004
  DEVELOPMENT_HINTS = /代码|源码|编译|构建|测试|报错|错误|bug|修复|排查|调试|实现|开发|接口|脚本|函数|类型|依赖|部署|代码 review|code|build|test|debug|implement|fix|compile|typescript|javascript|python|git/i;
4005
4005
  TECHNICAL_OBJECTS = /代码|源码|仓库|项目|模块|插件|接口|服务|引擎|engine|voice[- ]?chat|docker|容器|llm|模型|延迟|工具|脚本|函数|依赖|部署|分支|commit|api/i;
4006
4006
  TECHNICAL_ACTIONS = /优化|瘦身|提速|迁移|重构|接线|收尾|改动|加上|删掉|换成|裁剪|升级|降级|修|接入|拆分|合并|调试|排查/i;
4007
- USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy"]);
4007
+ USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy", "ccBridge"]);
4008
4008
  loadedBySession = /* @__PURE__ */ new Map();
4009
4009
  MAX_TRACKED_SESSIONS = 100;
4010
4010
  }
@@ -6717,7 +6717,13 @@ var init_anthropic_provider = __esm({
6717
6717
  thinking: { type: "enabled", budget_tokens: thinkingBudget }
6718
6718
  } : params.disableThinking ? {
6719
6719
  // 显式关闭 thinking(智谱 GLM-5.1 需要传 disabled 才真正关闭)
6720
- thinking: { type: "disabled" },
6720
+ // 0928 修:MiniMax M3.1-Flash 系强制 adaptive,disabled 直接 400(码 2013,
6721
+ // 实证:proactive 后台调用换 M3.1 后 LLM UNREACHABLE)——该系降级发 adaptive
6722
+ ...params.model?.includes("M3.1-Flash") ? {
6723
+ thinking: { type: "adaptive" }
6724
+ } : {
6725
+ thinking: { type: "disabled" }
6726
+ },
6721
6727
  ...params.temperature !== void 0 ? { temperature: params.temperature } : {}
6722
6728
  } : {
6723
6729
  ...params.temperature !== void 0 ? { temperature: params.temperature } : {}
@@ -7786,7 +7792,10 @@ function loadConfig(configPath) {
7786
7792
  channels: agentDefaults.channels || [],
7787
7793
  extensions: agentDefaults.extensions,
7788
7794
  maxTurns: agentDefaults.maxTurns,
7789
- devMode: agentDefaults.devMode === true
7795
+ // ⚠️ **默认 true**(0928 翀哥拍板):开源项目,功能不按 license 分档。
7796
+ // 要收起来才显式配 `agents.defaults.devMode: false`。
7797
+ // 反过来写(=== true)时,任何没手配这一格的配置都会静默落进"基础引擎模式"。
7798
+ devMode: agentDefaults.devMode !== false
7790
7799
  };
7791
7800
  const rawSession = raw.session || {};
7792
7801
  const sessionScope = Object.keys(rawSession).length > 0 ? { ...rawSession } : void 0;
@@ -10001,9 +10010,9 @@ Follow these principles when generating episodic memories:
10001
10010
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
10002
10011
  import { mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6, existsSync as existsSync14, readdirSync as readdirSync2 } from "node:fs";
10003
10012
  import { join as join24 } from "node:path";
10004
- import { createHash } from "node:crypto";
10013
+ import { createHash as createHash2 } from "node:crypto";
10005
10014
  function contentHash(text) {
10006
- return createHash("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
10015
+ return createHash2("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
10007
10016
  }
10008
10017
  function mem7Root(stateDir) {
10009
10018
  return join24(stateDir, "mem7");
@@ -14181,8 +14190,8 @@ function mapOperation(raw) {
14181
14190
  }
14182
14191
  function extractJson2(text) {
14183
14192
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
14184
- const candidates = fenced ? [fenced[1], text] : [text];
14185
- for (const c of candidates) {
14193
+ const candidates2 = fenced ? [fenced[1], text] : [text];
14194
+ for (const c of candidates2) {
14186
14195
  const start = c.indexOf("{");
14187
14196
  const end = c.lastIndexOf("}");
14188
14197
  if (start === -1 || end <= start) continue;
@@ -14853,14 +14862,14 @@ async function runProactiveCycle(deps, now) {
14853
14862
  for (const act of persist.dueActions("9999-12-31T23:59:59Z", 1e3)) {
14854
14863
  if (act.status === "queued") existingTitles.add(normalizeTitle(act.title));
14855
14864
  }
14856
- const candidates = actionable.filter((s) => {
14865
+ const candidates2 = actionable.filter((s) => {
14857
14866
  if (queuedIntentIds.has(s.node.id)) return false;
14858
14867
  if (cooldownIntentIds.has(s.node.id)) return false;
14859
14868
  return true;
14860
14869
  });
14861
- if (candidates.length === 0) return 0;
14870
+ if (candidates2.length === 0) return 0;
14862
14871
  let enqueued = 0;
14863
- for (const scored of candidates) {
14872
+ for (const scored of candidates2) {
14864
14873
  const intent = scored.node;
14865
14874
  if (intent.data.status === "resolved") continue;
14866
14875
  store.updateNode(intent.id, { status: "action_scheduled" });
@@ -18318,25 +18327,43 @@ var init_tasks2 = __esm({
18318
18327
  }
18319
18328
  });
18320
18329
 
18321
- // src/startup/api-status.ts
18322
- var api_status_exports = {};
18323
- __export(api_status_exports, {
18324
- buildEngineStatus: () => buildEngineStatus,
18325
- readEngineVersion: () => readEngineVersion
18326
- });
18327
- import * as fs35 from "node:fs";
18328
- import * as path35 from "node:path";
18330
+ // src/version.ts
18331
+ import { readFileSync as readFileSync31 } from "node:fs";
18332
+ import { dirname as dirname10, join as join43 } from "node:path";
18329
18333
  import { fileURLToPath as fileURLToPath6 } from "node:url";
18330
- function readEngineVersion() {
18331
- const here = path35.dirname(fileURLToPath6(import.meta.url));
18332
- for (const p of [path35.join(here, "..", "package.json"), path35.join(here, "..", "..", "package.json")]) {
18334
+ function candidates() {
18335
+ if (_pkgDirOverride !== null) return [join43(_pkgDirOverride, "package.json")];
18336
+ const here = dirname10(fileURLToPath6(import.meta.url));
18337
+ return [join43(here, "..", "package.json"), join43(here, "..", "..", "package.json")];
18338
+ }
18339
+ function readFromDisk() {
18340
+ for (const p of candidates()) {
18333
18341
  try {
18334
- return JSON.parse(fs35.readFileSync(p, "utf8")).version || "unknown";
18342
+ return JSON.parse(readFileSync31(p, "utf-8")).version || "unknown";
18335
18343
  } catch {
18336
18344
  }
18337
18345
  }
18338
18346
  return "unknown";
18339
18347
  }
18348
+ function readEngineVersion() {
18349
+ if (_loaded === null) _loaded = readFromDisk();
18350
+ return _loaded;
18351
+ }
18352
+ var _loaded, _pkgDirOverride;
18353
+ var init_version = __esm({
18354
+ "src/version.ts"() {
18355
+ "use strict";
18356
+ _loaded = null;
18357
+ _pkgDirOverride = null;
18358
+ }
18359
+ });
18360
+
18361
+ // src/startup/api-status.ts
18362
+ var api_status_exports = {};
18363
+ __export(api_status_exports, {
18364
+ buildEngineStatus: () => buildEngineStatus,
18365
+ readEngineVersion: () => readEngineVersion
18366
+ });
18340
18367
  function hasUsable(v) {
18341
18368
  return !!v && !isUnresolvedEnvRef(v);
18342
18369
  }
@@ -18359,6 +18386,7 @@ var init_api_status = __esm({
18359
18386
  "src/startup/api-status.ts"() {
18360
18387
  "use strict";
18361
18388
  init_loader();
18389
+ init_version();
18362
18390
  }
18363
18391
  });
18364
18392
 
@@ -18935,8 +18963,8 @@ __export(voice_chat_tts_daemon_exports, {
18935
18963
  });
18936
18964
  import { spawn as spawn5 } from "child_process";
18937
18965
  import net2 from "net";
18938
- import path39 from "path";
18939
- import fs39 from "fs";
18966
+ import path38 from "path";
18967
+ import fs38 from "fs";
18940
18968
  function _log(msg2) {
18941
18969
  console.log(`[tts-daemon] ${msg2}`);
18942
18970
  }
@@ -18981,16 +19009,16 @@ async function startTtsDaemon(warmupCfg) {
18981
19009
  return;
18982
19010
  }
18983
19011
  const script = (() => {
18984
- const distDir = path39.dirname(new URL(import.meta.url).pathname);
18985
- const candidates = [
18986
- path39.join(distDir, "..", "python", "tts_daemon.py"),
18987
- path39.join(process.cwd(), "engine/src/python/tts_daemon.py"),
19012
+ const distDir = path38.dirname(new URL(import.meta.url).pathname);
19013
+ const candidates2 = [
19014
+ path38.join(distDir, "..", "python", "tts_daemon.py"),
19015
+ path38.join(process.cwd(), "engine/src/python/tts_daemon.py"),
18988
19016
  "/Users/chongzhang/work/twinsun-hearth/engine/src/python/tts_daemon.py"
18989
19017
  ];
18990
- for (const c of candidates) {
18991
- if (fs39.existsSync(c)) return c;
19018
+ for (const c of candidates2) {
19019
+ if (fs38.existsSync(c)) return c;
18992
19020
  }
18993
- return candidates[0];
19021
+ return candidates2[0];
18994
19022
  })();
18995
19023
  _log(`spawning ${script}`);
18996
19024
  const env = {
@@ -20457,7 +20485,7 @@ var init_builtInAgents = __esm({
20457
20485
  });
20458
20486
 
20459
20487
  // src/tools/AgentTool/forkSubagent.ts
20460
- import { randomUUID as randomUUID8 } from "crypto";
20488
+ import { randomUUID as randomUUID9 } from "crypto";
20461
20489
  function isForkSubagentEnabled() {
20462
20490
  return true;
20463
20491
  }
@@ -20496,7 +20524,7 @@ function buildForkedMessages(directive, parentMessages) {
20496
20524
  type: "tool_result",
20497
20525
  tool_use_id: block.id,
20498
20526
  content: FORK_PLACEHOLDER_RESULT,
20499
- id: `toolr_${randomUUID8().replace(/-/g, "").slice(0, 24)}`
20527
+ id: `toolr_${randomUUID9().replace(/-/g, "").slice(0, 24)}`
20500
20528
  }));
20501
20529
  const toolResultMessage = msg_user(
20502
20530
  buildChildMessage(directive),
@@ -20559,10 +20587,10 @@ var init_forkSubagent = __esm({
20559
20587
  });
20560
20588
 
20561
20589
  // src/tools/AgentTool/runAgent.ts
20562
- import { randomBytes as randomBytes6 } from "node:crypto";
20590
+ import { randomBytes as randomBytes7 } from "node:crypto";
20563
20591
  function generateAgentId() {
20564
20592
  const prefix = "a";
20565
- const bytes = randomBytes6(8);
20593
+ const bytes = randomBytes7(8);
20566
20594
  let id = prefix;
20567
20595
  for (let i = 0; i < 8; i++) {
20568
20596
  id += TASK_ID_ALPHABET2[bytes[i] % TASK_ID_ALPHABET2.length];
@@ -21693,7 +21721,7 @@ var init_TeamDeleteTool = __esm({
21693
21721
  },
21694
21722
  isEnabled: () => isAgentSwarmsEnabled(),
21695
21723
  handler: async (_args, ctx) => {
21696
- const teamName = ctx.teamName;
21724
+ const teamName = ctx.teamName || getLeaderTeamName() || void 0;
21697
21725
  if (teamName) {
21698
21726
  const teamFile = readTeamFile(teamName);
21699
21727
  if (teamFile) {
@@ -21777,7 +21805,7 @@ function getAgentName(ctx) {
21777
21805
  return ctx.agentName || TEAM_LEAD_NAME;
21778
21806
  }
21779
21807
  function getTeamName(ctx) {
21780
- return ctx.teamName;
21808
+ return ctx.teamName || getLeaderTeamName() || void 0;
21781
21809
  }
21782
21810
  async function handleMessage(recipientName, content, summary, ctx) {
21783
21811
  const teamName = getTeamName(ctx);
@@ -23138,16 +23166,32 @@ import * as path10 from "node:path";
23138
23166
  import { fileURLToPath } from "node:url";
23139
23167
 
23140
23168
  // src/config/secrets-env.ts
23141
- import { basename, dirname, join as join9 } from "node:path";
23169
+ import { createHash } from "node:crypto";
23170
+ import { basename, dirname, join as join9, normalize as normalize3 } from "node:path";
23142
23171
  function secretsHome() {
23143
23172
  return process.env.HOME || process.env.USERPROFILE || ".";
23144
23173
  }
23174
+ function normalizeStateDir(stateDir) {
23175
+ return normalize3(stateDir).split("\\").join("/").replace(/\/+$/, "").toLowerCase();
23176
+ }
23177
+ function stateKey(stateDir) {
23178
+ return createHash("sha256").update(normalizeStateDir(stateDir)).digest("hex").slice(0, 8);
23179
+ }
23180
+ function isDefaultStateDir(stateDir) {
23181
+ return normalizeStateDir(stateDir) === normalizeStateDir(join9(secretsHome(), ".engine7"));
23182
+ }
23145
23183
  function secretsCandidates(stateDir, configName) {
23146
- return [
23147
- join9(secretsHome(), ".engine7-secrets", `${configName}.env`),
23184
+ const shared = join9(secretsHome(), ".engine7-secrets", `${configName}.env`);
23185
+ const inState = [
23148
23186
  join9(stateDir, "configs", `.env.${configName}`),
23149
23187
  join9(stateDir, "configs", ".env")
23150
23188
  ];
23189
+ if (isDefaultStateDir(stateDir)) return [shared, ...inState];
23190
+ return [
23191
+ join9(secretsHome(), ".engine7-secrets", `${configName}-${stateKey(stateDir)}.env`),
23192
+ shared,
23193
+ ...inState
23194
+ ];
23151
23195
  }
23152
23196
 
23153
23197
  // src/reload/orchestrator.ts
@@ -24327,11 +24371,11 @@ var ALL_PAID_FEATURES = [
24327
24371
  "report-gen",
24328
24372
  "custom-soul"
24329
24373
  ];
24330
- function loadLicense(stateDir, devMode) {
24374
+ function loadLicense(stateDir, devMode = true) {
24331
24375
  if (_licenseChecked) return _cachedLicense;
24332
24376
  _licenseChecked = true;
24333
24377
  if (devMode) {
24334
- console.log("[license] devMode enabled \u2014 all features active (internal development)");
24378
+ console.log("[license] \u5F00\u653E\u6A21\u5F0F \u2014 \u5168\u90E8 feature \u6FC0\u6D3B\uFF08\u8981\u5206\u6863\uFF1Aagents.defaults.devMode = false\uFF09");
24335
24379
  const allActive = {};
24336
24380
  for (const id of ALL_PAID_FEATURES) {
24337
24381
  allActive[id] = { active: true };
@@ -24341,7 +24385,7 @@ function loadLicense(stateDir, devMode) {
24341
24385
  }
24342
24386
  const licensePath = path15.join(stateDir, "license.json");
24343
24387
  if (!fs16.existsSync(licensePath)) {
24344
- console.log("[license] No license.json found, running basic engine only");
24388
+ console.log("[license] devMode=false \u4E14\u65E0 license.json \u2014\u2014 \u57FA\u7840\u5F15\u64CE\u6A21\u5F0F");
24345
24389
  return null;
24346
24390
  }
24347
24391
  try {
@@ -25622,9 +25666,9 @@ function readDbEmbeddings(dbPath) {
25622
25666
  db2.close();
25623
25667
  }
25624
25668
  }
25625
- async function llmRerank(query, candidates, topK, ollamaChatUrl, model) {
25626
- if (candidates.length <= topK) return candidates;
25627
- const manifest = candidates.map(
25669
+ async function llmRerank(query, candidates2, topK, ollamaChatUrl, model) {
25670
+ if (candidates2.length <= topK) return candidates2;
25671
+ const manifest = candidates2.map(
25628
25672
  (c, i) => `${i + 1}. ${c.filename}
25629
25673
  ${c.description || c.name}`
25630
25674
  ).join("\n");
@@ -25656,7 +25700,7 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
25656
25700
  });
25657
25701
  if (!resp.ok) {
25658
25702
  console.warn(`[memdir] rerank: ollama chat failed: ${resp.status}`);
25659
- return candidates.slice(0, topK);
25703
+ return candidates2.slice(0, topK);
25660
25704
  }
25661
25705
  const data = await resp.json();
25662
25706
  const text = data?.message?.content ?? "";
@@ -25665,19 +25709,19 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
25665
25709
  try {
25666
25710
  const parsed = JSON.parse(jsonMatch[0]);
25667
25711
  if (parsed.selected && Array.isArray(parsed.selected)) {
25668
- const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates.length).map((n) => n - 1);
25669
- const result = indices.map((i) => candidates[i]);
25670
- console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
25712
+ const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates2.length).map((n) => n - 1);
25713
+ const result = indices.map((i) => candidates2[i]);
25714
+ console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates2.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
25671
25715
  return result.slice(0, topK);
25672
25716
  }
25673
25717
  } catch {
25674
25718
  }
25675
25719
  }
25676
25720
  console.warn(`[memdir] rerank: no valid JSON in response (${text.length} chars), falling back to vector order`);
25677
- return candidates.slice(0, topK);
25721
+ return candidates2.slice(0, topK);
25678
25722
  } catch (e) {
25679
25723
  console.warn(`[memdir] rerank: error: ${e?.message ?? e}, falling back to vector order`);
25680
- return candidates.slice(0, topK);
25724
+ return candidates2.slice(0, topK);
25681
25725
  }
25682
25726
  }
25683
25727
  async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
@@ -25726,17 +25770,17 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
25726
25770
  }
25727
25771
  }
25728
25772
  scored.sort((a, b) => b.score - a.score);
25729
- const candidates = scored.slice(0, CANDIDATE_POOL);
25773
+ const candidates2 = scored.slice(0, CANDIDATE_POOL);
25730
25774
  const vectorMs = Date.now() - t0;
25731
25775
  console.log(
25732
- `[memdir] vector recall: ${candidates.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
25776
+ `[memdir] vector recall: ${candidates2.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
25733
25777
  );
25734
- if (candidates.length > 0) {
25735
- console.log(`[memdir] vector top scores: ${candidates.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates.length > 5 ? "..." : ""}`);
25778
+ if (candidates2.length > 0) {
25779
+ console.log(`[memdir] vector top scores: ${candidates2.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates2.length > 5 ? "..." : ""}`);
25736
25780
  }
25737
- if (candidates.length === 0) return [];
25781
+ if (candidates2.length === 0) return [];
25738
25782
  const rerankT0 = Date.now();
25739
- const reranked = await llmRerank(query, candidates, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
25783
+ const reranked = await llmRerank(query, candidates2, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
25740
25784
  const rerankMs = Date.now() - rerankT0;
25741
25785
  const totalMs = Date.now() - t0;
25742
25786
  console.log(`[memdir] recall total: ${totalMs}ms (vector=${vectorMs}ms, rerank=${rerankMs}ms)`);
@@ -30504,6 +30548,8 @@ function startInboxPoller(ctx) {
30504
30548
  const INBOX_POLL_INTERVAL_MS = 2e3;
30505
30549
  const INBOX_IDLE_INTERVAL_MS = 3e4;
30506
30550
  let pollCycleCount = 0;
30551
+ let lastStatusLine = "";
30552
+ let warnedNoPreview = false;
30507
30553
  let inboxPollerTimer = null;
30508
30554
  let inboxPollerStandby = false;
30509
30555
  function runInboxPollCycle() {
@@ -30540,7 +30586,11 @@ function startInboxPoller(ctx) {
30540
30586
  ).join(" ");
30541
30587
  const isMainActive = dispatcher.isActive("main");
30542
30588
  const mainPending = dispatcher.pendingCount("main");
30543
- console.log(`[inbox-poller] #${pollCycleCount} teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`);
30589
+ const statusLine = `teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`;
30590
+ if (statusLine !== lastStatusLine || pollCycleCount % 15 === 0) {
30591
+ lastStatusLine = statusLine;
30592
+ console.log(`[inbox-poller] #${pollCycleCount} ${statusLine}`);
30593
+ }
30544
30594
  const now = Date.now();
30545
30595
  const allTeamNames = new Set(teammates.filter((t) => t.status !== "killed").map((t) => t.identity.teamName));
30546
30596
  const teams = [...allTeamNames];
@@ -30618,7 +30668,6 @@ function startInboxPoller(ctx) {
30618
30668
  ${notifications}
30619
30669
  [/teammate-status]${taskSummary}`;
30620
30670
  const inboxTarget = "";
30621
- const inboxPreview = null;
30622
30671
  const submitted = dispatcher.submitMessage({
30623
30672
  text: queryContent,
30624
30673
  sessionId: "main",
@@ -30628,21 +30677,12 @@ ${notifications}
30628
30677
  priority: "next",
30629
30678
  // 对齐 CC: idle 时立即提交,不是 'later' 排队
30630
30679
  callbacks: {
30631
- onText: (text) => {
30632
- if (inboxPreview) {
30633
- inboxPreview.appendText(text);
30634
- } else {
30635
- console.warn(`[inbox-poller:onText] no preview target, Discord output dropped (${text.length} chars)`);
30636
- }
30680
+ onText: (_text) => {
30681
+ if (warnedNoPreview) return;
30682
+ warnedNoPreview = true;
30683
+ 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");
30637
30684
  },
30638
- onResult: async (content) => {
30639
- if (inboxPreview) {
30640
- const delivered = await inboxPreview.finish(content.trim() || "(\u5904\u7406\u5B8C\u6210)");
30641
- if (!delivered && inboxTarget) {
30642
- channelManager.send("discord", inboxTarget, content.trim() || "(\u5904\u7406\u5B8C\u6210)").catch(() => {
30643
- });
30644
- }
30645
- }
30685
+ onResult: async (_content) => {
30646
30686
  }
30647
30687
  },
30648
30688
  deps
@@ -31336,8 +31376,8 @@ var VoiceChatPlugin = class {
31336
31376
  // src/memory/everos/plugin.ts
31337
31377
  import { spawn as spawn4 } from "node:child_process";
31338
31378
  import net from "node:net";
31339
- import path36 from "node:path";
31340
- import fs36 from "node:fs";
31379
+ import path35 from "node:path";
31380
+ import fs35 from "node:fs";
31341
31381
 
31342
31382
  // src/memory/everos/config.ts
31343
31383
  var DEFAULTS4 = {
@@ -31571,8 +31611,8 @@ var EverosPlugin = class {
31571
31611
  }, 3e5);
31572
31612
  }
31573
31613
  async startEveros() {
31574
- const pythonDir = path36.dirname(this.config.lancedbPath);
31575
- const configPath = path36.join(pythonDir, "config.toml");
31614
+ const pythonDir = path35.dirname(this.config.lancedbPath);
31615
+ const configPath = path35.join(pythonDir, "config.toml");
31576
31616
  await this.ensureFcntlCompat();
31577
31617
  const venvPython = this.findVenvPython();
31578
31618
  const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
@@ -31662,19 +31702,19 @@ var EverosPlugin = class {
31662
31702
  return child;
31663
31703
  }
31664
31704
  findVenvPython() {
31665
- const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31705
+ const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31666
31706
  if (process.platform === "win32") {
31667
- return path36.join(stateDir, "everos-venv", "Scripts", "python.exe");
31707
+ return path35.join(stateDir, "everos-venv", "Scripts", "python.exe");
31668
31708
  }
31669
- return path36.join(stateDir, "everos-venv", "bin", "python");
31709
+ return path35.join(stateDir, "everos-venv", "bin", "python");
31670
31710
  }
31671
31711
  /** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
31672
31712
  async ensureVenv() {
31673
31713
  const venvPython = this.findVenvPython();
31674
- if (fs36.existsSync(venvPython)) return;
31675
- const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31676
- const venvDir = path36.join(stateDir, "everos-venv");
31677
- const everosSrc = path36.join(stateDir, "workspace", "research", "EverOS");
31714
+ if (fs35.existsSync(venvPython)) return;
31715
+ const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
31716
+ const venvDir = path35.join(stateDir, "everos-venv");
31717
+ const everosSrc = path35.join(stateDir, "workspace", "research", "EverOS");
31678
31718
  console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
31679
31719
  console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
31680
31720
  const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
@@ -31696,9 +31736,9 @@ var EverosPlugin = class {
31696
31736
  console.log(`[everos] Creating venv with ${sysPython}...`);
31697
31737
  const { execSync: execSync3 } = await import("node:child_process");
31698
31738
  execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
31699
- const pip = process.platform === "win32" ? path36.join(venvDir, "Scripts", "pip.exe") : path36.join(venvDir, "bin", "pip");
31700
- const everosReq = path36.join(this.getPythonDir(), "requirements.txt");
31701
- if (fs36.existsSync(everosReq)) {
31739
+ const pip = process.platform === "win32" ? path35.join(venvDir, "Scripts", "pip.exe") : path35.join(venvDir, "bin", "pip");
31740
+ const everosReq = path35.join(this.getPythonDir(), "requirements.txt");
31741
+ if (fs35.existsSync(everosReq)) {
31702
31742
  console.log(`[everos] Installing from requirements.txt...`);
31703
31743
  execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
31704
31744
  } else {
@@ -31713,29 +31753,29 @@ var EverosPlugin = class {
31713
31753
  }
31714
31754
  getPythonDir() {
31715
31755
  const dir = import.meta.dirname;
31716
- const candidates = [
31717
- path36.join(dir, "python"),
31718
- path36.resolve(dir, "..", "src", "memory", "everos", "python"),
31719
- path36.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
31756
+ const candidates2 = [
31757
+ path35.join(dir, "python"),
31758
+ path35.resolve(dir, "..", "src", "memory", "everos", "python"),
31759
+ path35.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
31720
31760
  ];
31721
- for (const candidate of candidates) {
31722
- if (fs36.existsSync(path36.join(candidate, "agentic_server.py"))) {
31761
+ for (const candidate of candidates2) {
31762
+ if (fs35.existsSync(path35.join(candidate, "agentic_server.py"))) {
31723
31763
  return candidate;
31724
31764
  }
31725
31765
  }
31726
- return candidates[0];
31766
+ return candidates2[0];
31727
31767
  }
31728
31768
  async ensureFcntlCompat() {
31729
31769
  if (process.platform !== "win32") return;
31730
31770
  const venvPython = this.findVenvPython();
31731
- const venvDir = path36.dirname(path36.dirname(venvPython));
31732
- const sitePackages = path36.join(venvDir, "Lib", "site-packages");
31733
- const target = path36.join(sitePackages, "fcntl.py");
31734
- if (fs36.existsSync(target)) return;
31735
- const source = path36.join(this.getPythonDir(), "fcntl_compat.py");
31736
- if (fs36.existsSync(source)) {
31771
+ const venvDir = path35.dirname(path35.dirname(venvPython));
31772
+ const sitePackages = path35.join(venvDir, "Lib", "site-packages");
31773
+ const target = path35.join(sitePackages, "fcntl.py");
31774
+ if (fs35.existsSync(target)) return;
31775
+ const source = path35.join(this.getPythonDir(), "fcntl_compat.py");
31776
+ if (fs35.existsSync(source)) {
31737
31777
  try {
31738
- fs36.copyFileSync(source, target);
31778
+ fs35.copyFileSync(source, target);
31739
31779
  console.log(`[everos] Installed fcntl compat shim to ${target}`);
31740
31780
  } catch (err) {
31741
31781
  console.warn(`[everos] Failed to install fcntl shim: ${err.message}`);
@@ -32344,8 +32384,8 @@ function createVisualBoot(ctx) {
32344
32384
  }
32345
32385
 
32346
32386
  // src/startup/message-pipeline.ts
32347
- import * as fs37 from "node:fs";
32348
- import * as path37 from "node:path";
32387
+ import * as fs36 from "node:fs";
32388
+ import * as path36 from "node:path";
32349
32389
 
32350
32390
  // src/commands/registry.ts
32351
32391
  var commands = /* @__PURE__ */ new Map();
@@ -33236,7 +33276,7 @@ function setupMessagePipeline(ctx) {
33236
33276
  console.log(`[vision] Downloading image: ${att.filename}`);
33237
33277
  let rawBuffer;
33238
33278
  if (att.url.startsWith("file://")) {
33239
- rawBuffer = fs37.readFileSync(decodeURIComponent(att.url.slice(7)));
33279
+ rawBuffer = fs36.readFileSync(decodeURIComponent(att.url.slice(7)));
33240
33280
  } else {
33241
33281
  rawBuffer = await downloadImage2(att.url, attachDispatcher);
33242
33282
  }
@@ -33244,8 +33284,8 @@ function setupMessagePipeline(ctx) {
33244
33284
  const ext = detected.split("/")[1] || "png";
33245
33285
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
33246
33286
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
33247
- const savedPath = path37.join(config.mediaDir, `${imageId}.${ext}`);
33248
- fs37.writeFileSync(savedPath, resized.buffer);
33287
+ const savedPath = path36.join(config.mediaDir, `${imageId}.${ext}`);
33288
+ fs36.writeFileSync(savedPath, resized.buffer);
33249
33289
  savedPaths.push(savedPath);
33250
33290
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
33251
33291
  imageBlocks.push({
@@ -33275,8 +33315,8 @@ ${pathStr}` }];
33275
33315
  }
33276
33316
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
33277
33317
  if (nonImageAttachments && nonImageAttachments.length > 0) {
33278
- const outDir = path37.join(config.mediaDir, sessionId);
33279
- fs37.mkdirSync(outDir, { recursive: true });
33318
+ const outDir = path36.join(config.mediaDir, sessionId);
33319
+ fs36.mkdirSync(outDir, { recursive: true });
33280
33320
  const resolved = [];
33281
33321
  for (const att of nonImageAttachments) {
33282
33322
  console.log(`[file] Downloading: ${att.filename} (${att.contentType}, ${att.size}B)`);
@@ -33284,9 +33324,9 @@ ${pathStr}` }];
33284
33324
  const resp = attachDispatcher ? await fetch(att.url, { dispatcher: attachDispatcher, signal: AbortSignal.timeout(3e4) }) : await fetch(att.url, { signal: AbortSignal.timeout(3e4) });
33285
33325
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
33286
33326
  const buffer = Buffer.from(await resp.arrayBuffer());
33287
- const safeName2 = path37.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
33288
- const savedPath = path37.join(outDir, safeName2);
33289
- fs37.writeFileSync(savedPath, buffer);
33327
+ const safeName2 = path36.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
33328
+ const savedPath = path36.join(outDir, safeName2);
33329
+ fs36.writeFileSync(savedPath, buffer);
33290
33330
  resolved.push(savedPath);
33291
33331
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
33292
33332
  } catch (err) {
@@ -33535,6 +33575,7 @@ ${pathStr}` }];
33535
33575
  init_live();
33536
33576
 
33537
33577
  // src/provision-free.ts
33578
+ import { randomBytes as randomBytes4, randomUUID as randomUUID6 } from "node:crypto";
33538
33579
  import { chmodSync, existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync34, writeFileSync as writeFileSync12 } from "node:fs";
33539
33580
  import { dirname as dirname11, join as join46 } from "node:path";
33540
33581
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -33543,19 +33584,68 @@ var IDENTITY_FILE = "install-identity.json";
33543
33584
  function readContactEmailOf(parsed) {
33544
33585
  return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
33545
33586
  }
33546
- function readInstallIdentity(stateDir) {
33547
- try {
33548
- const parsed = JSON.parse(readFileSync34(join46(stateDir, IDENTITY_FILE), "utf8"));
33549
- if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
33587
+ function loadOrCreateInstallIdentity(stateDir) {
33588
+ const file2 = join46(stateDir, IDENTITY_FILE);
33589
+ if (existsSync23(file2)) {
33590
+ let parsed;
33591
+ try {
33592
+ parsed = JSON.parse(readFileSync34(file2, "utf8"));
33593
+ } catch (e) {
33594
+ throw new Error(
33595
+ `${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`
33596
+ );
33597
+ }
33598
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
33599
+ throw new Error(`${IDENTITY_FILE} \u7F3A installId / installSecret \u5B57\u6BB5`);
33600
+ }
33550
33601
  const email = readContactEmailOf(parsed);
33551
33602
  return {
33552
33603
  installId: parsed.installId,
33553
33604
  installSecret: parsed.installSecret,
33554
33605
  ...email ? { contactEmail: email } : {}
33555
33606
  };
33607
+ }
33608
+ const identity = {
33609
+ // 服务端校验 installId 长度为 8..36、只允许字母数字和连字符;uuid 正好符合
33610
+ installId: randomUUID6(),
33611
+ // 服务端校验 installSecret 为 32..64 位十六进制
33612
+ installSecret: randomBytes4(32).toString("hex")
33613
+ };
33614
+ mkdirSync15(stateDir, { recursive: true });
33615
+ writeFileSync12(file2, JSON.stringify(identity, null, 2) + "\n", { mode: 384 });
33616
+ try {
33617
+ chmodSync(file2, 384);
33556
33618
  } catch {
33557
- return null;
33558
33619
  }
33620
+ return identity;
33621
+ }
33622
+ function readInstallIdentityDetailed(stateDir) {
33623
+ const file2 = join46(stateDir, IDENTITY_FILE);
33624
+ if (!existsSync23(file2)) return { ok: false, reason: "missing" };
33625
+ let text;
33626
+ try {
33627
+ text = readFileSync34(file2, "utf8");
33628
+ } catch (e) {
33629
+ return { ok: false, reason: "corrupt", detail: `\u5B58\u5728\u4F46\u8BFB\u4E0D\u4E86\uFF08${e?.message ?? e}\uFF09` };
33630
+ }
33631
+ let parsed;
33632
+ try {
33633
+ parsed = JSON.parse(text);
33634
+ } catch (e) {
33635
+ return { ok: false, reason: "corrupt", detail: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e?.message ?? e}\uFF09` };
33636
+ }
33637
+ if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
33638
+ return { ok: false, reason: "corrupt", detail: "\u7F3A installId / installSecret \u5B57\u6BB5" };
33639
+ }
33640
+ const email = readContactEmailOf(parsed);
33641
+ return {
33642
+ ok: true,
33643
+ identity: {
33644
+ installId: parsed.installId,
33645
+ installSecret: parsed.installSecret,
33646
+ ...email ? { contactEmail: email } : {}
33647
+ }
33648
+ };
33559
33649
  }
33560
33650
 
33561
33651
  // src/optional/reporting/state.ts
@@ -33582,10 +33672,10 @@ function writeReportState(stateDir, s) {
33582
33672
  }
33583
33673
 
33584
33674
  // src/optional/reporting/payload.ts
33585
- import { createHash as createHash2 } from "node:crypto";
33675
+ import { createHash as createHash3 } from "node:crypto";
33586
33676
  import * as os3 from "node:os";
33587
33677
  function installKeyOf(installId, installSecret) {
33588
- return createHash2("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33678
+ return createHash3("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33589
33679
  }
33590
33680
  function buildReportPayload(opts) {
33591
33681
  const p = {
@@ -33731,13 +33821,23 @@ function startReporting(opts) {
33731
33821
  const log = opts.log ?? (() => {
33732
33822
  });
33733
33823
  const startedAt = nowFn3();
33734
- const maybeIdentity = readInstallIdentity(stateDir);
33735
- if (!maybeIdentity) {
33736
- log("[reporting] \u6CA1\u6709\u53EF\u7528\u7684\u88C5\u673A\u8EAB\u4EFD\uFF0C\u4E0A\u62A5\u5173\u95ED");
33737
- return { stop() {
33738
- }, async beatNow() {
33739
- } };
33824
+ const identityRead = readInstallIdentityDetailed(stateDir);
33825
+ let maybeIdentity = null;
33826
+ if (identityRead.ok) {
33827
+ maybeIdentity = identityRead.identity;
33828
+ } else if (identityRead.reason === "missing") {
33829
+ try {
33830
+ maybeIdentity = loadOrCreateInstallIdentity(stateDir);
33831
+ 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");
33832
+ } catch (e) {
33833
+ log(`[reporting] \u8865\u5EFA\u88C5\u673A\u8EAB\u4EFD\u5931\u8D25\uFF1A${e?.message ?? e} \u2014\u2014 \u4E0A\u62A5\u5173\u95ED`);
33834
+ }
33835
+ } else {
33836
+ 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`);
33740
33837
  }
33838
+ if (!maybeIdentity) return { stop() {
33839
+ }, async beatNow() {
33840
+ } };
33741
33841
  const identity = maybeIdentity;
33742
33842
  const bootState = readReportState(stateDir);
33743
33843
  restoreCounters(bootState);
@@ -35020,8 +35120,8 @@ var FeishuAdapter = class _FeishuAdapter {
35020
35120
 
35021
35121
  // src/channels/wechat.ts
35022
35122
  import * as crypto5 from "crypto";
35023
- import * as fs38 from "fs";
35024
- import * as path38 from "path";
35123
+ import * as fs37 from "fs";
35124
+ import * as path37 from "path";
35025
35125
  import * as os4 from "os";
35026
35126
  import { promises as dns } from "dns";
35027
35127
  function sleep3(ms) {
@@ -35245,7 +35345,7 @@ function guessChatType(msg2, accountId) {
35245
35345
  return isGroup ? { type: "group", chatId: roomId || toId || String(msg2.from_user_id || "") } : { type: "dm", chatId: String(msg2.from_user_id || "") };
35246
35346
  }
35247
35347
  function mimeFromFilename(filename) {
35248
- const ext = path38.extname(filename).toLowerCase();
35348
+ const ext = path37.extname(filename).toLowerCase();
35249
35349
  const map = {
35250
35350
  ".jpg": "image/jpeg",
35251
35351
  ".jpeg": "image/jpeg",
@@ -35312,15 +35412,15 @@ var ContextTokenStore = class {
35312
35412
  cache = /* @__PURE__ */ new Map();
35313
35413
  filePath;
35314
35414
  constructor(stateDir, accountId) {
35315
- this.filePath = path38.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
35415
+ this.filePath = path37.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
35316
35416
  }
35317
35417
  k(a, p) {
35318
35418
  return `${a}:${p}`;
35319
35419
  }
35320
35420
  restore() {
35321
35421
  try {
35322
- if (fs38.existsSync(this.filePath)) {
35323
- for (const [k, v] of Object.entries(JSON.parse(fs38.readFileSync(this.filePath, "utf-8"))))
35422
+ if (fs37.existsSync(this.filePath)) {
35423
+ for (const [k, v] of Object.entries(JSON.parse(fs37.readFileSync(this.filePath, "utf-8"))))
35324
35424
  this.cache.set(k, String(v));
35325
35425
  }
35326
35426
  } catch {
@@ -35328,11 +35428,11 @@ var ContextTokenStore = class {
35328
35428
  }
35329
35429
  save() {
35330
35430
  try {
35331
- const dir = path38.dirname(this.filePath);
35332
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
35431
+ const dir = path37.dirname(this.filePath);
35432
+ if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
35333
35433
  const obj = {};
35334
35434
  this.cache.forEach((v, k) => obj[k] = v);
35335
- fs38.writeFileSync(this.filePath, JSON.stringify(obj));
35435
+ fs37.writeFileSync(this.filePath, JSON.stringify(obj));
35336
35436
  } catch {
35337
35437
  }
35338
35438
  }
@@ -35352,18 +35452,18 @@ var ContextTokenStore = class {
35352
35452
  };
35353
35453
  function loadSyncBuf(stateDir, accountId) {
35354
35454
  try {
35355
- const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
35356
- if (fs38.existsSync(fp)) return String(JSON.parse(fs38.readFileSync(fp, "utf-8")).sync_buf || "");
35455
+ const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
35456
+ if (fs37.existsSync(fp)) return String(JSON.parse(fs37.readFileSync(fp, "utf-8")).sync_buf || "");
35357
35457
  } catch {
35358
35458
  }
35359
35459
  return "";
35360
35460
  }
35361
35461
  function saveSyncBuf(stateDir, accountId, buf) {
35362
35462
  try {
35363
- const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
35364
- const dir = path38.dirname(fp);
35365
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
35366
- fs38.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
35463
+ const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
35464
+ const dir = path37.dirname(fp);
35465
+ if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
35466
+ fs37.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
35367
35467
  } catch {
35368
35468
  }
35369
35469
  }
@@ -35405,8 +35505,8 @@ var WechatAdapter = class {
35405
35505
  this.config = config;
35406
35506
  this.baseUrl = config.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
35407
35507
  this.cdnBaseUrl = config.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
35408
- this.stateDir = config.stateDir || path38.join(os4.homedir?.() || "/tmp", ".engine7");
35409
- if (!fs38.existsSync(this.stateDir)) fs38.mkdirSync(this.stateDir, { recursive: true });
35508
+ this.stateDir = config.stateDir || path37.join(os4.homedir?.() || "/tmp", ".engine7");
35509
+ if (!fs37.existsSync(this.stateDir)) fs37.mkdirSync(this.stateDir, { recursive: true });
35410
35510
  }
35411
35511
  // --- ChannelAdapter interface ---
35412
35512
  onMessage(handler2) {
@@ -35459,8 +35559,8 @@ var WechatAdapter = class {
35459
35559
  }
35460
35560
  async sendFile(target, message, attachment) {
35461
35561
  const filePath = attachment.path;
35462
- if (!fs38.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
35463
- const plaintext = fs38.readFileSync(filePath);
35562
+ if (!fs37.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
35563
+ const plaintext = fs37.readFileSync(filePath);
35464
35564
  const mediaType = this.guessMediaType(filePath, attachment.mimeType);
35465
35565
  const filekey = crypto5.randomBytes(16).toString("hex");
35466
35566
  const aesKey = crypto5.randomBytes(16);
@@ -35492,7 +35592,7 @@ var WechatAdapter = class {
35492
35592
  aesKeyForApi,
35493
35593
  ciphertextSize: ciphertext.length,
35494
35594
  plaintextSize: rawsize,
35495
- filename: path38.basename(filePath),
35595
+ filename: path37.basename(filePath),
35496
35596
  rawfilemd5,
35497
35597
  voiceDurationSec: attachment.voiceDurationSec
35498
35598
  });
@@ -35751,10 +35851,10 @@ var WechatAdapter = class {
35751
35851
  mediaRef.full_url,
35752
35852
  timeoutSec
35753
35853
  );
35754
- const tmpDir = path38.join(os4.tmpdir(), "weixin-media");
35755
- if (!fs38.existsSync(tmpDir)) fs38.mkdirSync(tmpDir, { recursive: true });
35756
- const tmpPath = path38.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
35757
- fs38.writeFileSync(tmpPath, data);
35854
+ const tmpDir = path37.join(os4.tmpdir(), "weixin-media");
35855
+ if (!fs37.existsSync(tmpDir)) fs37.mkdirSync(tmpDir, { recursive: true });
35856
+ const tmpPath = path37.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
35857
+ fs37.writeFileSync(tmpPath, data);
35758
35858
  return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
35759
35859
  }
35760
35860
  // --- Internal: send helpers ---
@@ -35804,7 +35904,7 @@ var WechatAdapter = class {
35804
35904
  throw lastError;
35805
35905
  }
35806
35906
  guessMediaType(filePath, mimeType) {
35807
- const ext = path38.extname(filePath).toLowerCase();
35907
+ const ext = path37.extname(filePath).toLowerCase();
35808
35908
  const mime = mimeType || mimeFromFilename(filePath);
35809
35909
  if (mime.startsWith("image/")) return MEDIA_IMAGE;
35810
35910
  if (mime.startsWith("video/")) return MEDIA_VIDEO;
@@ -35850,12 +35950,12 @@ init_voice_chat_tts_daemon();
35850
35950
  import { execFile as execFile2 } from "node:child_process";
35851
35951
  import { createInterface as createInterface2 } from "node:readline";
35852
35952
  import { promisify as promisify2 } from "node:util";
35853
- import * as fs40 from "node:fs";
35854
- import * as path40 from "node:path";
35953
+ import * as fs39 from "node:fs";
35954
+ import * as path39 from "node:path";
35855
35955
  import * as os5 from "node:os";
35856
35956
  import net3 from "net";
35857
35957
  var execFileAsync2 = promisify2(execFile2);
35858
- var VOICE_DIR = path40.join(os5.tmpdir(), "engine-voice");
35958
+ var VOICE_DIR = path39.join(os5.tmpdir(), "engine-voice");
35859
35959
  function wavDurationSec(buf) {
35860
35960
  if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
35861
35961
  throw new Error("wavDurationSec: not a RIFF/WAVE file");
@@ -35904,17 +36004,17 @@ function resolvePython(configuredPath) {
35904
36004
  }
35905
36005
  if (_pyCache) return _pyCache;
35906
36006
  const localAppData = process.env.LOCALAPPDATA;
35907
- const candidates = [
36007
+ const candidates2 = [
35908
36008
  "python3",
35909
36009
  "python",
35910
36010
  // LOCALAPPDATA 缺席(非 Windows / 精简环境)时**不加这两个候选**:
35911
36011
  // 拼出来会是相对路径 'Microsoft/WindowsApps/python.exe',那可能撞上 cwd 里的东西。
35912
36012
  ...localAppData ? [
35913
- path40.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
35914
- path40.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
36013
+ path39.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
36014
+ path39.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
35915
36015
  ] : []
35916
36016
  ];
35917
- for (const c of candidates) {
36017
+ for (const c of candidates2) {
35918
36018
  try {
35919
36019
  __require("child_process").execFileSync(c, ["-c", "1"], { timeout: 5e3, stdio: "ignore" });
35920
36020
  _pyCache = c;
@@ -35931,8 +36031,8 @@ async function ttsCosyvoice(text, apiKey, model, voice, workspaceId, instruction
35931
36031
  }
35932
36032
  async function ttsCosyvoiceStream(text, cfg, onChunk) {
35933
36033
  const { apiKey, model, voice, workspaceId, instruction } = cfg;
35934
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
35935
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36034
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36035
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
35936
36036
  const script = `
35937
36037
  import sys, json, base64, threading
35938
36038
  import dashscope
@@ -36017,7 +36117,7 @@ print('EOS')
36017
36117
  header.writeUInt16LE(16, 34);
36018
36118
  header.write("data", 36);
36019
36119
  header.writeUInt32LE(pcm.length, 40);
36020
- fs40.writeFileSync(output, Buffer.concat([header, pcm]));
36120
+ fs39.writeFileSync(output, Buffer.concat([header, pcm]));
36021
36121
  return output;
36022
36122
  }
36023
36123
  async function ttsCosyvoiceWorker(text, cfg, onChunk) {
@@ -36127,9 +36227,9 @@ async function ttsQwen3Opus(text, cfg) {
36127
36227
  const ogg = Buffer.from(await res.arrayBuffer());
36128
36228
  if (ogg.length < 20) throw new Error("qwen3-3060 opus: empty output");
36129
36229
  const durationMs = parseInt(res.headers.get("x-duration-ms") || "0", 10);
36130
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36131
- const oggPath = path40.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
36132
- fs40.writeFileSync(oggPath, ogg);
36230
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36231
+ const oggPath = path39.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
36232
+ fs39.writeFileSync(oggPath, ogg);
36133
36233
  return { oggPath, durationMs };
36134
36234
  }
36135
36235
  async function ttsGptsovits(text, c) {
@@ -36141,8 +36241,8 @@ async function ttsGptsovits(text, c) {
36141
36241
  "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"
36142
36242
  );
36143
36243
  }
36144
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36145
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36244
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36245
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36146
36246
  const params = new URLSearchParams({
36147
36247
  text,
36148
36248
  text_language: "zh",
@@ -36153,13 +36253,13 @@ async function ttsGptsovits(text, c) {
36153
36253
  const res = await fetch(`${url}/?${params}`);
36154
36254
  if (!res.ok) throw new Error(`GPT-SoVITS API ${res.status}`);
36155
36255
  const buf = Buffer.from(await res.arrayBuffer());
36156
- fs40.writeFileSync(output, buf);
36256
+ fs39.writeFileSync(output, buf);
36157
36257
  return output;
36158
36258
  }
36159
36259
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
36160
36260
  async function ttsEdge(text) {
36161
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36162
- const output = path40.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
36261
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36262
+ const output = path39.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
36163
36263
  const script = `
36164
36264
  import asyncio, edge_tts, sys
36165
36265
  async def main():
@@ -36204,8 +36304,8 @@ async function synthQwen3(text, _sink) {
36204
36304
  const pcmChunks = [];
36205
36305
  await ttsQwen3Local(text, { url: c.url, voice: c.voice, lang: c.lang }, (pcm2) => pcmChunks.push(pcm2));
36206
36306
  const pcm = Buffer.concat(pcmChunks);
36207
- fs40.mkdirSync(VOICE_DIR, { recursive: true });
36208
- const wavPath = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36307
+ fs39.mkdirSync(VOICE_DIR, { recursive: true });
36308
+ const wavPath = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
36209
36309
  const header = Buffer.alloc(44);
36210
36310
  header.write("RIFF", 0);
36211
36311
  header.writeUInt32LE(36 + pcm.length, 4);
@@ -36220,7 +36320,7 @@ async function synthQwen3(text, _sink) {
36220
36320
  header.writeUInt16LE(16, 34);
36221
36321
  header.write("data", 36);
36222
36322
  header.writeUInt32LE(pcm.length, 40);
36223
- fs40.writeFileSync(wavPath, Buffer.concat([header, pcm]));
36323
+ fs39.writeFileSync(wavPath, Buffer.concat([header, pcm]));
36224
36324
  return { path: wavPath, format: "wav", unit: unit(text), voice: c.voice };
36225
36325
  }
36226
36326
  }
@@ -36539,8 +36639,365 @@ var DeskBuddyAdapter = class {
36539
36639
  }
36540
36640
  };
36541
36641
 
36642
+ // src/channels/cc-bridge.ts
36643
+ init_live();
36644
+ import fs40 from "node:fs";
36645
+ import path40 from "node:path";
36646
+ import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
36647
+ var DEFAULT_PORT = 16996;
36648
+ var CcBridgeAdapter = class {
36649
+ name = "ccBridge";
36650
+ config;
36651
+ wss = null;
36652
+ clients = /* @__PURE__ */ new Set();
36653
+ messageHandler = null;
36654
+ seq = 0;
36655
+ constructor(config) {
36656
+ this.config = config;
36657
+ }
36658
+ /** 工具流水默认不推(见 CcBridgeConfig.showToolDisplay) */
36659
+ get suppressToolDisplay() {
36660
+ return this.config.showToolDisplay !== true;
36661
+ }
36662
+ /** 实际监听的端口。配 `port: 0` 时由内核分配 —— 测试用它拿真端口,不用赌某个数字没被占 */
36663
+ get boundPort() {
36664
+ const a = this.wss?.address();
36665
+ return a && typeof a === "object" ? a.port : null;
36666
+ }
36667
+ onMessage(handler2) {
36668
+ this.messageHandler = handler2;
36669
+ }
36670
+ // ── outbox(持久队列)──
36671
+ //
36672
+ // 每次现读 stateDir,不缓存 —— stateDir 的 effect 是 restart,缓存下来的
36673
+ // 只会在热加载后变成指向旧目录的幽灵路径(配置访问铁律)。
36674
+ outboxFile() {
36675
+ const sd = liveConfig.get("stateDir") || process.cwd();
36676
+ return path40.join(sd, "state", "cc-bridge", "outbox.jsonl");
36677
+ }
36678
+ /**
36679
+ * 读队列。**只有 ENOENT 才等于"空队列"。**
36680
+ *
36681
+ * ⚠️ 这里原来写的是 `catch { return [] }` —— 那是**会丢数据的**:
36682
+ * 读失败(Windows 上 AV/同步盘/搜索索引的瞬时共享锁 EBUSY,或 EPERM/EACCES)
36683
+ * 返回空数组,紧接着 `enqueue` 拿这份空数组**整份覆写**,待投递的消息全没,
36684
+ * 而且一句警告都没有。而"读不了"和"不存在"是完全不同的两件事 ——
36685
+ * 这个区分正是"宁可重复,不可丢失"能不能成立的支点。
36686
+ */
36687
+ readOutbox() {
36688
+ let raw;
36689
+ try {
36690
+ raw = fs40.readFileSync(this.outboxFile(), "utf8");
36691
+ } catch (e) {
36692
+ if (e?.code === "ENOENT") return [];
36693
+ throw e;
36694
+ }
36695
+ const out = [];
36696
+ for (const line of raw.split("\n")) {
36697
+ if (!line.trim()) continue;
36698
+ try {
36699
+ out.push(JSON.parse(line));
36700
+ } catch {
36701
+ }
36702
+ }
36703
+ return out;
36704
+ }
36705
+ /**
36706
+ * 写队列:**先写 .tmp 再 rename**。
36707
+ *
36708
+ * `writeFileSync(file, ...)` 是原地截断(`w` 模式先清零再写)——
36709
+ * 在这中间被 taskkill / OOM / 断电,文件就是空的或半截,
36710
+ * 下次读回来是 [] 且无从分辨,最多 maxQueue 条待投递消息整体消失。
36711
+ * rename 到已存在文件走的是替换语义,落盘要么全旧要么全新。
36712
+ *
36713
+ * **失败要抛**:调用方(cron/heartbeat 经 `manager.send`)必须知道没存住 ——
36714
+ * 吞成 warning 的话,磁盘满/只读时客户端在线还能收到实时那条,
36715
+ * 客户端离线就是真丢,而这恰恰是这个通道要保的场景。
36716
+ */
36717
+ writeOutbox(items) {
36718
+ const file2 = this.outboxFile();
36719
+ const tmp = `${file2}.tmp`;
36720
+ fs40.mkdirSync(path40.dirname(file2), { recursive: true });
36721
+ fs40.writeFileSync(tmp, items.map((i) => JSON.stringify(i)).join("\n") + (items.length ? "\n" : ""), "utf8");
36722
+ fs40.renameSync(tmp, file2);
36723
+ }
36724
+ /** 入队(dedupe 不做 —— id 是新的,重复投递由 ack 收敛)。读/写失败都往外抛,不吞。 */
36725
+ enqueue(item) {
36726
+ const items = this.readOutbox();
36727
+ items.push(item);
36728
+ const cap = this.config.maxQueue ?? 500;
36729
+ if (items.length > cap) {
36730
+ const dropped = items.length - cap;
36731
+ items.splice(0, dropped);
36732
+ console.warn(`[ccBridge] outbox \u8D85\u8FC7 ${cap} \u6761\uFF0C\u4E22\u4E86\u6700\u8001\u7684 ${dropped} \u6761 \u2014\u2014 CC \u592A\u4E45\u6CA1\u6765\u6536`);
36733
+ }
36734
+ this.writeOutbox(items);
36735
+ }
36736
+ /**
36737
+ * ack:从队列删掉客户端确认过的。
36738
+ *
36739
+ * 读失败时**返回 0 而不是抛** —— 这里宁可"删不掉"也不能"删错":
36740
+ * ack 的语义是收敛,失败留给下一轮再收敛,代价只是重复投递。
36741
+ */
36742
+ ack(ids) {
36743
+ if (!ids.length) return 0;
36744
+ const set = new Set(ids);
36745
+ let items;
36746
+ try {
36747
+ items = this.readOutbox();
36748
+ } catch (e) {
36749
+ 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}`);
36750
+ return 0;
36751
+ }
36752
+ const kept = items.filter((i) => !set.has(i.id));
36753
+ const removed = items.length - kept.length;
36754
+ if (removed) {
36755
+ try {
36756
+ this.writeOutbox(kept);
36757
+ } catch (e) {
36758
+ console.warn(`[ccBridge] ack \u5199\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF1A${e.message}`);
36759
+ return 0;
36760
+ }
36761
+ }
36762
+ return removed;
36763
+ }
36764
+ /** 队列条数(读不到就算 0 —— 只用于日志/握手,不能用来做删除决策) */
36765
+ queuedCount() {
36766
+ try {
36767
+ return this.readOutbox().length;
36768
+ } catch {
36769
+ return 0;
36770
+ }
36771
+ }
36772
+ /** 读队列但**不因读失败而中断**(握手补发、日志这类"读不到也能继续"的场合) */
36773
+ safeReadOutbox(where) {
36774
+ try {
36775
+ return this.readOutbox();
36776
+ } catch (e) {
36777
+ 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}`);
36778
+ return [];
36779
+ }
36780
+ }
36781
+ /**
36782
+ * 升级握手校验。见 `connect()` 里那段注释 —— 绑回环挡不住浏览器。
36783
+ * 返回 false 时 `ws` 会直接拒绝升级(HTTP 401),连接根本建立不起来。
36784
+ */
36785
+ verifyUpgrade(info) {
36786
+ const h = info?.req?.headers ?? {};
36787
+ if (h.origin) {
36788
+ console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u5E26 Origin \u7684\u8FDE\u63A5\uFF1A${h.origin}\uFF08\u7591\u4F3C\u7F51\u9875\u53D1\u8D77\uFF09`);
36789
+ return false;
36790
+ }
36791
+ const host = String(h.host || "").replace(/:\d+$/, "").toLowerCase();
36792
+ const ok = host === "127.0.0.1" || host === "localhost" || host === "[::1]" || host === "::1" || host === "";
36793
+ if (!ok) console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u975E\u56DE\u73AF Host\uFF1A${h.host}\uFF08\u7591\u4F3C DNS rebinding\uFF09`);
36794
+ return ok;
36795
+ }
36796
+ // ── 连接 ──
36797
+ async connect() {
36798
+ const port = this.config.port ?? DEFAULT_PORT;
36799
+ try {
36800
+ fs40.mkdirSync(path40.dirname(this.outboxFile()), { recursive: true });
36801
+ } catch {
36802
+ }
36803
+ this.wss = new WebSocketServer2({
36804
+ port,
36805
+ host: "127.0.0.1",
36806
+ maxPayload: 4 * 1024 * 1024,
36807
+ // ⛔ **绑 127.0.0.1 不是安全边界。** WebSocket 不受同源策略约束,
36808
+ // 用户浏览器里**任意** http 页面都能 `new WebSocket('ws://127.0.0.1:16996')`
36809
+ // 连上来 —— 而这里连上就能以任意 `from` 往 agent 主会话注入消息
36810
+ // (实测过:伪造 Origin/Host 能成功注入一条 `from: 翀哥` 的消息)。
36811
+ // 所以必须自己校验:
36812
+ // · 带 `Origin` 头 = 网页发起的 → 直接拒(`scripts/cc-inbox.mjs` 是 Node
36813
+ // 客户端,本来就不发这个头,不受影响)
36814
+ // · `Host` 必须是本机回环 → 挡 DNS rebinding(域名解析到 127.0.0.1 时
36815
+ // Host 会是那个域名,而不是 127.0.0.1)
36816
+ verifyClient: (info) => this.verifyUpgrade(info)
36817
+ });
36818
+ this.wss.on("connection", (ws) => {
36819
+ this.clients.add(ws);
36820
+ console.log(`[ccBridge] CC connected (${this.clients.size} online)`);
36821
+ const pending2 = this.safeReadOutbox("\u63E1\u624B\u8865\u53D1");
36822
+ this.sendTo(ws, { type: "hello", channel: this.name, queued: pending2.length });
36823
+ if (pending2.length) {
36824
+ console.log(`[ccBridge] \u8865\u53D1 ${pending2.length} \u6761\u79EF\u538B`);
36825
+ this.sendTo(ws, { type: "backlog", messages: pending2 });
36826
+ }
36827
+ ws.on("message", (data) => this.handleFrame(ws, data));
36828
+ ws.on("close", () => {
36829
+ this.clients.delete(ws);
36830
+ console.log(`[ccBridge] CC disconnected (${this.clients.size} online)`);
36831
+ });
36832
+ ws.on("error", () => this.clients.delete(ws));
36833
+ });
36834
+ this.wss.on("error", (e) => {
36835
+ console.error(`[ccBridge] server error on 127.0.0.1:${port}: ${e.message}`);
36836
+ });
36837
+ await new Promise((resolve12, reject) => {
36838
+ const wss = this.wss;
36839
+ const ok = () => {
36840
+ wss.off("error", bad);
36841
+ resolve12();
36842
+ };
36843
+ const bad = (e) => {
36844
+ wss.off("listening", ok);
36845
+ reject(e);
36846
+ };
36847
+ wss.once("listening", ok);
36848
+ wss.once("error", bad);
36849
+ });
36850
+ console.log(`[ccBridge] listening on 127.0.0.1:${this.boundPort} (${this.queuedCount()} queued)`);
36851
+ }
36852
+ handleFrame(ws, data) {
36853
+ let msg2;
36854
+ try {
36855
+ msg2 = JSON.parse(String(data));
36856
+ } catch {
36857
+ this.sendTo(ws, { type: "error", error: "bad json" });
36858
+ return;
36859
+ }
36860
+ if (msg2.type === "ping") {
36861
+ this.sendTo(ws, { type: "pong" });
36862
+ return;
36863
+ }
36864
+ if (msg2.type === "ack") {
36865
+ const ids = Array.isArray(msg2.ids) ? msg2.ids.map(String) : [];
36866
+ const n = this.ack(ids);
36867
+ this.sendTo(ws, { type: "ack-ok", removed: n, queued: this.queuedCount() });
36868
+ return;
36869
+ }
36870
+ if (msg2.type === "chat") {
36871
+ const text = String(msg2.text ?? "").trim();
36872
+ if (!text) {
36873
+ this.sendTo(ws, { type: "error", error: "empty text" });
36874
+ return;
36875
+ }
36876
+ const from = String(msg2.from || this.config.userId || "cc");
36877
+ const inbound = {
36878
+ content: text,
36879
+ from,
36880
+ fromName: String(msg2.fromName || from),
36881
+ channel_id: this.name,
36882
+ channel: this.name,
36883
+ channelType: "dm",
36884
+ isBot: false,
36885
+ messageId: `ccb-${Date.now()}-${++this.seq}`,
36886
+ recvAt: Date.now()
36887
+ };
36888
+ console.log(`[ccBridge] \u2190 ${inbound.fromName}: ${text.slice(0, 80)}`);
36889
+ this.messageHandler?.(inbound);
36890
+ return;
36891
+ }
36892
+ this.sendTo(ws, { type: "error", error: `unknown type: ${msg2.type}` });
36893
+ }
36894
+ sendTo(ws, payload) {
36895
+ if (ws.readyState !== WebSocket2.OPEN) return;
36896
+ try {
36897
+ ws.send(JSON.stringify(payload));
36898
+ } catch {
36899
+ }
36900
+ }
36901
+ /** 广播给出站消息之外的控制帧(握手/pong 走 sendTo 单播) */
36902
+ broadcast(payload) {
36903
+ const s = JSON.stringify(payload);
36904
+ let n = 0;
36905
+ for (const ws of [...this.clients]) {
36906
+ if (ws.readyState !== WebSocket2.OPEN) {
36907
+ this.clients.delete(ws);
36908
+ continue;
36909
+ }
36910
+ try {
36911
+ ws.send(s);
36912
+ n++;
36913
+ } catch {
36914
+ this.clients.delete(ws);
36915
+ }
36916
+ }
36917
+ return n;
36918
+ }
36919
+ async disconnect() {
36920
+ for (const ws of [...this.clients]) {
36921
+ try {
36922
+ ws.close();
36923
+ } catch {
36924
+ }
36925
+ }
36926
+ this.clients.clear();
36927
+ this.wss?.close();
36928
+ this.wss = null;
36929
+ }
36930
+ // ── 出站 ──
36931
+ /**
36932
+ * 发消息给 CC。
36933
+ *
36934
+ * **先落 outbox 再推实时** —— 顺序反过来的话,推送成功但 CC 恰好这时断开,
36935
+ * 这条消息就既不在队列里也没送达,静默消失。宁可重复,不可丢失:
36936
+ * 重复只是多读一遍,丢失是 agent 主动说了话而没人知道。
36937
+ */
36938
+ async send(_target, message, _options) {
36939
+ if (!message.trim()) return;
36940
+ const item = {
36941
+ id: `m${Date.now()}-${++this.seq}`,
36942
+ at: (/* @__PURE__ */ new Date()).toISOString(),
36943
+ text: message,
36944
+ agent: this.config.agentName || "agent"
36945
+ };
36946
+ this.enqueue(item);
36947
+ const n = this.broadcast({ type: "message", message: item });
36948
+ if (n === 0) console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u5DF2\u5165\u961F\uFF08${this.queuedCount()} \u6761\u5F85\u6536\uFF09`);
36949
+ }
36950
+ /**
36951
+ * 发文件。
36952
+ *
36953
+ * **只送路径,不送内容**(`mode: 'path-ref'`)。这个通道两端都在 127.0.0.1 上
36954
+ * (引擎与 CC 同机),把文件 base64 塞进 WS 帧纯属浪费 —— 大文件还会把 outbox 撑爆。
36955
+ *
36956
+ * ⚠️ **但"只送路径"是把解析推迟到接收端,推迟不等于可以不做。**
36957
+ * 它有一个很难看的退化形态:发送端返回「已发送」(路径形式上没问题),
36958
+ * 接收端拿着路径在**它自己的机器上**找不到 —— **失败发生在接收端,
36959
+ * 而发送端日志一切正常**。(张小T 0928 指出的,她是对的。)
36960
+ *
36961
+ * 所以这里补两道:
36962
+ * 1. **入队前 `statSync`** —— 路径打错/文件被挪走,**在本机就报错**,
36963
+ * 而不是等接收端去找一个不存在的路径
36964
+ * 2. 帧里带 `size`/`mtimeMs` + `mode: 'path-ref'` —— 接收端知道这是**引用不是内容**,
36965
+ * 且能对一下"拿到的是不是你说的那份"
36966
+ *
36967
+ * 客户端那侧对应地**读不到就不 ack**(见 `scripts/cc-inbox.mjs`)——
36968
+ * ack 的语义是"我拿到了",拿到一半不能算。
36969
+ */
36970
+ async sendFile(_target, message, attachment) {
36971
+ let st;
36972
+ try {
36973
+ st = fs40.statSync(attachment.path);
36974
+ } catch (e) {
36975
+ throw new Error(`\u9644\u4EF6\u4E0D\u5B58\u5728\uFF0C\u672A\u53D1\u9001\uFF1A${attachment.path}\uFF08${e?.code || e?.message}\uFF09`);
36976
+ }
36977
+ const file2 = {
36978
+ mode: "path-ref",
36979
+ path: attachment.path,
36980
+ filename: attachment.filename || path40.basename(attachment.path),
36981
+ mimeType: attachment.mimeType || "application/octet-stream",
36982
+ size: st.size,
36983
+ mtimeMs: Math.round(st.mtimeMs)
36984
+ };
36985
+ const item = {
36986
+ id: `f${Date.now()}-${++this.seq}`,
36987
+ at: (/* @__PURE__ */ new Date()).toISOString(),
36988
+ text: message || "",
36989
+ agent: this.config.agentName || "agent",
36990
+ file: file2
36991
+ };
36992
+ this.enqueue(item);
36993
+ if (this.broadcast({ type: "message", message: item }) === 0) {
36994
+ console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u9644\u4EF6\u5DF2\u5165\u961F\uFF1A${file2.filename}`);
36995
+ }
36996
+ }
36997
+ };
36998
+
36542
36999
  // src/channels/manager.ts
36543
- var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy"];
37000
+ var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"];
36544
37001
  var defaultFactories = {
36545
37002
  discord: (block) => {
36546
37003
  const accounts = block.accounts || {};
@@ -36602,6 +37059,9 @@ var defaultFactories = {
36602
37059
  },
36603
37060
  deskBuddy: (block) => {
36604
37061
  return new DeskBuddyAdapter(block);
37062
+ },
37063
+ ccBridge: (block) => {
37064
+ return new CcBridgeAdapter(block);
36605
37065
  }
36606
37066
  };
36607
37067
  var ChannelManager = class {
@@ -38553,7 +39013,7 @@ Examples:
38553
39013
  properties: {
38554
39014
  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" },
38555
39015
  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" },
38556
- 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"] },
39016
+ 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"] },
38557
39017
  content: { type: "string", description: "\u6D88\u606F\u6B63\u6587" }
38558
39018
  },
38559
39019
  required: ["content", "to"]
@@ -42498,22 +42958,22 @@ ${lines.join("\n")}`);
42498
42958
  await ctx.reply(wasOverride ? `Reset to default: **${defaultRef}** (auto-routing restored, fallback cooldowns cleared)` : `Already using default: **${defaultRef}**`);
42499
42959
  return;
42500
42960
  }
42501
- const candidates = matchModels(input, aliases, available);
42502
- if (candidates.length === 0) {
42961
+ const candidates2 = matchModels(input, aliases, available);
42962
+ if (candidates2.length === 0) {
42503
42963
  await ctx.reply(`No model matching "${input}". Use \`/model\` to see list.`);
42504
42964
  return;
42505
42965
  }
42506
- if (candidates.length > 1) {
42507
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42508
- const more = candidates.length > 10 ? `
42509
- ... and ${candidates.length - 10} more` : "";
42966
+ if (candidates2.length > 1) {
42967
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42968
+ const more = candidates2.length > 10 ? `
42969
+ ... and ${candidates2.length - 10} more` : "";
42510
42970
  await ctx.reply(`Multiple matches for "${input}":
42511
42971
  ${lines.join("\n")}${more}
42512
42972
 
42513
- Use full ref like \`/model ${candidates[0].ref}\``);
42973
+ Use full ref like \`/model ${candidates2[0].ref}\``);
42514
42974
  return;
42515
42975
  }
42516
- const target = candidates[0].ref;
42976
+ const target = candidates2[0].ref;
42517
42977
  let targetDeps = deps.modelDepsCache.get(target);
42518
42978
  if (!targetDeps) {
42519
42979
  targetDeps = deps.createModelDeps(target);
@@ -42551,8 +43011,8 @@ Use full ref like \`/model ${candidates[0].ref}\``);
42551
43011
  const writer = deps.sessions.getWriter(sid);
42552
43012
  writer.writeModelChange(pid, mid);
42553
43013
  }
42554
- console.log(`[model] Switched to ${target} (${candidates[0].name})`);
42555
- await ctx.reply(`Switched to **${target}** (${candidates[0].name})
43014
+ console.log(`[model] Switched to ${target} (${candidates2[0].name})`);
43015
+ await ctx.reply(`Switched to **${target}** (${candidates2[0].name})
42556
43016
  Auto-routing disabled \u2014 all messages use this model.
42557
43017
  \`/model auto\` to restore default.`);
42558
43018
  }
@@ -42575,20 +43035,20 @@ Usage: \`/primary provider/model\` to persist (restart required)`);
42575
43035
  return;
42576
43036
  }
42577
43037
  const { aliases, available } = collectModels(deps.config);
42578
- const candidates = matchModels(input, aliases, available);
42579
- if (candidates.length === 0) {
43038
+ const candidates2 = matchModels(input, aliases, available);
43039
+ if (candidates2.length === 0) {
42580
43040
  await ctx.reply(`No model matching "${input}".`);
42581
43041
  return;
42582
43042
  }
42583
- if (candidates.length > 1) {
42584
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43043
+ if (candidates2.length > 1) {
43044
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42585
43045
  await ctx.reply(`Multiple matches:
42586
43046
  ${lines.join("\n")}
42587
43047
 
42588
- Use full ref like \`/primary ${candidates[0].ref}\``);
43048
+ Use full ref like \`/primary ${candidates2[0].ref}\``);
42589
43049
  return;
42590
43050
  }
42591
- const target = candidates[0].ref;
43051
+ const target = candidates2[0].ref;
42592
43052
  if (!configPath || !writePath) {
42593
43053
  await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
42594
43054
  return;
@@ -42603,7 +43063,7 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
42603
43063
  cfg.agents.defaults.model.primary = target;
42604
43064
  await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42605
43065
  console.log(`[primary] Persisted primary=${target} to ${writePath}`);
42606
- await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates[0].name})
43066
+ await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates2[0].name})
42607
43067
  Written to config, hot-reloading provider chain now.`);
42608
43068
  } catch (err) {
42609
43069
  await ctx.reply(`\u26A0\uFE0F Failed to write config: ${err.message}`);
@@ -42629,20 +43089,20 @@ Usage: \`/vision-primary provider/model\` to persist (hot-reload)
42629
43089
  return;
42630
43090
  }
42631
43091
  const { aliases, available } = collectModels(deps.config);
42632
- const candidates = matchModels(input, aliases, available);
42633
- if (candidates.length === 0) {
43092
+ const candidates2 = matchModels(input, aliases, available);
43093
+ if (candidates2.length === 0) {
42634
43094
  await ctx.reply(`No model matching "${input}".`);
42635
43095
  return;
42636
43096
  }
42637
- if (candidates.length > 1) {
42638
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43097
+ if (candidates2.length > 1) {
43098
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42639
43099
  await ctx.reply(`Multiple matches:
42640
43100
  ${lines.join("\n")}
42641
43101
 
42642
- Use full ref like \`/vision-primary ${candidates[0].ref}\``);
43102
+ Use full ref like \`/vision-primary ${candidates2[0].ref}\``);
42643
43103
  return;
42644
43104
  }
42645
- const target = candidates[0].ref;
43105
+ const target = candidates2[0].ref;
42646
43106
  if (!configPath || !writePath) {
42647
43107
  await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
42648
43108
  return;
@@ -42663,7 +43123,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
42663
43123
  cfg.agents.defaults.model.vision = target;
42664
43124
  await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42665
43125
  console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
42666
- await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates[0].name})
43126
+ await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates2[0].name})
42667
43127
  Written to config. Hot-reload will apply.`);
42668
43128
  }
42669
43129
  } catch (err) {
@@ -42695,20 +43155,20 @@ Available models: use \`/model\` to see full list
42695
43155
  return;
42696
43156
  }
42697
43157
  const { aliases, available } = collectModels(deps.config);
42698
- const candidates = matchModels(input, aliases, available);
42699
- if (candidates.length === 0) {
43158
+ const candidates2 = matchModels(input, aliases, available);
43159
+ if (candidates2.length === 0) {
42700
43160
  await ctx.reply(`No model matching "${input}".`);
42701
43161
  return;
42702
43162
  }
42703
- if (candidates.length > 1) {
42704
- const lines = candidates.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
43163
+ if (candidates2.length > 1) {
43164
+ const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
42705
43165
  await ctx.reply(`Multiple matches:
42706
43166
  ${lines.join("\n")}
42707
43167
 
42708
- Use full ref like \`/vision-model ${candidates[0].ref}\``);
43168
+ Use full ref like \`/vision-model ${candidates2[0].ref}\``);
42709
43169
  return;
42710
43170
  }
42711
- const target = candidates[0].ref;
43171
+ const target = candidates2[0].ref;
42712
43172
  let targetDeps = deps.modelDepsCache.get(`vision:${target}`);
42713
43173
  if (!targetDeps) {
42714
43174
  targetDeps = deps.createModelDeps(target);
@@ -42721,7 +43181,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
42721
43181
  deps.setVisionOverride(target);
42722
43182
  deps.setVisionDeps(targetDeps);
42723
43183
  console.log(`[vision-model] Override: ${target}`);
42724
- await ctx.reply(`Vision switched to **${target}** (${candidates[0].name}) (immediate)
43184
+ await ctx.reply(`Vision switched to **${target}** (${candidates2[0].name}) (immediate)
42725
43185
  \`/vision-model auto\` to restore default.`);
42726
43186
  }
42727
43187
  });
@@ -43540,7 +44000,7 @@ async function startEngine(config, opts) {
43540
44000
  };
43541
44001
  const startReportingNow = () => {
43542
44002
  stopReporting();
43543
- reportingHandle = startReporting({ stateDir: config.stateDir });
44003
+ reportingHandle = startReporting({ stateDir: config.stateDir, log: (m) => console.log(m) });
43544
44004
  };
43545
44005
  const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
43546
44006
  reloadOrchestrator.register({