engine7 7.1.112 → 7.1.113

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
@@ -141,14 +141,14 @@ var init_live = __esm({
141
141
  }
142
142
  /** 把改动写回 config 文件对应段(找不到文件路径则只内存生效) */
143
143
  async persistToFile(dotPath, val) {
144
- const fs58 = await import("node:fs");
144
+ const fs59 = await import("node:fs");
145
145
  const cfgPath = this.current?._configFilePath;
146
- if (!cfgPath || !fs58.existsSync(cfgPath)) {
146
+ if (!cfgPath || !fs59.existsSync(cfgPath)) {
147
147
  console.warn(`[liveConfig] set: no config file path, in-memory only (${dotPath})`);
148
148
  return;
149
149
  }
150
150
  try {
151
- const raw = JSON.parse(fs58.readFileSync(cfgPath, "utf-8"));
151
+ const raw = JSON.parse(fs59.readFileSync(cfgPath, "utf-8"));
152
152
  const keys = dotPath.split(".");
153
153
  let o = raw;
154
154
  for (let i = 0; i < keys.length - 1; i++) {
@@ -156,7 +156,7 @@ var init_live = __esm({
156
156
  o = o[keys[i]];
157
157
  }
158
158
  o[keys[keys.length - 1]] = val;
159
- fs58.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
159
+ fs59.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
160
160
  console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
161
161
  markSelfWrite();
162
162
  } catch (e) {
@@ -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;
@@ -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,26 +4585,26 @@ __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) {
4602
- const fs58 = await import("node:fs");
4605
+ const fs59 = await import("node:fs");
4603
4606
  try {
4604
- await fs58.promises.mkdir(memoryDir, { recursive: true });
4607
+ await fs59.promises.mkdir(memoryDir, { recursive: true });
4605
4608
  } catch (e) {
4606
4609
  const code = e?.code;
4607
4610
  if (code !== "EEXIST") {
@@ -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;
@@ -8491,7 +8496,7 @@ async function acquireLock(inboxPath) {
8491
8496
  for (let i = 0; i < LOCK_RETRIES; i++) {
8492
8497
  if (await isLockStale(lockPath2)) {
8493
8498
  try {
8494
- await import("node:fs/promises").then((fs58) => fs58.rm(lockPath2, { force: true }));
8499
+ await import("node:fs/promises").then((fs59) => fs59.rm(lockPath2, { force: true }));
8495
8500
  } catch {
8496
8501
  }
8497
8502
  }
@@ -8499,7 +8504,7 @@ async function acquireLock(inboxPath) {
8499
8504
  await writeFile2(lockPath2, `${process.pid}-${Date.now()}`, { encoding: "utf-8", flag: "wx" });
8500
8505
  return async () => {
8501
8506
  try {
8502
- await import("node:fs/promises").then((fs58) => fs58.rm(lockPath2, { force: true }));
8507
+ await import("node:fs/promises").then((fs59) => fs59.rm(lockPath2, { force: true }));
8503
8508
  } catch {
8504
8509
  }
8505
8510
  };
@@ -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");
@@ -16857,11 +16862,11 @@ async function readLastConsolidatedAt(memoryDir) {
16857
16862
  }
16858
16863
  }
16859
16864
  async function tryAcquireConsolidationLock(memoryDir) {
16860
- const path60 = lockPath(memoryDir);
16865
+ const path61 = lockPath(memoryDir);
16861
16866
  let mtimeMs;
16862
16867
  let holderPid;
16863
16868
  try {
16864
- const [s, raw] = await Promise.all([stat3(path60), readFile5(path60, "utf8")]);
16869
+ const [s, raw] = await Promise.all([stat3(path61), readFile5(path61, "utf8")]);
16865
16870
  mtimeMs = s.mtimeMs;
16866
16871
  const parsed = parseInt(raw.trim(), 10);
16867
16872
  holderPid = Number.isFinite(parsed) ? parsed : void 0;
@@ -16874,10 +16879,10 @@ async function tryAcquireConsolidationLock(memoryDir) {
16874
16879
  }
16875
16880
  }
16876
16881
  await mkdir3(memoryDir, { recursive: true });
16877
- await writeFile4(path60, String(process.pid));
16882
+ await writeFile4(path61, String(process.pid));
16878
16883
  let verify2;
16879
16884
  try {
16880
- verify2 = await readFile5(path60, "utf8");
16885
+ verify2 = await readFile5(path61, "utf8");
16881
16886
  } catch {
16882
16887
  return null;
16883
16888
  }
@@ -16885,15 +16890,15 @@ async function tryAcquireConsolidationLock(memoryDir) {
16885
16890
  return mtimeMs ?? 0;
16886
16891
  }
16887
16892
  async function rollbackConsolidationLock(memoryDir, priorMtime) {
16888
- const path60 = lockPath(memoryDir);
16893
+ const path61 = lockPath(memoryDir);
16889
16894
  try {
16890
16895
  if (priorMtime === 0) {
16891
- await unlink(path60);
16896
+ await unlink(path61);
16892
16897
  return;
16893
16898
  }
16894
- await writeFile4(path60, "");
16899
+ await writeFile4(path61, "");
16895
16900
  const t = priorMtime / 1e3;
16896
- await utimes(path60, t, t);
16901
+ await utimes(path61, t, t);
16897
16902
  } catch (e) {
16898
16903
  console.log(`[autoDream] rollback failed: ${e.message} \u2014 next trigger delayed to minHours`);
16899
16904
  }
@@ -17495,12 +17500,12 @@ async function executeAndDeliver(task, now, deps) {
17495
17500
  if (promptText.startsWith("@")) {
17496
17501
  let filePath = promptText.slice(1).trim();
17497
17502
  try {
17498
- const fs58 = await import("fs");
17499
- const path60 = await import("path");
17500
- if (!path60.isAbsolute(filePath)) {
17501
- filePath = path60.join(deps.sessions["config"].stateDir, filePath);
17503
+ const fs59 = await import("fs");
17504
+ const path61 = await import("path");
17505
+ if (!path61.isAbsolute(filePath)) {
17506
+ filePath = path61.join(deps.sessions["config"].stateDir, filePath);
17502
17507
  }
17503
- promptText = fs58.readFileSync(filePath, "utf-8");
17508
+ promptText = fs59.readFileSync(filePath, "utf-8");
17504
17509
  console.log(`[cron] Loaded prompt from ${filePath} (${promptText.length} chars)`);
17505
17510
  } catch (err) {
17506
17511
  throw new Error(`Prompt file not found: ${filePath}: ${err.message}`);
@@ -17538,16 +17543,16 @@ async function executeAndDeliver(task, now, deps) {
17538
17543
  let finalResult = result;
17539
17544
  if (task.postProcess) {
17540
17545
  try {
17541
- const path60 = await import("path");
17542
- const fs58 = await import("fs");
17546
+ const path61 = await import("path");
17547
+ const fs59 = await import("fs");
17543
17548
  let scriptPath = task.postProcess;
17544
- if (!path60.isAbsolute(scriptPath)) {
17545
- scriptPath = path60.join(deps.sessions["config"].stateDir, scriptPath);
17549
+ if (!path61.isAbsolute(scriptPath)) {
17550
+ scriptPath = path61.join(deps.sessions["config"].stateDir, scriptPath);
17546
17551
  }
17547
- const resultsDirTmp = path60.join(getStorageDir(), "results");
17548
- fs58.mkdirSync(resultsDirTmp, { recursive: true });
17549
- const inputFile = path60.join(resultsDirTmp, `${task.id}.input.txt`);
17550
- fs58.writeFileSync(inputFile, result, "utf-8");
17552
+ const resultsDirTmp = path61.join(getStorageDir(), "results");
17553
+ fs59.mkdirSync(resultsDirTmp, { recursive: true });
17554
+ const inputFile = path61.join(resultsDirTmp, `${task.id}.input.txt`);
17555
+ fs59.writeFileSync(inputFile, result, "utf-8");
17551
17556
  const { execFile: execFile5 } = await import("child_process");
17552
17557
  await new Promise((resolve12) => {
17553
17558
  execFile5("python", [scriptPath, "main", "--file", inputFile], {
@@ -17574,12 +17579,12 @@ async function executeAndDeliver(task, now, deps) {
17574
17579
  }
17575
17580
  }
17576
17581
  try {
17577
- const fs58 = await import("fs");
17578
- const path60 = await import("path");
17579
- const resultsDir = path60.join(getStorageDir(), "results");
17580
- fs58.mkdirSync(resultsDir, { recursive: true });
17581
- const resultFile = path60.join(resultsDir, `${task.id}.json`);
17582
- fs58.writeFileSync(resultFile, JSON.stringify({
17582
+ const fs59 = await import("fs");
17583
+ const path61 = await import("path");
17584
+ const resultsDir = path61.join(getStorageDir(), "results");
17585
+ fs59.mkdirSync(resultsDir, { recursive: true });
17586
+ const resultFile = path61.join(resultsDir, `${task.id}.json`);
17587
+ fs59.writeFileSync(resultFile, JSON.stringify({
17583
17588
  taskId: task.id,
17584
17589
  description: task.description,
17585
17590
  executedAt: now.toISOString(),
@@ -17909,30 +17914,30 @@ function registerCronTools() {
17909
17914
  }
17910
17915
  },
17911
17916
  handler: async (args2) => {
17912
- const fs58 = await import("fs");
17913
- const path60 = await import("path");
17914
- const resultsDir = path60.join(getStorageDir(), "results");
17915
- if (!fs58.existsSync(resultsDir)) {
17917
+ const fs59 = await import("fs");
17918
+ const path61 = await import("path");
17919
+ const resultsDir = path61.join(getStorageDir(), "results");
17920
+ if (!fs59.existsSync(resultsDir)) {
17916
17921
  return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
17917
17922
  }
17918
17923
  if (args2.task_id) {
17919
- const file2 = path60.join(resultsDir, `${args2.task_id}.json`);
17920
- if (!fs58.existsSync(file2)) {
17924
+ const file2 = path61.join(resultsDir, `${args2.task_id}.json`);
17925
+ if (!fs59.existsSync(file2)) {
17921
17926
  return { content: `\u4EFB\u52A1 ${args2.task_id} \u6682\u65E0\u6267\u884C\u7ED3\u679C`, isError: true };
17922
17927
  }
17923
- const data = JSON.parse(fs58.readFileSync(file2, "utf-8"));
17928
+ const data = JSON.parse(fs59.readFileSync(file2, "utf-8"));
17924
17929
  return { content: `## ${data.description}
17925
17930
  \u6267\u884C\u65F6\u95F4: ${data.executedAt}
17926
17931
  \u7B2C${data.runCount}\u6B21\u6267\u884C
17927
17932
 
17928
17933
  ${data.result}` };
17929
17934
  }
17930
- const files = fs58.readdirSync(resultsDir).filter((f) => f.endsWith(".json"));
17935
+ const files = fs59.readdirSync(resultsDir).filter((f) => f.endsWith(".json"));
17931
17936
  if (files.length === 0) {
17932
17937
  return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
17933
17938
  }
17934
17939
  const results = files.map((f) => {
17935
- const data = JSON.parse(fs58.readFileSync(path60.join(resultsDir, f), "utf-8"));
17940
+ const data = JSON.parse(fs59.readFileSync(path61.join(resultsDir, f), "utf-8"));
17936
17941
  return `### ${data.description} (${data.taskId.slice(0, 8)})
17937
17942
  \u6267\u884C: ${data.executedAt} | \u7B2C${data.runCount}\u6B21
17938
17943
  ${data.result.slice(0, 500)}${data.result.length > 500 ? "..." : ""}`;
@@ -18607,10 +18612,10 @@ function ensureLoaded(workspace, configIds) {
18607
18612
  if (!state.blockedUserIds.includes(id)) state.blockedUserIds.push(id);
18608
18613
  }
18609
18614
  }
18610
- const path60 = join44(workspace, ".reply-blocklist.json");
18615
+ const path61 = join44(workspace, ".reply-blocklist.json");
18611
18616
  try {
18612
- if (existsSync23(path60)) {
18613
- const raw = readFileSync33(path60, "utf-8");
18617
+ if (existsSync23(path61)) {
18618
+ const raw = readFileSync33(path61, "utf-8");
18614
18619
  const parsed = JSON.parse(raw);
18615
18620
  if (parsed.blockedUserIds) {
18616
18621
  for (const id of parsed.blockedUserIds) {
@@ -18626,9 +18631,9 @@ function ensureLoaded(workspace, configIds) {
18626
18631
  loaded = true;
18627
18632
  }
18628
18633
  function save(workspace) {
18629
- const path60 = join44(workspace, ".reply-blocklist.json");
18634
+ const path61 = join44(workspace, ".reply-blocklist.json");
18630
18635
  try {
18631
- writeFileSync11(path60, JSON.stringify(state, null, 2), "utf-8");
18636
+ writeFileSync11(path61, JSON.stringify(state, null, 2), "utf-8");
18632
18637
  } catch (err) {
18633
18638
  console.warn(`[reply-blocklist] Failed to save: ${err.message}`);
18634
18639
  }
@@ -19198,15 +19203,15 @@ var read_exports = {};
19198
19203
  __export(read_exports, {
19199
19204
  readFileState: () => readFileState
19200
19205
  });
19201
- import * as fs51 from "node:fs";
19202
- import * as path50 from "node:path";
19206
+ import * as fs52 from "node:fs";
19207
+ import * as path51 from "node:path";
19203
19208
  function isBlockedDevicePath(filePath) {
19204
19209
  if (BLOCKED_DEVICE_PATHS.has(filePath)) return true;
19205
19210
  if (filePath.startsWith("/proc/") && (filePath.endsWith("/fd/0") || filePath.endsWith("/fd/1") || filePath.endsWith("/fd/2"))) return true;
19206
19211
  return false;
19207
19212
  }
19208
19213
  function checkReadLoop(filePath, offset, limit) {
19209
- const stat4 = fs51.statSync(filePath);
19214
+ const stat4 = fs52.statSync(filePath);
19210
19215
  const mtimeMs = stat4.mtimeMs;
19211
19216
  const prev = readHistory.get(filePath);
19212
19217
  if (prev && prev.offset === offset && prev.limit === limit && prev.mtimeMs === mtimeMs) {
@@ -19220,19 +19225,19 @@ function checkReadLoop(filePath, offset, limit) {
19220
19225
  return null;
19221
19226
  }
19222
19227
  function readFileContent(filePath) {
19223
- const fd = fs51.openSync(filePath, "r");
19228
+ const fd = fs52.openSync(filePath, "r");
19224
19229
  const bom = Buffer.alloc(2);
19225
- fs51.readSync(fd, bom, 0, 2, 0);
19226
- fs51.closeSync(fd);
19230
+ fs52.readSync(fd, bom, 0, 2, 0);
19231
+ fs52.closeSync(fd);
19227
19232
  let encoding = "utf8";
19228
19233
  if (bom[0] === 255 && bom[1] === 254) {
19229
19234
  encoding = "utf16le";
19230
19235
  }
19231
- const stat4 = fs51.statSync(filePath);
19236
+ const stat4 = fs52.statSync(filePath);
19232
19237
  if (stat4.size > MAX_FILE_SIZE) {
19233
19238
  throw new Error(`\u6587\u4EF6\u592A\u5927 (${(stat4.size / 1024).toFixed(1)}KB)\uFF0C\u8D85\u8FC7 ${MAX_FILE_SIZE / 1024}KB \u9650\u5236\u3002\u8BF7\u4F7F\u7528 offset + limit \u5206\u6BB5\u8BFB\u53D6\u3002`);
19234
19239
  }
19235
- const raw = fs51.readFileSync(filePath, encoding);
19240
+ const raw = fs52.readFileSync(filePath, encoding);
19236
19241
  const content = raw.toString().replaceAll("\r\n", "\n");
19237
19242
  return { content, encoding };
19238
19243
  }
@@ -19374,11 +19379,11 @@ Usage:
19374
19379
  } catch (e) {
19375
19380
  return { content: e.message, isError: true };
19376
19381
  }
19377
- if (!fs51.existsSync(filePath)) {
19382
+ if (!fs52.existsSync(filePath)) {
19378
19383
  return { content: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, isError: true };
19379
19384
  }
19380
- const stat4 = fs51.statSync(filePath);
19381
- const baseName = path50.basename(filePath).toUpperCase();
19385
+ const stat4 = fs52.statSync(filePath);
19386
+ const baseName = path51.basename(filePath).toUpperCase();
19382
19387
  if (BLOCKED_BASENAMES.has(baseName)) {
19383
19388
  return { content: `\u8BBE\u5907\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6: ${filePath}`, isError: true };
19384
19389
  }
@@ -19386,11 +19391,11 @@ Usage:
19386
19391
  return { content: `\u8BBE\u5907\u6587\u4EF6\u4F1A\u963B\u585E\u6216\u4EA7\u751F\u65E0\u9650\u8F93\u51FA: ${filePath}`, isError: true };
19387
19392
  }
19388
19393
  if (stat4.isDirectory()) {
19389
- const entries2 = fs51.readdirSync(filePath);
19394
+ const entries2 = fs52.readdirSync(filePath);
19390
19395
  const items = entries2.map((e) => {
19391
- const full = path50.join(filePath, e);
19396
+ const full = path51.join(filePath, e);
19392
19397
  try {
19393
- const s = fs51.statSync(full);
19398
+ const s = fs52.statSync(full);
19394
19399
  return s.isDirectory() ? `${e}/` : e;
19395
19400
  } catch {
19396
19401
  return e;
@@ -19399,7 +19404,7 @@ Usage:
19399
19404
  return { content: `\u76EE\u5F55 (${entries2.length} \u9879):
19400
19405
  ${items.join("\n")}` };
19401
19406
  }
19402
- const ext = path50.extname(filePath).toLowerCase();
19407
+ const ext = path51.extname(filePath).toLowerCase();
19403
19408
  if (BINARY_EXTENSIONS.has(ext)) {
19404
19409
  return { content: `\u4E8C\u8FDB\u5236\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6 (${ext}): ${filePath}`, isError: true };
19405
19410
  }
@@ -19447,22 +19452,22 @@ ${result}` : result };
19447
19452
 
19448
19453
  // src/tools/write.ts
19449
19454
  var write_exports = {};
19450
- import * as fs52 from "node:fs";
19451
- import * as path51 from "node:path";
19455
+ import * as fs53 from "node:fs";
19456
+ import * as path52 from "node:path";
19452
19457
  function isBlockedPath(filePath) {
19453
19458
  return BLOCKED_PATTERNS.some((p) => p.test(filePath));
19454
19459
  }
19455
19460
  function atomicWrite(filePath, content) {
19456
19461
  const tmpPath = filePath + ".tmp." + Date.now() + ".write";
19457
- fs52.writeFileSync(tmpPath, content, "utf-8");
19462
+ fs53.writeFileSync(tmpPath, content, "utf-8");
19458
19463
  try {
19459
- fs52.renameSync(tmpPath, filePath);
19464
+ fs53.renameSync(tmpPath, filePath);
19460
19465
  } catch (e) {
19461
19466
  try {
19462
- fs52.unlinkSync(tmpPath);
19467
+ fs53.unlinkSync(tmpPath);
19463
19468
  } catch {
19464
19469
  }
19465
- fs52.writeFileSync(filePath, content, "utf-8");
19470
+ fs53.writeFileSync(filePath, content, "utf-8");
19466
19471
  }
19467
19472
  }
19468
19473
  function simpleDiff(oldContent, newContent) {
@@ -19556,15 +19561,15 @@ Usage:
19556
19561
  }
19557
19562
  const rawContent = args2.content;
19558
19563
  const content = rawContent.replaceAll("\r\n", "\n");
19559
- if (fs52.existsSync(filePath) && fs52.statSync(filePath).isDirectory()) {
19564
+ if (fs53.existsSync(filePath) && fs53.statSync(filePath).isDirectory()) {
19560
19565
  return { content: `\u8DEF\u5F84\u662F\u76EE\u5F55\u4E0D\u662F\u6587\u4EF6: ${filePath}`, isError: true };
19561
19566
  }
19562
19567
  let oldContent = null;
19563
19568
  let isCreate = true;
19564
- if (fs52.existsSync(filePath)) {
19569
+ if (fs53.existsSync(filePath)) {
19565
19570
  isCreate = false;
19566
19571
  try {
19567
- oldContent = fs52.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
19572
+ oldContent = fs53.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
19568
19573
  } catch {
19569
19574
  isCreate = true;
19570
19575
  }
@@ -19580,10 +19585,10 @@ Usage:
19580
19585
  isError: true
19581
19586
  };
19582
19587
  }
19583
- const currentStat = fs52.statSync(filePath);
19588
+ const currentStat = fs53.statSync(filePath);
19584
19589
  const lastWriteTime = Math.floor(currentStat.mtimeMs);
19585
19590
  if (lastWriteTime > readState.timestamp) {
19586
- const currentContent = fs52.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
19591
+ const currentContent = fs53.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
19587
19592
  if (currentContent !== oldContent) {
19588
19593
  return {
19589
19594
  content: `\u6587\u4EF6\u5728\u8BFB\u53D6\u540E\u88AB\u4FEE\u6539\u3002\u8BF7\u5148\u91CD\u65B0\u8BFB\u53D6\u6587\u4EF6\u518D\u5199\u5165: ${filePath}`,
@@ -19592,9 +19597,9 @@ Usage:
19592
19597
  }
19593
19598
  }
19594
19599
  }
19595
- const dir = path51.dirname(filePath);
19600
+ const dir = path52.dirname(filePath);
19596
19601
  try {
19597
- fs52.mkdirSync(dir, { recursive: true });
19602
+ fs53.mkdirSync(dir, { recursive: true });
19598
19603
  } catch (e) {
19599
19604
  return { content: `\u65E0\u6CD5\u521B\u5EFA\u76EE\u5F55: ${dir} \u2014 ${e.message}`, isError: true };
19600
19605
  }
@@ -19603,11 +19608,11 @@ Usage:
19603
19608
  } catch (e) {
19604
19609
  return { content: `\u5199\u5165\u5931\u8D25: ${e.message}`, isError: true };
19605
19610
  }
19606
- readFileState.set(filePath, { timestamp: fs52.statSync(filePath).mtimeMs });
19611
+ readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
19607
19612
  const action = isCreate ? "\u521B\u5EFA" : "\u66F4\u65B0";
19608
19613
  const lines = content.split("\n").length;
19609
19614
  const chars = content.length;
19610
- const stat4 = fs52.statSync(filePath);
19615
+ const stat4 = fs53.statSync(filePath);
19611
19616
  let diff = "";
19612
19617
  if (!isCreate && oldContent !== null) {
19613
19618
  diff = `
@@ -19627,8 +19632,8 @@ ${simpleDiff(oldContent, content)}`;
19627
19632
 
19628
19633
  // src/tools/edit.ts
19629
19634
  var edit_exports = {};
19630
- import * as fs53 from "node:fs";
19631
- import * as path52 from "node:path";
19635
+ import * as fs54 from "node:fs";
19636
+ import * as path53 from "node:path";
19632
19637
  function normalizeQuotes(str) {
19633
19638
  return str.replaceAll(LEFT_SINGLE_CURLY, "'").replaceAll(RIGHT_SINGLE_CURLY, "'").replaceAll(LEFT_DOUBLE_CURLY, '"').replaceAll(RIGHT_DOUBLE_CURLY, '"');
19634
19639
  }
@@ -19781,7 +19786,7 @@ Usage:
19781
19786
  }
19782
19787
  let fileContent = null;
19783
19788
  try {
19784
- const stat4 = fs53.statSync(filePath);
19789
+ const stat4 = fs54.statSync(filePath);
19785
19790
  if (stat4.isDirectory()) {
19786
19791
  return { content: `\u8DEF\u5F84\u662F\u76EE\u5F55\u4E0D\u662F\u6587\u4EF6: ${filePath}`, isError: true };
19787
19792
  }
@@ -19791,21 +19796,21 @@ Usage:
19791
19796
  } catch (e) {
19792
19797
  if (e.code === "ENOENT") {
19793
19798
  if (oldString === "") {
19794
- const dir = path52.dirname(filePath);
19795
- fs53.mkdirSync(dir, { recursive: true });
19796
- fs53.writeFileSync(filePath, newString, "utf-8");
19797
- readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
19799
+ const dir = path53.dirname(filePath);
19800
+ fs54.mkdirSync(dir, { recursive: true });
19801
+ fs54.writeFileSync(filePath, newString, "utf-8");
19802
+ readFileState.set(filePath, { timestamp: fs54.statSync(filePath).mtimeMs });
19798
19803
  return { content: `\u521B\u5EFA\u6587\u4EF6: ${filePath} (${newString.split("\n").length} \u884C)` };
19799
19804
  }
19800
19805
  return { content: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, isError: true };
19801
19806
  }
19802
19807
  throw e;
19803
19808
  }
19804
- const rawContent = fs53.readFileSync(filePath, "utf-8");
19809
+ const rawContent = fs54.readFileSync(filePath, "utf-8");
19805
19810
  fileContent = rawContent.replaceAll("\r\n", "\n");
19806
19811
  if (oldString === "" && fileContent.trim() === "") {
19807
- fs53.writeFileSync(filePath, newString, "utf-8");
19808
- readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
19812
+ fs54.writeFileSync(filePath, newString, "utf-8");
19813
+ readFileState.set(filePath, { timestamp: fs54.statSync(filePath).mtimeMs });
19809
19814
  return { content: `\u5199\u5165\u7A7A\u6587\u4EF6: ${filePath} (${newString.split("\n").length} \u884C)` };
19810
19815
  }
19811
19816
  const readState = readFileState.get(filePath);
@@ -19815,11 +19820,11 @@ Usage:
19815
19820
  isError: true
19816
19821
  };
19817
19822
  }
19818
- const currentStat = fs53.statSync(filePath);
19823
+ const currentStat = fs54.statSync(filePath);
19819
19824
  const lastWriteTime = Math.floor(currentStat.mtimeMs);
19820
19825
  if (lastWriteTime > readState.timestamp) {
19821
19826
  if (fileContent !== rawContent.replaceAll("\r\n", "\n")) {
19822
- if (fileContent !== fs53.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n")) {
19827
+ if (fileContent !== fs54.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n")) {
19823
19828
  return {
19824
19829
  content: `\u6587\u4EF6\u5728\u8BFB\u53D6\u540E\u88AB\u4FEE\u6539\u3002\u8BF7\u5148\u91CD\u65B0\u8BFB\u53D6\u6587\u4EF6\u518D\u7F16\u8F91: ${filePath}`,
19825
19830
  isError: true
@@ -19851,8 +19856,8 @@ ${preview}
19851
19856
  const actualNewString = preserveQuoteStyle(oldString, actualOldString, newString);
19852
19857
  const diffView = generateEditDiff(fileContent, actualOldString, actualNewString);
19853
19858
  const newContent = applyEditToFile(fileContent, actualOldString, actualNewString, replaceAll);
19854
- fs53.writeFileSync(filePath, newContent, "utf-8");
19855
- readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
19859
+ fs54.writeFileSync(filePath, newContent, "utf-8");
19860
+ readFileState.set(filePath, { timestamp: fs54.statSync(filePath).mtimeMs });
19856
19861
  const strategy = actualOldString === oldString ? "\u7CBE\u786E\u5339\u914D" : "\u5F15\u53F7\u89C4\u8303\u5316\u5339\u914D";
19857
19862
  const count = replaceAll ? matchCount : 1;
19858
19863
  const diff = `${oldString.length}\u2192${newString.length}\u5B57\u7B26`;
@@ -19870,8 +19875,8 @@ ${diffView}`
19870
19875
 
19871
19876
  // src/tools/glob.ts
19872
19877
  var glob_exports = {};
19873
- import * as fs54 from "node:fs";
19874
- import * as path53 from "node:path";
19878
+ import * as fs55 from "node:fs";
19879
+ import * as path54 from "node:path";
19875
19880
  function globMatch(pattern, filename) {
19876
19881
  const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
19877
19882
  try {
@@ -19891,18 +19896,18 @@ function findFiles(dir, pattern, limit, baseDir) {
19891
19896
  }
19892
19897
  let entries2;
19893
19898
  try {
19894
- entries2 = fs54.readdirSync(currentDir, { withFileTypes: true });
19899
+ entries2 = fs55.readdirSync(currentDir, { withFileTypes: true });
19895
19900
  } catch {
19896
19901
  return;
19897
19902
  }
19898
19903
  for (const entry of entries2) {
19899
19904
  if (truncated) return;
19900
- const fullPath = path53.join(currentDir, entry.name);
19905
+ const fullPath = path54.join(currentDir, entry.name);
19901
19906
  if (entry.isDirectory()) {
19902
19907
  if (VCS_DIRS.has(entry.name)) continue;
19903
19908
  walk(fullPath);
19904
19909
  } else if (entry.isFile()) {
19905
- const relativePath = path53.relative(baseDir, fullPath).replace(/\\/g, "/");
19910
+ const relativePath = path54.relative(baseDir, fullPath).replace(/\\/g, "/");
19906
19911
  const patternsToTry = [pattern];
19907
19912
  if (pattern.startsWith("**/")) {
19908
19913
  patternsToTry.push(pattern.slice(3));
@@ -19912,7 +19917,7 @@ function findFiles(dir, pattern, limit, baseDir) {
19912
19917
  );
19913
19918
  if (matched) {
19914
19919
  try {
19915
- const stat4 = fs54.statSync(fullPath);
19920
+ const stat4 = fs55.statSync(fullPath);
19916
19921
  results.push({ path: fullPath, mtimeMs: stat4.mtimeMs });
19917
19922
  } catch {
19918
19923
  }
@@ -19929,7 +19934,7 @@ function findFiles(dir, pattern, limit, baseDir) {
19929
19934
  };
19930
19935
  }
19931
19936
  function toRelativePath(absolutePath, cwd) {
19932
- if (absolutePath.startsWith(cwd + path53.sep)) {
19937
+ if (absolutePath.startsWith(cwd + path54.sep)) {
19933
19938
  return absolutePath.slice(cwd.length + 1);
19934
19939
  }
19935
19940
  return absolutePath;
@@ -19962,10 +19967,10 @@ var init_glob = __esm({
19962
19967
  const searchPath = args2.path ? resolvePath(args2.path, ctx.workspace) : ctx.workspace;
19963
19968
  const pattern = args2.pattern;
19964
19969
  const limit = args2.limit || DEFAULT_LIMIT;
19965
- if (!fs54.existsSync(searchPath)) {
19970
+ if (!fs55.existsSync(searchPath)) {
19966
19971
  return { content: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${searchPath}`, isError: true };
19967
19972
  }
19968
- if (!fs54.statSync(searchPath).isDirectory()) {
19973
+ if (!fs55.statSync(searchPath).isDirectory()) {
19969
19974
  return { content: `\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${searchPath}`, isError: true };
19970
19975
  }
19971
19976
  const start = Date.now();
@@ -19990,7 +19995,7 @@ ${filenames.join("\n")}${truncatedNote}`
19990
19995
  // src/tools/grep.ts
19991
19996
  var grep_exports = {};
19992
19997
  import { execFile as execFile4 } from "node:child_process";
19993
- import * as path54 from "node:path";
19998
+ import * as path55 from "node:path";
19994
19999
  function ripGrep(args2, searchPath, signal) {
19995
20000
  return new Promise((resolve12) => {
19996
20001
  const fullArgs = [...args2, searchPath];
@@ -20022,7 +20027,7 @@ function applyHeadLimit(items, limit, offset = 0) {
20022
20027
  };
20023
20028
  }
20024
20029
  function toRelativePath2(absolutePath, cwd) {
20025
- if (absolutePath.startsWith(cwd + path54.sep)) {
20030
+ if (absolutePath.startsWith(cwd + path55.sep)) {
20026
20031
  return absolutePath.slice(cwd.length + 1);
20027
20032
  }
20028
20033
  if (absolutePath.startsWith(cwd)) {
@@ -21831,7 +21836,7 @@ var init_TeamDeleteTool = __esm({
21831
21836
  },
21832
21837
  isEnabled: () => isAgentSwarmsEnabled(),
21833
21838
  handler: async (_args, ctx) => {
21834
- const teamName = ctx.teamName;
21839
+ const teamName = ctx.teamName || getLeaderTeamName() || void 0;
21835
21840
  if (teamName) {
21836
21841
  const teamFile = readTeamFile(teamName);
21837
21842
  if (teamFile) {
@@ -21915,7 +21920,7 @@ function getAgentName(ctx) {
21915
21920
  return ctx.agentName || TEAM_LEAD_NAME;
21916
21921
  }
21917
21922
  function getTeamName(ctx) {
21918
- return ctx.teamName;
21923
+ return ctx.teamName || getLeaderTeamName() || void 0;
21919
21924
  }
21920
21925
  async function handleMessage(recipientName, content, summary, ctx) {
21921
21926
  const teamName = getTeamName(ctx);
@@ -22759,8 +22764,8 @@ __export(manager_exports, {
22759
22764
  McpManager: () => McpManager,
22760
22765
  convertInputSchema: () => convertInputSchema
22761
22766
  });
22762
- import * as fs57 from "node:fs";
22763
- import * as path57 from "node:path";
22767
+ import * as fs58 from "node:fs";
22768
+ import * as path58 from "node:path";
22764
22769
  import { Client as Client3 } from "@modelcontextprotocol/sdk/client/index.js";
22765
22770
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
22766
22771
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -22801,12 +22806,12 @@ function convertInputSchema(inputSchema) {
22801
22806
  }
22802
22807
  function persistBinary(base64Data, mimeType, persistId) {
22803
22808
  const ext = mimeType?.split("/")[1] || "bin";
22804
- const dir = path57.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
22805
- fs57.mkdirSync(dir, { recursive: true });
22806
- const filepath = path57.join(dir, `${persistId}.${ext}`);
22809
+ const dir = path58.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
22810
+ fs58.mkdirSync(dir, { recursive: true });
22811
+ const filepath = path58.join(dir, `${persistId}.${ext}`);
22807
22812
  try {
22808
22813
  const buf = Buffer.from(base64Data, "base64");
22809
- fs57.writeFileSync(filepath, buf);
22814
+ fs58.writeFileSync(filepath, buf);
22810
22815
  return { filepath, size: buf.length };
22811
22816
  } catch (err) {
22812
22817
  return { error: err.message };
@@ -23140,7 +23145,7 @@ __export(resources_exports, {
23140
23145
  registerMcpResourceTools: () => registerMcpResourceTools,
23141
23146
  unregisterMcpResourceTools: () => unregisterMcpResourceTools
23142
23147
  });
23143
- import * as path58 from "node:path";
23148
+ import * as path59 from "node:path";
23144
23149
  function registerMcpResourceTools(manager) {
23145
23150
  mcpManagerRef = manager;
23146
23151
  registry.register(listResourcesTool);
@@ -23158,7 +23163,7 @@ var init_resources = __esm({
23158
23163
  "use strict";
23159
23164
  init_registry();
23160
23165
  MAX_RESULT_CHARS2 = 1e5;
23161
- MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path58.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
23166
+ MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path59.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
23162
23167
  MCP_LIST_RESOURCES_TOOL = "mcp__list_resources";
23163
23168
  MCP_READ_RESOURCE_TOOL = "mcp__read_resource";
23164
23169
  mcpManagerRef = null;
@@ -23247,17 +23252,33 @@ ${lines.join("\n\n")}`;
23247
23252
  init_loader();
23248
23253
 
23249
23254
  // src/config/secrets-env.ts
23255
+ import { createHash } from "node:crypto";
23250
23256
  import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
23251
- import { basename, dirname, join as join2 } from "node:path";
23257
+ import { basename, dirname, join as join2, normalize } from "node:path";
23252
23258
  function secretsHome() {
23253
23259
  return process.env.HOME || process.env.USERPROFILE || ".";
23254
23260
  }
23261
+ function normalizeStateDir(stateDir) {
23262
+ return normalize(stateDir).split("\\").join("/").replace(/\/+$/, "").toLowerCase();
23263
+ }
23264
+ function stateKey(stateDir) {
23265
+ return createHash("sha256").update(normalizeStateDir(stateDir)).digest("hex").slice(0, 8);
23266
+ }
23267
+ function isDefaultStateDir(stateDir) {
23268
+ return normalizeStateDir(stateDir) === normalizeStateDir(join2(secretsHome(), ".engine7"));
23269
+ }
23255
23270
  function secretsCandidates(stateDir, configName) {
23256
- return [
23257
- join2(secretsHome(), ".engine7-secrets", `${configName}.env`),
23271
+ const shared = join2(secretsHome(), ".engine7-secrets", `${configName}.env`);
23272
+ const inState = [
23258
23273
  join2(stateDir, "configs", `.env.${configName}`),
23259
23274
  join2(stateDir, "configs", ".env")
23260
23275
  ];
23276
+ if (isDefaultStateDir(stateDir)) return [shared, ...inState];
23277
+ return [
23278
+ join2(secretsHome(), ".engine7-secrets", `${configName}-${stateKey(stateDir)}.env`),
23279
+ shared,
23280
+ ...inState
23281
+ ];
23261
23282
  }
23262
23283
  function loadSecretsEnv(stateDir, configName) {
23263
23284
  for (const p of secretsCandidates(stateDir, configName)) {
@@ -23290,7 +23311,7 @@ function loadSecretsEnvForConfig(configPath2) {
23290
23311
  // src/engine-startup.ts
23291
23312
  init_query();
23292
23313
  init_live();
23293
- import * as path59 from "node:path";
23314
+ import * as path60 from "node:path";
23294
23315
 
23295
23316
  // src/startup/providers.ts
23296
23317
  init_provider_factory();
@@ -24643,7 +24664,7 @@ ${formatted}` };
24643
24664
  };
24644
24665
  }
24645
24666
  function createEverosGetTool() {
24646
- const fs58 = __require("node:fs/promises");
24667
+ const fs59 = __require("node:fs/promises");
24647
24668
  return {
24648
24669
  name: "memory_get",
24649
24670
  description: "Read a memory file by path.",
@@ -24659,7 +24680,7 @@ function createEverosGetTool() {
24659
24680
  handler: async (args2) => {
24660
24681
  try {
24661
24682
  const filePath = args2.path;
24662
- const content = await fs58.readFile(filePath, "utf-8");
24683
+ const content = await fs59.readFile(filePath, "utf-8");
24663
24684
  const lines = content.split("\n");
24664
24685
  const fromLine = args2.from ?? 1;
24665
24686
  const numLines = args2.lines ?? lines.length;
@@ -26452,18 +26473,18 @@ function resetSessionStartInjection(sessionId) {
26452
26473
  }
26453
26474
  var externalChanRulesCache = null;
26454
26475
  function loadExternalChanRules(workspace) {
26455
- const path60 = join36(workspace, "prompts", "external-chan-rules.md");
26456
- if (externalChanRulesCache && externalChanRulesCache.path === path60) return externalChanRulesCache;
26476
+ const path61 = join36(workspace, "prompts", "external-chan-rules.md");
26477
+ if (externalChanRulesCache && externalChanRulesCache.path === path61) return externalChanRulesCache;
26457
26478
  let content = "";
26458
- if (existsSync20(path60)) {
26479
+ if (existsSync20(path61)) {
26459
26480
  try {
26460
- content = readFileSync27(path60, "utf-8").trim();
26481
+ content = readFileSync27(path61, "utf-8").trim();
26461
26482
  } catch (e) {
26462
26483
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
26463
26484
  }
26464
26485
  }
26465
- externalChanRulesCache = { path: path60, content };
26466
- console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path60}`);
26486
+ externalChanRulesCache = { path: path61, content };
26487
+ console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path61}`);
26467
26488
  return externalChanRulesCache;
26468
26489
  }
26469
26490
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -27350,15 +27371,15 @@ function registerCompactHooks(config2) {
27350
27371
  const dailyDir = path22.join(workspace, "memory", "daily");
27351
27372
  const dailyPath = path22.join(dailyDir, `${dateStr}.md`);
27352
27373
  try {
27353
- const fs58 = await import("node:fs");
27354
- if (!fs58.existsSync(dailyDir)) {
27355
- fs58.mkdirSync(dailyDir, { recursive: true });
27374
+ const fs59 = await import("node:fs");
27375
+ if (!fs59.existsSync(dailyDir)) {
27376
+ fs59.mkdirSync(dailyDir, { recursive: true });
27356
27377
  }
27357
27378
  const sessionsDir = path22.join(config2.stateDir, "agents", "main", "sessions");
27358
27379
  const sessionFile = path22.join(sessionsDir, `${sessionId}.jsonl`);
27359
27380
  const recentLines = [];
27360
- if (fs58.existsSync(sessionFile)) {
27361
- const content = fs58.readFileSync(sessionFile, "utf-8");
27381
+ if (fs59.existsSync(sessionFile)) {
27382
+ const content = fs59.readFileSync(sessionFile, "utf-8");
27362
27383
  const lines = content.trim().split("\n").filter(Boolean);
27363
27384
  const userLines = lines.filter((l) => {
27364
27385
  try {
@@ -27388,10 +27409,10 @@ function registerCompactHooks(config2) {
27388
27409
  const entry = `${header}
27389
27410
  ${body}
27390
27411
  `;
27391
- if (fs58.existsSync(dailyPath)) {
27392
- fs58.appendFileSync(dailyPath, entry);
27412
+ if (fs59.existsSync(dailyPath)) {
27413
+ fs59.appendFileSync(dailyPath, entry);
27393
27414
  } else {
27394
- fs58.writeFileSync(dailyPath, `# ${dateStr} \u65E5\u5FD7
27415
+ fs59.writeFileSync(dailyPath, `# ${dateStr} \u65E5\u5FD7
27395
27416
  ${entry}`);
27396
27417
  }
27397
27418
  console.log(`[hooks] PreCompact: saved ${recentLines.length} lines to ${dailyPath}`);
@@ -27407,16 +27428,16 @@ ${entry}`);
27407
27428
  const workspace = input.cwd || input.workspace || "";
27408
27429
  if (!workspace) return { continue: true };
27409
27430
  try {
27410
- const fs58 = await import("node:fs");
27431
+ const fs59 = await import("node:fs");
27411
27432
  const bufferPath = path22.join(workspace, "memory", "working-buffer.md");
27412
- if (fs58.existsSync(bufferPath)) {
27413
- const stat4 = fs58.statSync(bufferPath);
27433
+ if (fs59.existsSync(bufferPath)) {
27434
+ const stat4 = fs59.statSync(bufferPath);
27414
27435
  const ageMs = Date.now() - stat4.mtimeMs;
27415
27436
  const ageMin = Math.round(ageMs / 6e4);
27416
27437
  if (ageMin > 10) {
27417
27438
  console.warn(`[hooks] PostCompact: \u26A0\uFE0F working-buffer.md is ${ageMin}min old (last modified ${stat4.mtime.toISOString()}) \u2014 content may be stale!`);
27418
27439
  }
27419
- const content = fs58.readFileSync(bufferPath, "utf-8");
27440
+ const content = fs59.readFileSync(bufferPath, "utf-8");
27420
27441
  if (content.trim()) {
27421
27442
  console.log(`[hooks] PostCompact: injecting working-buffer (${content.length} chars, ${ageMin}min old)`);
27422
27443
  return {
@@ -33871,10 +33892,10 @@ function writeReportState(stateDir, s) {
33871
33892
  }
33872
33893
 
33873
33894
  // src/optional/reporting/payload.ts
33874
- import { createHash as createHash2 } from "node:crypto";
33895
+ import { createHash as createHash3 } from "node:crypto";
33875
33896
  import * as os3 from "node:os";
33876
33897
  function installKeyOf(installId, installSecret) {
33877
- return createHash2("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33898
+ return createHash3("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
33878
33899
  }
33879
33900
  function buildReportPayload(opts) {
33880
33901
  const p = {
@@ -34251,13 +34272,13 @@ var DiscordAdapter = class _DiscordAdapter {
34251
34272
  }
34252
34273
  /** 发送媒体附件(图片/文件/音频)— discord.js channel.send({ files }) */
34253
34274
  async sendFile(target, message, attachment) {
34254
- const fs58 = await import("node:fs");
34255
- const path60 = await import("node:path");
34256
- if (!fs58.existsSync(attachment.path)) {
34275
+ const fs59 = await import("node:fs");
34276
+ const path61 = await import("node:path");
34277
+ if (!fs59.existsSync(attachment.path)) {
34257
34278
  throw new Error(`File not found: ${attachment.path}`);
34258
34279
  }
34259
- const filename = attachment.filename || path60.basename(attachment.path);
34260
- const fileBuffer = fs58.readFileSync(attachment.path);
34280
+ const filename = attachment.filename || path61.basename(attachment.path);
34281
+ const fileBuffer = fs59.readFileSync(attachment.path);
34261
34282
  const filePayload = {
34262
34283
  attachment: fileBuffer,
34263
34284
  name: filename
@@ -34706,13 +34727,13 @@ var FeishuAdapter = class _FeishuAdapter {
34706
34727
  }
34707
34728
  /** 发送媒体附件(图片/文件) */
34708
34729
  async sendFile(target, message, attachment) {
34709
- const fs58 = await import("node:fs");
34710
- const path60 = await import("node:path");
34711
- if (!fs58.existsSync(attachment.path)) {
34730
+ const fs59 = await import("node:fs");
34731
+ const path61 = await import("node:path");
34732
+ if (!fs59.existsSync(attachment.path)) {
34712
34733
  throw new Error(`File not found: ${attachment.path}`);
34713
34734
  }
34714
- const filename = attachment.filename || path60.basename(attachment.path);
34715
- const fileBuffer = fs58.readFileSync(attachment.path);
34735
+ const filename = attachment.filename || path61.basename(attachment.path);
34736
+ const fileBuffer = fs59.readFileSync(attachment.path);
34716
34737
  const safeTarget = target || this.config.defaultTarget || "";
34717
34738
  if (!safeTarget) throw new Error("Feishu sendFile: no target (\u4E3B\u52A8\u53D1\u9001\u9700\u914D\u7F6E defaultTarget \u6216\u4F20\u5165 target)");
34718
34739
  const receiveIdType = safeTarget.startsWith("ou_") ? "open_id" : "chat_id";
@@ -36534,22 +36555,22 @@ var DeskBuddyAdapter = class {
36534
36555
  try {
36535
36556
  if (!this.historyFile) {
36536
36557
  const os7 = __require("os");
36537
- const fs59 = __require("fs");
36538
- const path60 = __require("path");
36539
- const dir = path60.join(os7.homedir(), "xiaowen", "workspace", ".desktopBuddy");
36540
- fs59.mkdirSync(dir, { recursive: true });
36541
- this.historyFile = path60.join(dir, "history.json");
36542
- this._fs = fs59;
36543
- }
36544
- const fs58 = this._fs;
36558
+ const fs60 = __require("fs");
36559
+ const path61 = __require("path");
36560
+ const dir = path61.join(os7.homedir(), "xiaowen", "workspace", ".desktopBuddy");
36561
+ fs60.mkdirSync(dir, { recursive: true });
36562
+ this.historyFile = path61.join(dir, "history.json");
36563
+ this._fs = fs60;
36564
+ }
36565
+ const fs59 = this._fs;
36545
36566
  let arr = [];
36546
36567
  try {
36547
- arr = JSON.parse(fs58.readFileSync(this.historyFile, "utf-8")) || [];
36568
+ arr = JSON.parse(fs59.readFileSync(this.historyFile, "utf-8")) || [];
36548
36569
  } catch {
36549
36570
  }
36550
36571
  arr.push({ ...entry, ts: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN", { hour12: false }) });
36551
36572
  if (arr.length > 500) arr = arr.slice(-500);
36552
- fs58.writeFileSync(this.historyFile, JSON.stringify(arr, null, 1));
36573
+ fs59.writeFileSync(this.historyFile, JSON.stringify(arr, null, 1));
36553
36574
  } catch (e) {
36554
36575
  console.warn("[deskBuddy] history write fail", e);
36555
36576
  }
@@ -36828,8 +36849,365 @@ var DeskBuddyAdapter = class {
36828
36849
  }
36829
36850
  };
36830
36851
 
36852
+ // src/channels/cc-bridge.ts
36853
+ init_live();
36854
+ import fs41 from "node:fs";
36855
+ import path41 from "node:path";
36856
+ import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
36857
+ var DEFAULT_PORT = 16996;
36858
+ var CcBridgeAdapter = class {
36859
+ name = "ccBridge";
36860
+ config;
36861
+ wss = null;
36862
+ clients = /* @__PURE__ */ new Set();
36863
+ messageHandler = null;
36864
+ seq = 0;
36865
+ constructor(config2) {
36866
+ this.config = config2;
36867
+ }
36868
+ /** 工具流水默认不推(见 CcBridgeConfig.showToolDisplay) */
36869
+ get suppressToolDisplay() {
36870
+ return this.config.showToolDisplay !== true;
36871
+ }
36872
+ /** 实际监听的端口。配 `port: 0` 时由内核分配 —— 测试用它拿真端口,不用赌某个数字没被占 */
36873
+ get boundPort() {
36874
+ const a = this.wss?.address();
36875
+ return a && typeof a === "object" ? a.port : null;
36876
+ }
36877
+ onMessage(handler2) {
36878
+ this.messageHandler = handler2;
36879
+ }
36880
+ // ── outbox(持久队列)──
36881
+ //
36882
+ // 每次现读 stateDir,不缓存 —— stateDir 的 effect 是 restart,缓存下来的
36883
+ // 只会在热加载后变成指向旧目录的幽灵路径(配置访问铁律)。
36884
+ outboxFile() {
36885
+ const sd = liveConfig.get("stateDir") || process.cwd();
36886
+ return path41.join(sd, "state", "cc-bridge", "outbox.jsonl");
36887
+ }
36888
+ /**
36889
+ * 读队列。**只有 ENOENT 才等于"空队列"。**
36890
+ *
36891
+ * ⚠️ 这里原来写的是 `catch { return [] }` —— 那是**会丢数据的**:
36892
+ * 读失败(Windows 上 AV/同步盘/搜索索引的瞬时共享锁 EBUSY,或 EPERM/EACCES)
36893
+ * 返回空数组,紧接着 `enqueue` 拿这份空数组**整份覆写**,待投递的消息全没,
36894
+ * 而且一句警告都没有。而"读不了"和"不存在"是完全不同的两件事 ——
36895
+ * 这个区分正是"宁可重复,不可丢失"能不能成立的支点。
36896
+ */
36897
+ readOutbox() {
36898
+ let raw;
36899
+ try {
36900
+ raw = fs41.readFileSync(this.outboxFile(), "utf8");
36901
+ } catch (e) {
36902
+ if (e?.code === "ENOENT") return [];
36903
+ throw e;
36904
+ }
36905
+ const out = [];
36906
+ for (const line of raw.split("\n")) {
36907
+ if (!line.trim()) continue;
36908
+ try {
36909
+ out.push(JSON.parse(line));
36910
+ } catch {
36911
+ }
36912
+ }
36913
+ return out;
36914
+ }
36915
+ /**
36916
+ * 写队列:**先写 .tmp 再 rename**。
36917
+ *
36918
+ * `writeFileSync(file, ...)` 是原地截断(`w` 模式先清零再写)——
36919
+ * 在这中间被 taskkill / OOM / 断电,文件就是空的或半截,
36920
+ * 下次读回来是 [] 且无从分辨,最多 maxQueue 条待投递消息整体消失。
36921
+ * rename 到已存在文件走的是替换语义,落盘要么全旧要么全新。
36922
+ *
36923
+ * **失败要抛**:调用方(cron/heartbeat 经 `manager.send`)必须知道没存住 ——
36924
+ * 吞成 warning 的话,磁盘满/只读时客户端在线还能收到实时那条,
36925
+ * 客户端离线就是真丢,而这恰恰是这个通道要保的场景。
36926
+ */
36927
+ writeOutbox(items) {
36928
+ const file2 = this.outboxFile();
36929
+ const tmp = `${file2}.tmp`;
36930
+ fs41.mkdirSync(path41.dirname(file2), { recursive: true });
36931
+ fs41.writeFileSync(tmp, items.map((i) => JSON.stringify(i)).join("\n") + (items.length ? "\n" : ""), "utf8");
36932
+ fs41.renameSync(tmp, file2);
36933
+ }
36934
+ /** 入队(dedupe 不做 —— id 是新的,重复投递由 ack 收敛)。读/写失败都往外抛,不吞。 */
36935
+ enqueue(item) {
36936
+ const items = this.readOutbox();
36937
+ items.push(item);
36938
+ const cap = this.config.maxQueue ?? 500;
36939
+ if (items.length > cap) {
36940
+ const dropped = items.length - cap;
36941
+ items.splice(0, dropped);
36942
+ console.warn(`[ccBridge] outbox \u8D85\u8FC7 ${cap} \u6761\uFF0C\u4E22\u4E86\u6700\u8001\u7684 ${dropped} \u6761 \u2014\u2014 CC \u592A\u4E45\u6CA1\u6765\u6536`);
36943
+ }
36944
+ this.writeOutbox(items);
36945
+ }
36946
+ /**
36947
+ * ack:从队列删掉客户端确认过的。
36948
+ *
36949
+ * 读失败时**返回 0 而不是抛** —— 这里宁可"删不掉"也不能"删错":
36950
+ * ack 的语义是收敛,失败留给下一轮再收敛,代价只是重复投递。
36951
+ */
36952
+ ack(ids) {
36953
+ if (!ids.length) return 0;
36954
+ const set = new Set(ids);
36955
+ let items;
36956
+ try {
36957
+ items = this.readOutbox();
36958
+ } catch (e) {
36959
+ 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}`);
36960
+ return 0;
36961
+ }
36962
+ const kept = items.filter((i) => !set.has(i.id));
36963
+ const removed = items.length - kept.length;
36964
+ if (removed) {
36965
+ try {
36966
+ this.writeOutbox(kept);
36967
+ } catch (e) {
36968
+ console.warn(`[ccBridge] ack \u5199\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF1A${e.message}`);
36969
+ return 0;
36970
+ }
36971
+ }
36972
+ return removed;
36973
+ }
36974
+ /** 队列条数(读不到就算 0 —— 只用于日志/握手,不能用来做删除决策) */
36975
+ queuedCount() {
36976
+ try {
36977
+ return this.readOutbox().length;
36978
+ } catch {
36979
+ return 0;
36980
+ }
36981
+ }
36982
+ /** 读队列但**不因读失败而中断**(握手补发、日志这类"读不到也能继续"的场合) */
36983
+ safeReadOutbox(where) {
36984
+ try {
36985
+ return this.readOutbox();
36986
+ } catch (e) {
36987
+ 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}`);
36988
+ return [];
36989
+ }
36990
+ }
36991
+ /**
36992
+ * 升级握手校验。见 `connect()` 里那段注释 —— 绑回环挡不住浏览器。
36993
+ * 返回 false 时 `ws` 会直接拒绝升级(HTTP 401),连接根本建立不起来。
36994
+ */
36995
+ verifyUpgrade(info) {
36996
+ const h = info?.req?.headers ?? {};
36997
+ if (h.origin) {
36998
+ console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u5E26 Origin \u7684\u8FDE\u63A5\uFF1A${h.origin}\uFF08\u7591\u4F3C\u7F51\u9875\u53D1\u8D77\uFF09`);
36999
+ return false;
37000
+ }
37001
+ const host = String(h.host || "").replace(/:\d+$/, "").toLowerCase();
37002
+ const ok = host === "127.0.0.1" || host === "localhost" || host === "[::1]" || host === "::1" || host === "";
37003
+ if (!ok) console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u975E\u56DE\u73AF Host\uFF1A${h.host}\uFF08\u7591\u4F3C DNS rebinding\uFF09`);
37004
+ return ok;
37005
+ }
37006
+ // ── 连接 ──
37007
+ async connect() {
37008
+ const port = this.config.port ?? DEFAULT_PORT;
37009
+ try {
37010
+ fs41.mkdirSync(path41.dirname(this.outboxFile()), { recursive: true });
37011
+ } catch {
37012
+ }
37013
+ this.wss = new WebSocketServer2({
37014
+ port,
37015
+ host: "127.0.0.1",
37016
+ maxPayload: 4 * 1024 * 1024,
37017
+ // ⛔ **绑 127.0.0.1 不是安全边界。** WebSocket 不受同源策略约束,
37018
+ // 用户浏览器里**任意** http 页面都能 `new WebSocket('ws://127.0.0.1:16996')`
37019
+ // 连上来 —— 而这里连上就能以任意 `from` 往 agent 主会话注入消息
37020
+ // (实测过:伪造 Origin/Host 能成功注入一条 `from: 翀哥` 的消息)。
37021
+ // 所以必须自己校验:
37022
+ // · 带 `Origin` 头 = 网页发起的 → 直接拒(`scripts/cc-inbox.mjs` 是 Node
37023
+ // 客户端,本来就不发这个头,不受影响)
37024
+ // · `Host` 必须是本机回环 → 挡 DNS rebinding(域名解析到 127.0.0.1 时
37025
+ // Host 会是那个域名,而不是 127.0.0.1)
37026
+ verifyClient: (info) => this.verifyUpgrade(info)
37027
+ });
37028
+ this.wss.on("connection", (ws) => {
37029
+ this.clients.add(ws);
37030
+ console.log(`[ccBridge] CC connected (${this.clients.size} online)`);
37031
+ const pending2 = this.safeReadOutbox("\u63E1\u624B\u8865\u53D1");
37032
+ this.sendTo(ws, { type: "hello", channel: this.name, queued: pending2.length });
37033
+ if (pending2.length) {
37034
+ console.log(`[ccBridge] \u8865\u53D1 ${pending2.length} \u6761\u79EF\u538B`);
37035
+ this.sendTo(ws, { type: "backlog", messages: pending2 });
37036
+ }
37037
+ ws.on("message", (data) => this.handleFrame(ws, data));
37038
+ ws.on("close", () => {
37039
+ this.clients.delete(ws);
37040
+ console.log(`[ccBridge] CC disconnected (${this.clients.size} online)`);
37041
+ });
37042
+ ws.on("error", () => this.clients.delete(ws));
37043
+ });
37044
+ this.wss.on("error", (e) => {
37045
+ console.error(`[ccBridge] server error on 127.0.0.1:${port}: ${e.message}`);
37046
+ });
37047
+ await new Promise((resolve12, reject) => {
37048
+ const wss = this.wss;
37049
+ const ok = () => {
37050
+ wss.off("error", bad);
37051
+ resolve12();
37052
+ };
37053
+ const bad = (e) => {
37054
+ wss.off("listening", ok);
37055
+ reject(e);
37056
+ };
37057
+ wss.once("listening", ok);
37058
+ wss.once("error", bad);
37059
+ });
37060
+ console.log(`[ccBridge] listening on 127.0.0.1:${this.boundPort} (${this.queuedCount()} queued)`);
37061
+ }
37062
+ handleFrame(ws, data) {
37063
+ let msg2;
37064
+ try {
37065
+ msg2 = JSON.parse(String(data));
37066
+ } catch {
37067
+ this.sendTo(ws, { type: "error", error: "bad json" });
37068
+ return;
37069
+ }
37070
+ if (msg2.type === "ping") {
37071
+ this.sendTo(ws, { type: "pong" });
37072
+ return;
37073
+ }
37074
+ if (msg2.type === "ack") {
37075
+ const ids = Array.isArray(msg2.ids) ? msg2.ids.map(String) : [];
37076
+ const n = this.ack(ids);
37077
+ this.sendTo(ws, { type: "ack-ok", removed: n, queued: this.queuedCount() });
37078
+ return;
37079
+ }
37080
+ if (msg2.type === "chat") {
37081
+ const text = String(msg2.text ?? "").trim();
37082
+ if (!text) {
37083
+ this.sendTo(ws, { type: "error", error: "empty text" });
37084
+ return;
37085
+ }
37086
+ const from = String(msg2.from || this.config.userId || "cc");
37087
+ const inbound = {
37088
+ content: text,
37089
+ from,
37090
+ fromName: String(msg2.fromName || from),
37091
+ channel_id: this.name,
37092
+ channel: this.name,
37093
+ channelType: "dm",
37094
+ isBot: false,
37095
+ messageId: `ccb-${Date.now()}-${++this.seq}`,
37096
+ recvAt: Date.now()
37097
+ };
37098
+ console.log(`[ccBridge] \u2190 ${inbound.fromName}: ${text.slice(0, 80)}`);
37099
+ this.messageHandler?.(inbound);
37100
+ return;
37101
+ }
37102
+ this.sendTo(ws, { type: "error", error: `unknown type: ${msg2.type}` });
37103
+ }
37104
+ sendTo(ws, payload) {
37105
+ if (ws.readyState !== WebSocket2.OPEN) return;
37106
+ try {
37107
+ ws.send(JSON.stringify(payload));
37108
+ } catch {
37109
+ }
37110
+ }
37111
+ /** 广播给出站消息之外的控制帧(握手/pong 走 sendTo 单播) */
37112
+ broadcast(payload) {
37113
+ const s = JSON.stringify(payload);
37114
+ let n = 0;
37115
+ for (const ws of [...this.clients]) {
37116
+ if (ws.readyState !== WebSocket2.OPEN) {
37117
+ this.clients.delete(ws);
37118
+ continue;
37119
+ }
37120
+ try {
37121
+ ws.send(s);
37122
+ n++;
37123
+ } catch {
37124
+ this.clients.delete(ws);
37125
+ }
37126
+ }
37127
+ return n;
37128
+ }
37129
+ async disconnect() {
37130
+ for (const ws of [...this.clients]) {
37131
+ try {
37132
+ ws.close();
37133
+ } catch {
37134
+ }
37135
+ }
37136
+ this.clients.clear();
37137
+ this.wss?.close();
37138
+ this.wss = null;
37139
+ }
37140
+ // ── 出站 ──
37141
+ /**
37142
+ * 发消息给 CC。
37143
+ *
37144
+ * **先落 outbox 再推实时** —— 顺序反过来的话,推送成功但 CC 恰好这时断开,
37145
+ * 这条消息就既不在队列里也没送达,静默消失。宁可重复,不可丢失:
37146
+ * 重复只是多读一遍,丢失是 agent 主动说了话而没人知道。
37147
+ */
37148
+ async send(_target, message, _options) {
37149
+ if (!message.trim()) return;
37150
+ const item = {
37151
+ id: `m${Date.now()}-${++this.seq}`,
37152
+ at: (/* @__PURE__ */ new Date()).toISOString(),
37153
+ text: message,
37154
+ agent: this.config.agentName || "agent"
37155
+ };
37156
+ this.enqueue(item);
37157
+ const n = this.broadcast({ type: "message", message: item });
37158
+ if (n === 0) console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u5DF2\u5165\u961F\uFF08${this.queuedCount()} \u6761\u5F85\u6536\uFF09`);
37159
+ }
37160
+ /**
37161
+ * 发文件。
37162
+ *
37163
+ * **只送路径,不送内容**(`mode: 'path-ref'`)。这个通道两端都在 127.0.0.1 上
37164
+ * (引擎与 CC 同机),把文件 base64 塞进 WS 帧纯属浪费 —— 大文件还会把 outbox 撑爆。
37165
+ *
37166
+ * ⚠️ **但"只送路径"是把解析推迟到接收端,推迟不等于可以不做。**
37167
+ * 它有一个很难看的退化形态:发送端返回「已发送」(路径形式上没问题),
37168
+ * 接收端拿着路径在**它自己的机器上**找不到 —— **失败发生在接收端,
37169
+ * 而发送端日志一切正常**。(张小T 0928 指出的,她是对的。)
37170
+ *
37171
+ * 所以这里补两道:
37172
+ * 1. **入队前 `statSync`** —— 路径打错/文件被挪走,**在本机就报错**,
37173
+ * 而不是等接收端去找一个不存在的路径
37174
+ * 2. 帧里带 `size`/`mtimeMs` + `mode: 'path-ref'` —— 接收端知道这是**引用不是内容**,
37175
+ * 且能对一下"拿到的是不是你说的那份"
37176
+ *
37177
+ * 客户端那侧对应地**读不到就不 ack**(见 `scripts/cc-inbox.mjs`)——
37178
+ * ack 的语义是"我拿到了",拿到一半不能算。
37179
+ */
37180
+ async sendFile(_target, message, attachment) {
37181
+ let st;
37182
+ try {
37183
+ st = fs41.statSync(attachment.path);
37184
+ } catch (e) {
37185
+ throw new Error(`\u9644\u4EF6\u4E0D\u5B58\u5728\uFF0C\u672A\u53D1\u9001\uFF1A${attachment.path}\uFF08${e?.code || e?.message}\uFF09`);
37186
+ }
37187
+ const file2 = {
37188
+ mode: "path-ref",
37189
+ path: attachment.path,
37190
+ filename: attachment.filename || path41.basename(attachment.path),
37191
+ mimeType: attachment.mimeType || "application/octet-stream",
37192
+ size: st.size,
37193
+ mtimeMs: Math.round(st.mtimeMs)
37194
+ };
37195
+ const item = {
37196
+ id: `f${Date.now()}-${++this.seq}`,
37197
+ at: (/* @__PURE__ */ new Date()).toISOString(),
37198
+ text: message || "",
37199
+ agent: this.config.agentName || "agent",
37200
+ file: file2
37201
+ };
37202
+ this.enqueue(item);
37203
+ if (this.broadcast({ type: "message", message: item }) === 0) {
37204
+ console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u9644\u4EF6\u5DF2\u5165\u961F\uFF1A${file2.filename}`);
37205
+ }
37206
+ }
37207
+ };
37208
+
36831
37209
  // src/channels/manager.ts
36832
- var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy"];
37210
+ var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"];
36833
37211
  var defaultFactories = {
36834
37212
  discord: (block) => {
36835
37213
  const accounts = block.accounts || {};
@@ -36891,6 +37269,9 @@ var defaultFactories = {
36891
37269
  },
36892
37270
  deskBuddy: (block) => {
36893
37271
  return new DeskBuddyAdapter(block);
37272
+ },
37273
+ ccBridge: (block) => {
37274
+ return new CcBridgeAdapter(block);
36894
37275
  }
36895
37276
  };
36896
37277
  var ChannelManager = class {
@@ -37162,20 +37543,20 @@ var ChannelManager = class {
37162
37543
  // src/session/session-manager.ts
37163
37544
  init_types();
37164
37545
  init_types();
37165
- import * as path42 from "node:path";
37166
- import * as fs42 from "node:fs";
37546
+ import * as path43 from "node:path";
37547
+ import * as fs43 from "node:fs";
37167
37548
  import * as crypto6 from "node:crypto";
37168
37549
 
37169
37550
  // src/session/reader.ts
37170
37551
  init_attachments();
37171
- import * as fs41 from "node:fs";
37172
- import * as path41 from "node:path";
37552
+ import * as fs42 from "node:fs";
37553
+ import * as path42 from "node:path";
37173
37554
  import * as readline2 from "node:readline";
37174
37555
  var SKIP_PRECOMPACT_THRESHOLD = 5 * 1024 * 1024;
37175
37556
  var TRANSCRIPT_READ_CHUNK_SIZE = 1024 * 1024;
37176
37557
  var MAX_TRANSCRIPT_READ_BYTES = 50 * 1024 * 1024;
37177
37558
  async function readSessionHeader(filePath) {
37178
- const stream = fs41.createReadStream(filePath, { encoding: "utf-8" });
37559
+ const stream = fs42.createReadStream(filePath, { encoding: "utf-8" });
37179
37560
  const rl = readline2.createInterface({ input: stream });
37180
37561
  return new Promise((resolve12) => {
37181
37562
  let resolved = false;
@@ -37206,7 +37587,7 @@ async function readSessionHeader(filePath) {
37206
37587
  });
37207
37588
  }
37208
37589
  async function readLargeFilePostBoundary(filePath) {
37209
- const stat4 = fs41.statSync(filePath);
37590
+ const stat4 = fs42.statSync(filePath);
37210
37591
  const fileSize = stat4.size;
37211
37592
  const boundaryMarker = '"compact_boundary"';
37212
37593
  const CHUNK_SIZE = TRANSCRIPT_READ_CHUNK_SIZE;
@@ -37214,13 +37595,13 @@ async function readLargeFilePostBoundary(filePath) {
37214
37595
  let outLen = 0;
37215
37596
  let boundaryStartOffset = 0;
37216
37597
  const chunk = Buffer.allocUnsafe(CHUNK_SIZE);
37217
- const fd = fs41.openSync(filePath, "r");
37598
+ const fd = fs42.openSync(filePath, "r");
37218
37599
  try {
37219
37600
  let filePos = 0;
37220
37601
  let carryBuf;
37221
37602
  let carryLen = 0;
37222
37603
  while (filePos < fileSize) {
37223
- const bytesRead = fs41.readSync(fd, chunk, 0, Math.min(CHUNK_SIZE, fileSize - filePos), filePos);
37604
+ const bytesRead = fs42.readSync(fd, chunk, 0, Math.min(CHUNK_SIZE, fileSize - filePos), filePos);
37224
37605
  if (bytesRead === 0) break;
37225
37606
  filePos += bytesRead;
37226
37607
  let scanBuf;
@@ -37272,7 +37653,7 @@ async function readLargeFilePostBoundary(filePath) {
37272
37653
  }
37273
37654
  }
37274
37655
  } finally {
37275
- fs41.closeSync(fd);
37656
+ fs42.closeSync(fd);
37276
37657
  }
37277
37658
  const postBoundaryText = outBuf.subarray(0, outLen).toString("utf-8");
37278
37659
  return postBoundaryText.split("\n").filter((l) => l.trim().length > 0);
@@ -37396,7 +37777,7 @@ function recoverOrphanedParallelToolResults(allEntries, chain, byUuid, seen) {
37396
37777
  return result;
37397
37778
  }
37398
37779
  async function readSessionHistory(filePath) {
37399
- const stat4 = fs41.statSync(filePath);
37780
+ const stat4 = fs42.statSync(filePath);
37400
37781
  const fileSize = stat4.size;
37401
37782
  if (fileSize > MAX_TRANSCRIPT_READ_BYTES) {
37402
37783
  console.warn(`[reader] Session file exceeds 50MB limit (${(fileSize / 1024 / 1024).toFixed(1)}MB), using chunked fd read with boundary skip`);
@@ -37435,7 +37816,7 @@ async function readSessionHistory(filePath) {
37435
37816
  return [...sessionStarts, ...filtered, ...restAttachments];
37436
37817
  }
37437
37818
  async function readAllLines(filePath) {
37438
- const stream = fs41.createReadStream(filePath, { encoding: "utf-8" });
37819
+ const stream = fs42.createReadStream(filePath, { encoding: "utf-8" });
37439
37820
  const rl = readline2.createInterface({ input: stream });
37440
37821
  const lines = [];
37441
37822
  return new Promise((resolve12, reject) => {
@@ -37589,12 +37970,12 @@ function extractText4(content) {
37589
37970
  return content.filter((b) => b.type === "text").map((b) => b.text || "").join("") || null;
37590
37971
  }
37591
37972
  function listSessions(sessionsDir) {
37592
- if (!fs41.existsSync(sessionsDir)) return [];
37593
- const files = fs41.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") || f.includes(".jsonl.")).map((f) => ({
37594
- file: path41.join(sessionsDir, f),
37973
+ if (!fs42.existsSync(sessionsDir)) return [];
37974
+ const files = fs42.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") || f.includes(".jsonl.")).map((f) => ({
37975
+ file: path42.join(sessionsDir, f),
37595
37976
  id: f.replace(/\.jsonl.*$/, ""),
37596
37977
  // strip .jsonl and everything after (archived suffix)
37597
- modified: new Date(fs41.statSync(path41.join(sessionsDir, f)).mtimeMs)
37978
+ modified: new Date(fs42.statSync(path42.join(sessionsDir, f)).mtimeMs)
37598
37979
  })).sort((a, b) => b.modified.getTime() - a.modified.getTime());
37599
37980
  return files;
37600
37981
  }
@@ -37629,24 +38010,24 @@ var SessionManager = class {
37629
38010
  constructor(config2) {
37630
38011
  this.config = config2;
37631
38012
  this.scopeConfig = config2.scopeConfig;
37632
- this.sessionsDir = path42.join(config2.stateDir, "agents", "main", "sessions");
37633
- this.indexPath = path42.join(this.sessionsDir, "session-index.json");
37634
- this.platformMapPath = path42.join(this.sessionsDir, "platform-map.json");
37635
- fs42.mkdirSync(this.sessionsDir, { recursive: true });
38013
+ this.sessionsDir = path43.join(config2.stateDir, "agents", "main", "sessions");
38014
+ this.indexPath = path43.join(this.sessionsDir, "session-index.json");
38015
+ this.platformMapPath = path43.join(this.sessionsDir, "platform-map.json");
38016
+ fs43.mkdirSync(this.sessionsDir, { recursive: true });
37636
38017
  }
37637
38018
  // === 索引操作 ===
37638
38019
  loadIndex() {
37639
38020
  try {
37640
- if (fs42.existsSync(this.indexPath)) {
37641
- const raw = fs42.readFileSync(this.indexPath, "utf-8");
38021
+ if (fs43.existsSync(this.indexPath)) {
38022
+ const raw = fs43.readFileSync(this.indexPath, "utf-8");
37642
38023
  const obj = JSON.parse(raw);
37643
38024
  this.index = new Map(Object.entries(obj));
37644
38025
  let fixed = 0;
37645
38026
  for (const [key, entry] of this.index) {
37646
- if (fs42.existsSync(entry.file)) continue;
37647
- const basename10 = path42.basename(entry.file);
37648
- const localPath = path42.join(this.sessionsDir, basename10);
37649
- if (fs42.existsSync(localPath)) {
38027
+ if (fs43.existsSync(entry.file)) continue;
38028
+ const basename10 = path43.basename(entry.file);
38029
+ const localPath = path43.join(this.sessionsDir, basename10);
38030
+ if (fs43.existsSync(localPath)) {
37650
38031
  entry.file = localPath;
37651
38032
  fixed++;
37652
38033
  }
@@ -37665,7 +38046,7 @@ var SessionManager = class {
37665
38046
  try {
37666
38047
  const obj = {};
37667
38048
  for (const [k, v] of this.index) obj[k] = v;
37668
- fs42.writeFileSync(this.indexPath, JSON.stringify(obj, null, 2));
38049
+ fs43.writeFileSync(this.indexPath, JSON.stringify(obj, null, 2));
37669
38050
  } catch (err) {
37670
38051
  console.error(`[session] Failed to save index: ${err.message}`);
37671
38052
  }
@@ -37673,8 +38054,8 @@ var SessionManager = class {
37673
38054
  // === 平台映射 ===
37674
38055
  loadPlatformMap() {
37675
38056
  try {
37676
- if (fs42.existsSync(this.platformMapPath)) {
37677
- const raw = fs42.readFileSync(this.platformMapPath, "utf-8");
38057
+ if (fs43.existsSync(this.platformMapPath)) {
38058
+ const raw = fs43.readFileSync(this.platformMapPath, "utf-8");
37678
38059
  const obj = JSON.parse(raw);
37679
38060
  this.platformMap = new Map(Object.entries(obj));
37680
38061
  console.log(`[session] Platform map loaded: ${this.platformMap.size} entries`);
@@ -37686,7 +38067,7 @@ var SessionManager = class {
37686
38067
  try {
37687
38068
  const obj = {};
37688
38069
  for (const [k, v] of this.platformMap) obj[k] = v;
37689
- fs42.writeFileSync(this.platformMapPath, JSON.stringify(obj, null, 2));
38070
+ fs43.writeFileSync(this.platformMapPath, JSON.stringify(obj, null, 2));
37690
38071
  } catch (err) {
37691
38072
  console.error(`[session] Failed to save platform map: ${err.message}`);
37692
38073
  }
@@ -37725,17 +38106,17 @@ var SessionManager = class {
37725
38106
  */
37726
38107
  getSessionFilePath(sessionId) {
37727
38108
  const entry = this.index.get(sessionId);
37728
- if (entry && fs42.existsSync(entry.file)) return entry.file;
37729
- const direct = path42.join(this.sessionsDir, `${sessionId}.jsonl`);
37730
- return fs42.existsSync(direct) ? direct : null;
38109
+ if (entry && fs43.existsSync(entry.file)) return entry.file;
38110
+ const direct = path43.join(this.sessionsDir, `${sessionId}.jsonl`);
38111
+ return fs43.existsSync(direct) ? direct : null;
37731
38112
  }
37732
38113
  // === Writer 管理 ===
37733
38114
  getWriter(sessionId) {
37734
38115
  let writer = this.writers.get(sessionId);
37735
38116
  if (!writer) {
37736
38117
  const existing = this.index.get(sessionId);
37737
- if (existing && fs42.existsSync(existing.file)) {
37738
- const existingFileId = path42.basename(existing.file, ".jsonl");
38118
+ if (existing && fs43.existsSync(existing.file)) {
38119
+ const existingFileId = path43.basename(existing.file, ".jsonl");
37739
38120
  writer = new SessionWriter(this.sessionsDir, existingFileId);
37740
38121
  writer.open(this.config.workspace, sessionId);
37741
38122
  } else {
@@ -37747,7 +38128,7 @@ var SessionManager = class {
37747
38128
  this.saveIndex();
37748
38129
  this.writers.set(sessionId, writer);
37749
38130
  this.onWriterCreated?.(writer, sessionId);
37750
- console.log(`[session] ${existing ? "Resumed" : "New"}: ${sessionId} \u2192 ${path42.basename(writer.path)}`);
38131
+ console.log(`[session] ${existing ? "Resumed" : "New"}: ${sessionId} \u2192 ${path43.basename(writer.path)}`);
37751
38132
  }
37752
38133
  return writer;
37753
38134
  }
@@ -37766,7 +38147,7 @@ var SessionManager = class {
37766
38147
  if (!writer) return null;
37767
38148
  try {
37768
38149
  this.lastArchiveCheck = Date.now();
37769
- const stat4 = fs42.statSync(writer.path);
38150
+ const stat4 = fs43.statSync(writer.path);
37770
38151
  if (stat4.size < threshold) return null;
37771
38152
  const sizeMB = (stat4.size / 1024 / 1024).toFixed(1);
37772
38153
  const thresholdMB = (threshold / 1024 / 1024).toFixed(1);
@@ -37775,10 +38156,10 @@ var SessionManager = class {
37775
38156
  if (archivedPath) {
37776
38157
  this.index.set(sessionId, { file: writer.path, updatedAt: Date.now() });
37777
38158
  this.saveIndex();
37778
- const newSize = fs42.statSync(writer.path).size;
38159
+ const newSize = fs43.statSync(writer.path).size;
37779
38160
  const oldSizeMB = (stat4.size / 1024 / 1024).toFixed(1);
37780
- console.log(`[session] Archive complete: ${path42.basename(archivedPath)} (${oldSizeMB}MB) \u2192 new JSONL created (${newSize} bytes)`);
37781
- const fileId = path42.basename(writer.path).replace(/\.jsonl$/, "");
38161
+ console.log(`[session] Archive complete: ${path43.basename(archivedPath)} (${oldSizeMB}MB) \u2192 new JSONL created (${newSize} bytes)`);
38162
+ const fileId = path43.basename(writer.path).replace(/\.jsonl$/, "");
37782
38163
  this.purgeOldArchives(fileId, 3);
37783
38164
  return archivedPath;
37784
38165
  }
@@ -37794,15 +38175,15 @@ var SessionManager = class {
37794
38175
  purgeOldArchives(sessionId, keepCount = 3) {
37795
38176
  try {
37796
38177
  const dir = this.sessionsDir;
37797
- if (!fs42.existsSync(dir)) return;
37798
- const allFiles = fs42.readdirSync(dir);
38178
+ if (!fs43.existsSync(dir)) return;
38179
+ const allFiles = fs43.readdirSync(dir);
37799
38180
  const archiveFiles = allFiles.filter((f) => {
37800
38181
  if (!f.startsWith(sessionId)) return false;
37801
38182
  return f.includes(".archived.") || f.includes(".compaction.");
37802
38183
  }).map((f) => ({
37803
38184
  name: f,
37804
- path: path42.join(dir, f),
37805
- mtime: fs42.statSync(path42.join(dir, f)).mtimeMs
38185
+ path: path43.join(dir, f),
38186
+ mtime: fs43.statSync(path43.join(dir, f)).mtimeMs
37806
38187
  })).sort((a, b) => b.mtime - a.mtime);
37807
38188
  console.log(`[session] purgeOldArchives: sessionId=${sessionId.substring(0, 8)}... found=${archiveFiles.length} keep=${keepCount}`);
37808
38189
  if (archiveFiles.length <= keepCount) return;
@@ -37812,7 +38193,7 @@ var SessionManager = class {
37812
38193
  console.log(`[session] Skipping archived (kept for history): ${f.name}`);
37813
38194
  continue;
37814
38195
  }
37815
- fs42.unlinkSync(f.path);
38196
+ fs43.unlinkSync(f.path);
37816
38197
  console.log(`[session] Purged old archive: ${f.name}`);
37817
38198
  }
37818
38199
  if (toDelete.length > 0) {
@@ -37861,29 +38242,29 @@ var SessionManager = class {
37861
38242
  /** 列出 session 的所有文件(当前 + 归档),按修改时间排序 */
37862
38243
  findAllSessionFiles(sessionId) {
37863
38244
  const results = [];
37864
- const mainFile = path42.join(this.sessionsDir, `${sessionId}.jsonl`);
37865
- if (fs42.existsSync(mainFile)) {
38245
+ const mainFile = path43.join(this.sessionsDir, `${sessionId}.jsonl`);
38246
+ if (fs43.existsSync(mainFile)) {
37866
38247
  results.push(mainFile);
37867
38248
  }
37868
- if (fs42.existsSync(this.sessionsDir)) {
37869
- const compactionFiles = fs42.readdirSync(this.sessionsDir).filter((f) => f.startsWith(`${sessionId}.jsonl.compaction.`)).map((f) => ({
38249
+ if (fs43.existsSync(this.sessionsDir)) {
38250
+ const compactionFiles = fs43.readdirSync(this.sessionsDir).filter((f) => f.startsWith(`${sessionId}.jsonl.compaction.`)).map((f) => ({
37870
38251
  name: f,
37871
- path: path42.join(this.sessionsDir, f),
37872
- mtime: fs42.statSync(path42.join(this.sessionsDir, f)).mtimeMs
38252
+ path: path43.join(this.sessionsDir, f),
38253
+ mtime: fs43.statSync(path43.join(this.sessionsDir, f)).mtimeMs
37873
38254
  })).sort((a, b) => b.mtime - a.mtime);
37874
38255
  if (compactionFiles.length > 0) {
37875
38256
  results.push(compactionFiles[0].path);
37876
38257
  }
37877
38258
  }
37878
38259
  results.sort((a, b) => {
37879
- const ta = fs42.statSync(a).mtimeMs;
37880
- const tb = fs42.statSync(b).mtimeMs;
38260
+ const ta = fs43.statSync(a).mtimeMs;
38261
+ const tb = fs43.statSync(b).mtimeMs;
37881
38262
  return ta - tb;
37882
38263
  });
37883
38264
  return results;
37884
38265
  }
37885
38266
  async findSessionFiles(sessionId) {
37886
- if (!fs42.existsSync(this.sessionsDir)) {
38267
+ if (!fs43.existsSync(this.sessionsDir)) {
37887
38268
  console.log(`[session:find] SESSIONS_DIR not found: ${this.sessionsDir}`);
37888
38269
  return [];
37889
38270
  }
@@ -37895,15 +38276,15 @@ var SessionManager = class {
37895
38276
  return mappedFiles;
37896
38277
  }
37897
38278
  const indexedEntry = this.index.get(mappedId);
37898
- if (indexedEntry && fs42.existsSync(indexedEntry.file)) {
37899
- const fileUuid = path42.basename(indexedEntry.file, ".jsonl");
38279
+ if (indexedEntry && fs43.existsSync(indexedEntry.file)) {
38280
+ const fileUuid = path43.basename(indexedEntry.file, ".jsonl");
37900
38281
  return this.findAllSessionFiles(fileUuid);
37901
38282
  }
37902
38283
  }
37903
38284
  const allFiles = this.findAllSessionFiles(sessionId);
37904
38285
  if (allFiles.length > 0) {
37905
38286
  const labels = allFiles.map((f) => {
37906
- const base = path42.basename(f);
38287
+ const base = path43.basename(f);
37907
38288
  return base.endsWith(".jsonl") ? base : `${base} (archived)`;
37908
38289
  });
37909
38290
  console.log(`[session:find] Found ${allFiles.length} file(s) for ${sessionId}: ${labels.join(", ")}`);
@@ -37911,9 +38292,9 @@ var SessionManager = class {
37911
38292
  }
37912
38293
  const indexed = this.index.get(sessionId);
37913
38294
  if (indexed) {
37914
- console.log(`[session:find] Index hit: ${sessionId} \u2192 ${indexed.file} (exists=${fs42.existsSync(indexed.file)})`);
37915
- if (fs42.existsSync(indexed.file)) {
37916
- return this.findAllSessionFiles(path42.basename(indexed.file, ".jsonl"));
38295
+ console.log(`[session:find] Index hit: ${sessionId} \u2192 ${indexed.file} (exists=${fs43.existsSync(indexed.file)})`);
38296
+ if (fs43.existsSync(indexed.file)) {
38297
+ return this.findAllSessionFiles(path43.basename(indexed.file, ".jsonl"));
37917
38298
  }
37918
38299
  } else {
37919
38300
  console.log(`[session:find] Index miss: ${sessionId} (index has ${this.index.size} entries)`);
@@ -37942,8 +38323,8 @@ var SessionManager = class {
37942
38323
  return [];
37943
38324
  }
37944
38325
  files.sort((a, b) => {
37945
- const ta = fs42.statSync(a).mtimeMs;
37946
- const tb = fs42.statSync(b).mtimeMs;
38326
+ const ta = fs43.statSync(a).mtimeMs;
38327
+ const tb = fs43.statSync(b).mtimeMs;
37947
38328
  return ta - tb;
37948
38329
  });
37949
38330
  const latestFile = files[files.length - 1];
@@ -37951,7 +38332,7 @@ var SessionManager = class {
37951
38332
  if (!currentEntry || currentEntry.file !== latestFile) {
37952
38333
  this.index.set(sessionId, { file: latestFile, updatedAt: Date.now() });
37953
38334
  this.saveIndex();
37954
- console.log(`[session:restore] Synced index: ${sessionId} \u2192 ${path42.basename(latestFile)}`);
38335
+ console.log(`[session:restore] Synced index: ${sessionId} \u2192 ${path43.basename(latestFile)}`);
37955
38336
  }
37956
38337
  const allMessages = [];
37957
38338
  for (const file2 of files) {
@@ -37990,7 +38371,7 @@ var SessionManager = class {
37990
38371
  if (isAttachmentMessage(m) && m.attachment.type === "relevant_memories") {
37991
38372
  for (const mem of m.attachment.memories) {
37992
38373
  rawCount++;
37993
- restoredPaths.add(path42.resolve(mem.path));
38374
+ restoredPaths.add(path43.resolve(mem.path));
37994
38375
  }
37995
38376
  }
37996
38377
  }
@@ -38022,15 +38403,15 @@ var SessionManager = class {
38022
38403
  */
38023
38404
  rotateMainSession() {
38024
38405
  const entry = this.index.get("main");
38025
- if (!entry || !fs42.existsSync(entry.file)) return;
38026
- const oldFileId = path42.basename(entry.file, ".jsonl");
38027
- const stat4 = fs42.statSync(entry.file);
38406
+ if (!entry || !fs43.existsSync(entry.file)) return;
38407
+ const oldFileId = path43.basename(entry.file, ".jsonl");
38408
+ const stat4 = fs43.statSync(entry.file);
38028
38409
  if (stat4.size < 1024) return;
38029
38410
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
38030
38411
  const archivedPath = `${entry.file}.archived.${ts}`;
38031
- fs42.renameSync(entry.file, archivedPath);
38412
+ fs43.renameSync(entry.file, archivedPath);
38032
38413
  const sizeMB = (stat4.size / 1024 / 1024).toFixed(1);
38033
- console.log(`[session] Archived: ${path42.basename(entry.file)} \u2192 ${path42.basename(archivedPath)} (${sizeMB}MB)`);
38414
+ console.log(`[session] Archived: ${path43.basename(entry.file)} \u2192 ${path43.basename(archivedPath)} (${sizeMB}MB)`);
38034
38415
  const newWriter = new SessionWriter(this.sessionsDir);
38035
38416
  newWriter.open(this.config.workspace, "main");
38036
38417
  newWriter.writeModelChange(this.config.providerId, this.config.model);
@@ -38039,7 +38420,7 @@ var SessionManager = class {
38039
38420
  this.index.set("main", { file: newPath, updatedAt: Date.now() });
38040
38421
  this.saveIndex();
38041
38422
  this.purgeOldArchives(oldFileId, 3);
38042
- console.log(`[session] Rotated main: ${oldFileId}.jsonl \u2192 ${path42.basename(newPath)}`);
38423
+ console.log(`[session] Rotated main: ${oldFileId}.jsonl \u2192 ${path43.basename(newPath)}`);
38043
38424
  }
38044
38425
  // === 迁移 ===
38045
38426
  /** 迁移旧索引:session-index.json 里有 "discord:xxx" 格式的 key 时,生成 UUID 替换 */
@@ -38842,7 +39223,7 @@ Examples:
38842
39223
  properties: {
38843
39224
  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
39225
  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"] },
39226
+ 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
39227
  content: { type: "string", description: "\u6D88\u606F\u6B63\u6587" }
38847
39228
  },
38848
39229
  required: ["content", "to"]
@@ -38931,12 +39312,12 @@ Examples:
38931
39312
  // src/tools/msg-husband.ts
38932
39313
  init_registry();
38933
39314
  init_live();
38934
- import fs43 from "node:fs";
38935
- import path43 from "node:path";
39315
+ import fs44 from "node:fs";
39316
+ import path44 from "node:path";
38936
39317
  function getHusbandFeishuId(workspace) {
38937
- const contactsPath = path43.join(workspace, "prompts", "contacts.md");
39318
+ const contactsPath = path44.join(workspace, "prompts", "contacts.md");
38938
39319
  try {
38939
- const text = fs43.readFileSync(contactsPath, "utf-8");
39320
+ const text = fs44.readFileSync(contactsPath, "utf-8");
38940
39321
  const m = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
38941
39322
  return m ? m[1] : null;
38942
39323
  } catch {
@@ -39078,8 +39459,8 @@ Examples:
39078
39459
  if (!to && !resolvedChannelId) {
39079
39460
  return { content: "to \u548C channel_id \u4E0D\u80FD\u540C\u65F6\u4E3A\u7A7A\uFF0C\u4E14\u6CA1\u6709\u53EF\u7528\u7684\u6765\u6E90\u9891\u9053", isError: true };
39080
39461
  }
39081
- const fs58 = await import("node:fs");
39082
- if (!fs58.existsSync(filePath)) {
39462
+ const fs59 = await import("node:fs");
39463
+ if (!fs59.existsSync(filePath)) {
39083
39464
  return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6\u4E0D\u5B58\u5728 ${filePath}`, isError: true };
39084
39465
  }
39085
39466
  const toIds = to ? to.split(",").map((s) => s.trim()).filter(Boolean) : [];
@@ -39107,7 +39488,7 @@ Examples:
39107
39488
  md: "text/markdown"
39108
39489
  };
39109
39490
  const mimeType = mimeTypeMap[ext] || "application/octet-stream";
39110
- const stat4 = fs58.statSync(filePath);
39491
+ const stat4 = fs59.statSync(filePath);
39111
39492
  const sizeMB = stat4.size / 1024 / 1024;
39112
39493
  if (sizeMB > 25) {
39113
39494
  return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6 ${sizeMB.toFixed(1)}MB \u8D85\u8FC7 Discord 25MB \u9650\u5236`, isError: true };
@@ -39137,8 +39518,8 @@ Examples:
39137
39518
  init_live();
39138
39519
  init_registry();
39139
39520
  init_well_formed();
39140
- import * as fs44 from "node:fs";
39141
- import * as path44 from "node:path";
39521
+ import * as fs45 from "node:fs";
39522
+ import * as path45 from "node:path";
39142
39523
  var MIME_MAP = {
39143
39524
  ".jpg": "jpeg",
39144
39525
  ".jpeg": "jpeg",
@@ -39150,9 +39531,9 @@ var MIME_MAP = {
39150
39531
  var VIDEO_EXT = /\.(mp4|mov)$/i;
39151
39532
  var VIDEO_MAX_BYTES = 40 * 1024 * 1024;
39152
39533
  function resolveLatestImage(specifiedPath, mediaDir) {
39153
- if (specifiedPath && fs44.existsSync(specifiedPath)) return specifiedPath;
39154
- if (!fs44.existsSync(mediaDir)) return null;
39155
- const files = fs44.readdirSync(mediaDir).filter((f) => /\.(jpg|jpeg|png|webp|gif|bmp|mp4|mov)$/i.test(f)).map((f) => ({ name: f, p: path44.join(mediaDir, f), mtime: fs44.statSync(path44.join(mediaDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
39534
+ if (specifiedPath && fs45.existsSync(specifiedPath)) return specifiedPath;
39535
+ if (!fs45.existsSync(mediaDir)) return null;
39536
+ const files = fs45.readdirSync(mediaDir).filter((f) => /\.(jpg|jpeg|png|webp|gif|bmp|mp4|mov)$/i.test(f)).map((f) => ({ name: f, p: path45.join(mediaDir, f), mtime: fs45.statSync(path45.join(mediaDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
39156
39537
  return files[0]?.p || null;
39157
39538
  }
39158
39539
  registry.register({
@@ -39183,21 +39564,21 @@ registry.register({
39183
39564
  if (!provider?.streamChat) {
39184
39565
  return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
39185
39566
  }
39186
- const mediaDir = path44.join(ctx.stateDir, "media", "inbound");
39567
+ const mediaDir = path45.join(ctx.stateDir, "media", "inbound");
39187
39568
  const imagePath = resolveLatestImage(args2.image_path, mediaDir);
39188
39569
  if (!imagePath) {
39189
39570
  return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
39190
39571
  }
39191
39572
  const rawPrompt = args2.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
39192
- const ext = path44.extname(imagePath).toLowerCase();
39573
+ const ext = path45.extname(imagePath).toLowerCase();
39193
39574
  if (VIDEO_EXT.test(imagePath)) {
39194
- const stat4 = fs44.statSync(imagePath);
39575
+ const stat4 = fs45.statSync(imagePath);
39195
39576
  if (stat4.size > VIDEO_MAX_BYTES) {
39196
39577
  return { content: `Error: video too large (${(stat4.size / 1e6).toFixed(1)}MB > 40MB). Compress or trim first.`, isError: true };
39197
39578
  }
39198
39579
  const explicit = liveConfig.get("tools.my_eyes.videoBaseUrl");
39199
39580
  const baseUrl = explicit || providerCfg.baseUrl.replace(/\/anthropic\/?$/, "/v1").replace(/\/$/, "");
39200
- const vidB64 = fs44.readFileSync(imagePath).toString("base64");
39581
+ const vidB64 = fs45.readFileSync(imagePath).toString("base64");
39201
39582
  try {
39202
39583
  const res = await fetch(`${baseUrl}/chat/completions`, {
39203
39584
  method: "POST",
@@ -39227,7 +39608,7 @@ registry.register({
39227
39608
  }
39228
39609
  }
39229
39610
  const mime = MIME_MAP[ext] || "jpeg";
39230
- const imgB64 = fs44.readFileSync(imagePath).toString("base64");
39611
+ const imgB64 = fs45.readFileSync(imagePath).toString("base64");
39231
39612
  const userMsg = {
39232
39613
  role: "user",
39233
39614
  content: [
@@ -39305,7 +39686,7 @@ var TTS_PROVIDERS = [
39305
39686
  init_registry();
39306
39687
 
39307
39688
  // src/tools/opus-encoder.ts
39308
- import * as fs45 from "node:fs";
39689
+ import * as fs46 from "node:fs";
39309
39690
  var FRAME_MS = 20;
39310
39691
  var PRESKIP_48K = 312;
39311
39692
  var OPUS_RATE = 48e3;
@@ -39422,7 +39803,7 @@ async function wavToOggOpusNative(wavPath) {
39422
39803
  const mod = await import("@discordjs/opus");
39423
39804
  const OpusEncoder = mod.OpusEncoder ?? mod.default?.OpusEncoder;
39424
39805
  if (!OpusEncoder) throw new Error("@discordjs/opus export shape unexpected");
39425
- const wav = parseWav(fs45.readFileSync(wavPath));
39806
+ const wav = parseWav(fs46.readFileSync(wavPath));
39426
39807
  if (wav.channels !== 1) throw new Error(`unsupported ${wav.channels}ch (need mono)`);
39427
39808
  if (!NATIVE_RATES.includes(wav.sampleRate)) throw new Error(`unsupported rate ${wav.sampleRate}`);
39428
39809
  if (wav.pcm.length === 0) throw new Error("empty pcm data");
@@ -39458,7 +39839,7 @@ async function wavToOggOpusNative(wavPath) {
39458
39839
  }
39459
39840
  ogg.page(cur, pageGranule, false, true);
39460
39841
  const oggPath = wavPath.replace(/\.wav$/, ".ogg");
39461
- fs45.writeFileSync(oggPath, ogg.finish());
39842
+ fs46.writeFileSync(oggPath, ogg.finish());
39462
39843
  return oggPath;
39463
39844
  }
39464
39845
 
@@ -39487,10 +39868,10 @@ function fallbackChainOf(raw) {
39487
39868
  init_live();
39488
39869
 
39489
39870
  // src/tools/tts/relay.ts
39490
- import * as fs46 from "node:fs";
39871
+ import * as fs47 from "node:fs";
39491
39872
  import * as os6 from "node:os";
39492
- import * as path45 from "node:path";
39493
- var VOICE_DIR2 = path45.join(os6.tmpdir(), "engine-voice");
39873
+ import * as path46 from "node:path";
39874
+ var VOICE_DIR2 = path46.join(os6.tmpdir(), "engine-voice");
39494
39875
  var fetchFn = fetch;
39495
39876
  async function synthRelay(text, cfg) {
39496
39877
  const res = await fetchFn(`${cfg.baseUrl}/audio/speech`, {
@@ -39509,9 +39890,9 @@ async function synthRelay(text, cfg) {
39509
39890
  }
39510
39891
  const buf = Buffer.from(await res.arrayBuffer());
39511
39892
  if (buf.length < 20) throw new Error(`relay tts: \u54CD\u5E94\u4F53\u53EA\u6709 ${buf.length} \u5B57\u8282\uFF0C\u4E0D\u662F\u6709\u6548\u97F3\u9891`);
39512
- fs46.mkdirSync(VOICE_DIR2, { recursive: true });
39513
- const out = path45.join(VOICE_DIR2, `tts_relay_${Date.now()}.mp3`);
39514
- fs46.writeFileSync(out, buf);
39893
+ fs47.mkdirSync(VOICE_DIR2, { recursive: true });
39894
+ const out = path46.join(VOICE_DIR2, `tts_relay_${Date.now()}.mp3`);
39895
+ fs47.writeFileSync(out, buf);
39515
39896
  return {
39516
39897
  path: out,
39517
39898
  format: "mp3",
@@ -39630,8 +40011,8 @@ function synthesize(id, text, sink) {
39630
40011
  // src/tools/my-voice.ts
39631
40012
  import { execFile as execFile3 } from "node:child_process";
39632
40013
  import { promisify as promisify3 } from "node:util";
39633
- import * as fs47 from "node:fs";
39634
- import * as path46 from "node:path";
40014
+ import * as fs48 from "node:fs";
40015
+ import * as path47 from "node:path";
39635
40016
  var execFileAsync3 = promisify3(execFile3);
39636
40017
  async function toWav24kWithDuration(audioPath) {
39637
40018
  let wavPath = audioPath;
@@ -39649,15 +40030,15 @@ async function toWav24kWithDuration(audioPath) {
39649
40030
  "pcm_s16le",
39650
40031
  wavPath
39651
40032
  ], { timeout: 3e4 });
39652
- if (!fs47.existsSync(wavPath) || fs47.statSync(wavPath).size < 44) {
40033
+ if (!fs48.existsSync(wavPath) || fs48.statSync(wavPath).size < 44) {
39653
40034
  throw new Error("toWav24k produced empty output");
39654
40035
  }
39655
40036
  try {
39656
- fs47.unlinkSync(audioPath);
40037
+ fs48.unlinkSync(audioPath);
39657
40038
  } catch {
39658
40039
  }
39659
40040
  }
39660
- const durationSec = wavDurationSec(fs47.readFileSync(wavPath));
40041
+ const durationSec = wavDurationSec(fs48.readFileSync(wavPath));
39661
40042
  return { wavPath, durationSec };
39662
40043
  }
39663
40044
  async function compressWav(wavPath) {
@@ -39675,7 +40056,7 @@ async function compressWav(wavPath) {
39675
40056
  "+faststart",
39676
40057
  m4aPath
39677
40058
  ], { timeout: 3e4 });
39678
- fs47.unlinkSync(wavPath);
40059
+ fs48.unlinkSync(wavPath);
39679
40060
  return m4aPath;
39680
40061
  } catch {
39681
40062
  return wavPath;
@@ -39702,10 +40083,10 @@ async function wavToOggOpus(wavPath) {
39702
40083
  oggPath
39703
40084
  ], { timeout: 3e4 });
39704
40085
  }
39705
- if (!fs47.existsSync(oggPath) || fs47.statSync(oggPath).size < 20) {
40086
+ if (!fs48.existsSync(oggPath) || fs48.statSync(oggPath).size < 20) {
39706
40087
  throw new Error("ogg/opus encode produced empty output");
39707
40088
  }
39708
- fs47.unlinkSync(wavPath);
40089
+ fs48.unlinkSync(wavPath);
39709
40090
  return oggPath;
39710
40091
  }
39711
40092
  async function sendVoiceToMulticaIssue(text, issueId, opts) {
@@ -39720,7 +40101,7 @@ async function sendVoiceToMulticaIssue(text, issueId, opts) {
39720
40101
  } catch (e) {
39721
40102
  return { content: `\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
39722
40103
  }
39723
- if (!audioPath || !fs47.existsSync(audioPath)) {
40104
+ if (!audioPath || !fs48.existsSync(audioPath)) {
39724
40105
  return { content: "\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1ATTS \u672A\u4EA7\u51FA\u6587\u4EF6\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002", isError: true };
39725
40106
  }
39726
40107
  if (audioPath.endsWith(".wav")) {
@@ -39730,21 +40111,21 @@ async function sendVoiceToMulticaIssue(text, issueId, opts) {
39730
40111
  console.warn(`[my-voice:multaca] opus encode failed (${e.message}), attaching wav`);
39731
40112
  }
39732
40113
  }
39733
- const ext = path46.extname(audioPath).toLowerCase();
39734
- const sizeKB = fs47.statSync(audioPath).size / 1024;
40114
+ const ext = path47.extname(audioPath).toLowerCase();
40115
+ const sizeKB = fs48.statSync(audioPath).size / 1024;
39735
40116
  const voiceFile = `voice_${Date.now()}${ext}`;
39736
- const cwdCopy = path46.join(process.cwd(), voiceFile);
40117
+ const cwdCopy = path47.join(process.cwd(), voiceFile);
39737
40118
  try {
39738
- fs47.copyFileSync(audioPath, cwdCopy);
40119
+ fs48.copyFileSync(audioPath, cwdCopy);
39739
40120
  } catch (e) {
39740
40121
  try {
39741
- fs47.unlinkSync(audioPath);
40122
+ fs48.unlinkSync(audioPath);
39742
40123
  } catch {
39743
40124
  }
39744
40125
  return { content: `\u8BED\u97F3\u6302\u8F7D\u5931\u8D25\uFF08\u62F7\u8D1D\u5230\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1A${e.message}`, isError: true };
39745
40126
  }
39746
40127
  try {
39747
- fs47.unlinkSync(audioPath);
40128
+ fs48.unlinkSync(audioPath);
39748
40129
  } catch {
39749
40130
  }
39750
40131
  const content = opts.caption || `\u{1F399}\uFE0F \u8BED\u97F3\u6D88\u606F\uFF08${text.length} \u5B57\uFF09`;
@@ -39773,13 +40154,13 @@ async function sendVoiceToMulticaIssue(text, issueId, opts) {
39773
40154
  }
39774
40155
  if (!ok) throw new Error(`unexpected CLI output: ${String(stdout).slice(0, 200)}`);
39775
40156
  try {
39776
- fs47.unlinkSync(cwdCopy);
40157
+ fs48.unlinkSync(cwdCopy);
39777
40158
  } catch {
39778
40159
  }
39779
40160
  return { content: `Voice sent to multaca issue ${issueId}! (${actualEngine}, ${sizeKB.toFixed(0)}KB, \u9644\u4EF6\u5DF2\u6302\u5230\u8BC4\u8BBA\u2014\u2014\u5728 multaca \u5BA2\u6237\u7AEF\u64AD\u653E)` };
39780
40161
  } catch (e) {
39781
40162
  try {
39782
- fs47.unlinkSync(cwdCopy);
40163
+ fs48.unlinkSync(cwdCopy);
39783
40164
  } catch {
39784
40165
  }
39785
40166
  return { content: `\u8BED\u97F3\u6302\u5230 multaca issue \u5931\u8D25\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
@@ -39896,10 +40277,10 @@ registry.register({
39896
40277
  audioPath = await compressWav(audioPath);
39897
40278
  }
39898
40279
  }
39899
- const ext = path46.extname(audioPath).toLowerCase();
40280
+ const ext = path47.extname(audioPath).toLowerCase();
39900
40281
  const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg", ".silk": "audio/silk" };
39901
40282
  const mimeType = mimeMap[ext] || "audio/mpeg";
39902
- const sizeKB = fs47.statSync(audioPath).size / 1024;
40283
+ const sizeKB = fs48.statSync(audioPath).size / 1024;
39903
40284
  try {
39904
40285
  await mgr.sendFile(resolvedChannel, target, caption, {
39905
40286
  path: audioPath,
@@ -39908,7 +40289,7 @@ registry.register({
39908
40289
  ...voiceDurationSec !== void 0 ? { voiceDurationSec } : {}
39909
40290
  });
39910
40291
  try {
39911
- fs47.unlinkSync(audioPath);
40292
+ fs48.unlinkSync(audioPath);
39912
40293
  } catch {
39913
40294
  }
39914
40295
  return { content: `Voice sent! (${actualEngine}, ${sizeKB.toFixed(0)}KB, ${resolvedChannel})` };
@@ -39926,8 +40307,8 @@ registry.register({
39926
40307
  init_live();
39927
40308
  init_registry();
39928
40309
  init_well_formed();
39929
- import * as fs48 from "node:fs";
39930
- import * as path47 from "node:path";
40310
+ import * as fs49 from "node:fs";
40311
+ import * as path48 from "node:path";
39931
40312
  function getProxyDispatcher2() {
39932
40313
  const cfg = liveConfig.all();
39933
40314
  const proxy = cfg.providers?.xai?.proxy;
@@ -39941,7 +40322,7 @@ function getProxyDispatcher2() {
39941
40322
  }
39942
40323
  function dimsOf(p) {
39943
40324
  try {
39944
- const b = fs48.readFileSync(p);
40325
+ const b = fs49.readFileSync(p);
39945
40326
  let w = 0, h = 0;
39946
40327
  if (b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
39947
40328
  w = b.readUInt32BE(16);
@@ -40064,9 +40445,9 @@ function registerPortrait(relPath, name) {
40064
40445
  const cur = cfg?.tools?.my_selfie?.references || cfg?.my_selfie?.references || [];
40065
40446
  const next = Array.isArray(cur) ? cur.filter((r) => r?.name !== name) : [];
40066
40447
  next.unshift({ name, path: relPath });
40067
- const path60 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
40068
- void liveConfig.set(path60, next);
40069
- console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path60}[0] = ${relPath}`);
40448
+ const path61 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
40449
+ void liveConfig.set(path61, next);
40450
+ console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path61}[0] = ${relPath}`);
40070
40451
  } catch (e) {
40071
40452
  console.warn(`[my-selfie] \u5199\u5165 references \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u51FA\u56FE\uFF09: ${e.message}`);
40072
40453
  }
@@ -40347,10 +40728,10 @@ registry.register({
40347
40728
  const REFERENCES = getReferences(ctx);
40348
40729
  const refName = args2.reference || "default";
40349
40730
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
40350
- let refPath = path47.join(ctx.workspace, refEntry.p);
40731
+ let refPath = path48.join(ctx.workspace, refEntry.p);
40351
40732
  const adoptImg = args2.referenceImage?.trim();
40352
40733
  if (adoptImg) {
40353
- if (!fs48.existsSync(adoptImg)) {
40734
+ if (!fs49.existsSync(adoptImg)) {
40354
40735
  return { content: `\u627E\u4E0D\u5230\u90A3\u5F20\u56FE\uFF1A${adoptImg}`, isError: true };
40355
40736
  }
40356
40737
  const acheck = dimsOf(adoptImg);
@@ -40361,16 +40742,16 @@ registry.register({
40361
40742
  isError: true
40362
40743
  };
40363
40744
  }
40364
- const adir = path47.join(ctx.workspace, "images");
40365
- if (!fs48.existsSync(adir)) fs48.mkdirSync(adir, { recursive: true });
40366
- const aext = (path47.extname(adoptImg) || ".png").toLowerCase();
40745
+ const adir = path48.join(ctx.workspace, "images");
40746
+ if (!fs49.existsSync(adir)) fs49.mkdirSync(adir, { recursive: true });
40747
+ const aext = (path48.extname(adoptImg) || ".png").toLowerCase();
40367
40748
  const afname = `portrait_${Date.now()}${aext}`;
40368
- fs48.copyFileSync(adoptImg, path47.join(adir, afname));
40749
+ fs49.copyFileSync(adoptImg, path48.join(adir, afname));
40369
40750
  registerPortrait(`images/${afname}`, refName);
40370
- refPath = path47.join(adir, afname);
40751
+ refPath = path48.join(adir, afname);
40371
40752
  console.log(`[my-selfie] \u91C7\u7EB3\u73B0\u6210\u56FE\u5F53\u5F62\u8C61\uFF1Aimages/${afname}${acheck ? ` (${acheck})` : ""}`);
40372
40753
  }
40373
- if (provider !== "autodl" && !fs48.existsSync(refPath)) {
40754
+ if (provider !== "autodl" && !fs49.existsSync(refPath)) {
40374
40755
  const cfgAll = liveConfig.all();
40375
40756
  const portraitDesc = resolvePortraitDesc(
40376
40757
  args2.portrait,
@@ -40395,16 +40776,16 @@ registry.register({
40395
40776
  } catch (e) {
40396
40777
  return { content: `\u5851\u9020\u5F62\u8C61\u5931\u8D25\uFF1A${e.message}`, isError: true };
40397
40778
  }
40398
- const pdir = path47.join(ctx.workspace, "images");
40399
- if (!fs48.existsSync(pdir)) fs48.mkdirSync(pdir, { recursive: true });
40779
+ const pdir = path48.join(ctx.workspace, "images");
40780
+ if (!fs49.existsSync(pdir)) fs49.mkdirSync(pdir, { recursive: true });
40400
40781
  const pfname = `portrait_${Date.now()}.png`;
40401
- fs48.writeFileSync(path47.join(pdir, pfname), portraitBuf);
40782
+ fs49.writeFileSync(path48.join(pdir, pfname), portraitBuf);
40402
40783
  registerPortrait(`images/${pfname}`, refName);
40403
- refPath = path47.join(pdir, pfname);
40784
+ refPath = path48.join(pdir, pfname);
40404
40785
  console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u5B58 images/${pfname}\uFF0C\u7EE7\u7EED\u8FD9\u6B21\u81EA\u62CD`);
40405
40786
  }
40406
40787
  const resolution = args2.resolution || DEFAULT_RESOLUTION;
40407
- const refB64 = fs48.existsSync(refPath) ? fs48.readFileSync(refPath).toString("base64") : "";
40788
+ const refB64 = fs49.existsSync(refPath) ? fs49.readFileSync(refPath).toString("base64") : "";
40408
40789
  let imageBuffer;
40409
40790
  try {
40410
40791
  if (provider === "autodl") {
@@ -40420,7 +40801,7 @@ registry.register({
40420
40801
  return { content: `Error: relay provider \u672A\u914D\u7F6E\u3002config \u91CC\u8981\u6709 models.providers.${cfgAll?.my_selfie?.relayProviderId || "7yi"} \u7684 baseUrl \u548C apiKey\u3002`, isError: true };
40421
40802
  }
40422
40803
  const model = cfgAll?.my_selfie?.relayModel || cfgAll?.tools?.my_selfie?.relayModel || "engine7-img-free";
40423
- const ext = path47.extname(refPath).toLowerCase();
40804
+ const ext = path48.extname(refPath).toLowerCase();
40424
40805
  const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
40425
40806
  imageBuffer = await generateWithRelay(refB64, fullPrompt, mime, relay.baseUrl, relay.apiKey, model);
40426
40807
  } else {
@@ -40429,11 +40810,11 @@ registry.register({
40429
40810
  } catch (err) {
40430
40811
  return { content: `Selfie generation failed: ${err.message}`, isError: true };
40431
40812
  }
40432
- const imagesDir = path47.join(ctx.workspace, "images");
40433
- if (!fs48.existsSync(imagesDir)) fs48.mkdirSync(imagesDir, { recursive: true });
40813
+ const imagesDir = path48.join(ctx.workspace, "images");
40814
+ if (!fs49.existsSync(imagesDir)) fs49.mkdirSync(imagesDir, { recursive: true });
40434
40815
  const filename = `selfie_${Date.now()}.jpg`;
40435
- const outputPath = path47.join(imagesDir, filename);
40436
- fs48.writeFileSync(outputPath, imageBuffer);
40816
+ const outputPath = path48.join(imagesDir, filename);
40817
+ fs49.writeFileSync(outputPath, imageBuffer);
40437
40818
  const mgr = ctx.channelManager;
40438
40819
  if (mgr) {
40439
40820
  const resolvedChannel = ctx.channel || "feishu";
@@ -40444,11 +40825,11 @@ registry.register({
40444
40825
  mimeType: "image/jpeg"
40445
40826
  });
40446
40827
  } catch (err) {
40447
- return { content: `Selfie generated but send failed: ${err.message}. Image: ${path47.resolve(outputPath)}`, isError: false };
40828
+ return { content: `Selfie generated but send failed: ${err.message}. Image: ${path48.resolve(outputPath)}`, isError: false };
40448
40829
  }
40449
40830
  return { content: `Selfie sent! Mode: ${mode}, Provider: ${provider}, Ref: ${refEntry.name}` };
40450
40831
  }
40451
- return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path47.resolve(outputPath)}` };
40832
+ return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path48.resolve(outputPath)}` };
40452
40833
  },
40453
40834
  isConcurrencySafe: () => false,
40454
40835
  interruptBehavior: () => "block",
@@ -41000,8 +41381,8 @@ ${lines.join("\n")}` };
41000
41381
  init_registry();
41001
41382
  init_live();
41002
41383
  init_clock();
41003
- import * as fs49 from "node:fs";
41004
- import * as path48 from "node:path";
41384
+ import * as fs50 from "node:fs";
41385
+ import * as path49 from "node:path";
41005
41386
  import { exec as exec2, spawn as spawn6 } from "node:child_process";
41006
41387
  import { promisify as promisify4 } from "node:util";
41007
41388
  var execAsync = promisify4(exec2);
@@ -41010,9 +41391,9 @@ function getServices() {
41010
41391
  }
41011
41392
  function serviceLogPath(fileName) {
41012
41393
  const stateDir = String(liveConfig.get("stateDir") ?? "");
41013
- const logDir = path48.join(stateDir, "logs");
41014
- if (!fs49.existsSync(logDir)) fs49.mkdirSync(logDir, { recursive: true });
41015
- return path48.join(logDir, fileName);
41394
+ const logDir = path49.join(stateDir, "logs");
41395
+ if (!fs50.existsSync(logDir)) fs50.mkdirSync(logDir, { recursive: true });
41396
+ return path49.join(logDir, fileName);
41016
41397
  }
41017
41398
  async function runCmd(cmd, timeoutMs = 15e3) {
41018
41399
  try {
@@ -41331,16 +41712,16 @@ var EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
41331
41712
  init_planModeState();
41332
41713
 
41333
41714
  // src/utils/plans.ts
41334
- import * as fs50 from "node:fs";
41335
- import * as path49 from "node:path";
41715
+ import * as fs51 from "node:fs";
41716
+ import * as path50 from "node:path";
41336
41717
  import * as crypto7 from "node:crypto";
41337
41718
  var MAX_SLUG_RETRIES = 10;
41338
41719
  function generateSlug() {
41339
41720
  return crypto7.randomBytes(4).toString("hex");
41340
41721
  }
41341
41722
  function getPlansDirectory(stateDir) {
41342
- const plansDir = path49.join(stateDir, "plans");
41343
- fs50.mkdirSync(plansDir, { recursive: true });
41723
+ const plansDir = path50.join(stateDir, "plans");
41724
+ fs51.mkdirSync(plansDir, { recursive: true });
41344
41725
  return plansDir;
41345
41726
  }
41346
41727
  var planSlugCache = /* @__PURE__ */ new Map();
@@ -41350,8 +41731,8 @@ function getPlanSlug(sessionId, stateDir) {
41350
41731
  const plansDir = getPlansDirectory(stateDir);
41351
41732
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
41352
41733
  slug = generateSlug();
41353
- const filePath = path49.join(plansDir, `${slug}.md`);
41354
- if (!fs50.existsSync(filePath)) {
41734
+ const filePath = path50.join(plansDir, `${slug}.md`);
41735
+ if (!fs51.existsSync(filePath)) {
41355
41736
  break;
41356
41737
  }
41357
41738
  }
@@ -41362,21 +41743,21 @@ function getPlanSlug(sessionId, stateDir) {
41362
41743
  function getPlanFilePath(sessionId, stateDir, agentId) {
41363
41744
  const slug = getPlanSlug(sessionId, stateDir);
41364
41745
  if (!agentId) {
41365
- return path49.join(getPlansDirectory(stateDir), `${slug}.md`);
41746
+ return path50.join(getPlansDirectory(stateDir), `${slug}.md`);
41366
41747
  }
41367
- return path49.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
41748
+ return path50.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
41368
41749
  }
41369
41750
  function getPlan(sessionId, stateDir, agentId) {
41370
41751
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
41371
41752
  try {
41372
- return fs50.readFileSync(filePath, "utf-8");
41753
+ return fs51.readFileSync(filePath, "utf-8");
41373
41754
  } catch {
41374
41755
  return null;
41375
41756
  }
41376
41757
  }
41377
41758
  function writePlan(sessionId, stateDir, content, agentId) {
41378
41759
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
41379
- fs50.writeFileSync(filePath, content, "utf-8");
41760
+ fs51.writeFileSync(filePath, content, "utf-8");
41380
41761
  return filePath;
41381
41762
  }
41382
41763
 
@@ -42017,8 +42398,8 @@ init_features2();
42017
42398
  init_agent_registry();
42018
42399
 
42019
42400
  // src/commands/work.ts
42020
- import * as fs55 from "node:fs";
42021
- import * as path55 from "node:path";
42401
+ import * as fs56 from "node:fs";
42402
+ import * as path56 from "node:path";
42022
42403
  registerCommand({
42023
42404
  name: "work",
42024
42405
  description: "\u5DE5\u4F5C\u6A21\u5F0F\uFF1A\u65E0\u53C2\u770B\u72B6\u6001\uFF0Con=\u5F3A\u5236work\uFF0Coff=\u5F3A\u5236emotion\uFF0Cauto=\u81EA\u52A8\u5224\u5B9A",
@@ -42031,20 +42412,20 @@ registerCommand({
42031
42412
  ],
42032
42413
  handler: async (ctx, deps) => {
42033
42414
  const mode = (ctx.args.mode || "").trim().toLowerCase();
42034
- const modeFile = path55.join(deps.config.workspace, ".work-mode");
42415
+ const modeFile = path56.join(deps.config.workspace, ".work-mode");
42035
42416
  if (mode === "on" || mode === "off") {
42036
- fs55.writeFileSync(modeFile, mode, "utf8");
42417
+ fs56.writeFileSync(modeFile, mode, "utf8");
42037
42418
  await ctx.reply(mode === "on" ? "\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u5F3A\u5236 **work**\uFF08\u6240\u6709\u6D88\u606F\u5168\u91CF\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6761\u751F\u6548\uFF09" : "\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u5F3A\u5236 **emotion**\uFF08\u6240\u6709\u6D88\u606F\u8F7B\u91CF\u95F2\u804A\uFF0C\u4E0B\u4E00\u6761\u751F\u6548\uFF09");
42038
42419
  } else if (mode === "auto") {
42039
42420
  try {
42040
- fs55.unlinkSync(modeFile);
42421
+ fs56.unlinkSync(modeFile);
42041
42422
  } catch {
42042
42423
  }
42043
42424
  await ctx.reply("\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u6062\u590D **auto**\uFF08emotion_state_machine \u9010\u6761\u81EA\u52A8\u5224\u5B9A\uFF09");
42044
42425
  } else {
42045
42426
  let cur = "auto";
42046
42427
  try {
42047
- cur = fs55.readFileSync(modeFile, "utf8").trim() || "auto";
42428
+ cur = fs56.readFileSync(modeFile, "utf8").trim() || "auto";
42048
42429
  } catch {
42049
42430
  }
42050
42431
  const hint = cur === "on" ? "\uFF08\u5F3A\u5236 work\uFF1A\u5168\u91CF\u4E0A\u4E0B\u6587\uFF09" : cur === "off" ? "\uFF08\u5F3A\u5236 emotion\uFF1A\u8F7B\u91CF\u4E0A\u4E0B\u6587\uFF09" : "\uFF08\u6BCF\u6761\u6D88\u606F\u7531 emotion_state_machine \u81EA\u52A8\u5224\u5B9A\uFF09";
@@ -42514,8 +42895,8 @@ ${err.stack}`);
42514
42895
  });
42515
42896
 
42516
42897
  // src/commands/model.ts
42517
- import * as fs56 from "node:fs";
42518
- import * as path56 from "node:path";
42898
+ import * as fs57 from "node:fs";
42899
+ import * as path57 from "node:path";
42519
42900
  import { fileURLToPath as fileURLToPath8 } from "node:url";
42520
42901
  function collectModels(config2) {
42521
42902
  const aliases = config2.modelAliases || {};
@@ -42547,11 +42928,11 @@ function resolveConfigWritePath(config2, label) {
42547
42928
  const configPath2 = config2._configFilePath;
42548
42929
  if (!configPath2) return void 0;
42549
42930
  let writePath = configPath2;
42550
- if (configPath2 && !fs56.existsSync(configPath2)) {
42931
+ if (configPath2 && !fs57.existsSync(configPath2)) {
42551
42932
  const __pFile = fileURLToPath8(import.meta.url);
42552
- const __pDir = path56.dirname(__pFile);
42553
- const altPath = path56.join(path56.resolve(__pDir, "../configs"), path56.basename(configPath2));
42554
- if (fs56.existsSync(altPath)) {
42933
+ const __pDir = path57.dirname(__pFile);
42934
+ const altPath = path57.join(path57.resolve(__pDir, "../configs"), path57.basename(configPath2));
42935
+ if (fs57.existsSync(altPath)) {
42555
42936
  console.warn(`[${label}] Config not found at ${configPath2}, falling back to ${altPath}`);
42556
42937
  writePath = altPath;
42557
42938
  }
@@ -42706,14 +43087,14 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
42706
43087
  return;
42707
43088
  }
42708
43089
  try {
42709
- const raw = await fs56.promises.readFile(writePath, "utf-8");
43090
+ const raw = await fs57.promises.readFile(writePath, "utf-8");
42710
43091
  const cfg = JSON.parse(raw);
42711
43092
  if (!cfg.agents?.defaults?.model) {
42712
43093
  await ctx.reply(`\u26A0\uFE0F Config structure mismatch: agents.defaults.model not found`);
42713
43094
  return;
42714
43095
  }
42715
43096
  cfg.agents.defaults.model.primary = target;
42716
- await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
43097
+ await fs57.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42717
43098
  console.log(`[primary] Persisted primary=${target} to ${writePath}`);
42718
43099
  await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates[0].name})
42719
43100
  Written to config, hot-reloading provider chain now.`);
@@ -42760,7 +43141,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
42760
43141
  return;
42761
43142
  }
42762
43143
  try {
42763
- const raw = await fs56.promises.readFile(writePath, "utf-8");
43144
+ const raw = await fs57.promises.readFile(writePath, "utf-8");
42764
43145
  const cfg = JSON.parse(raw);
42765
43146
  if (!cfg.agents?.defaults?.model) {
42766
43147
  await ctx.reply(`\u26A0\uFE0F Config structure mismatch: agents.defaults.model not found`);
@@ -42768,12 +43149,12 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
42768
43149
  }
42769
43150
  if (input === "auto" || input === "reset") {
42770
43151
  delete cfg.agents.defaults.model.vision;
42771
- await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
43152
+ await fs57.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42772
43153
  console.log(`[vision-primary] Cleared vision primary in ${writePath}`);
42773
43154
  await ctx.reply(`\u2705 Vision primary cleared (auto). Written to config. Hot-reload will apply.`);
42774
43155
  } else {
42775
43156
  cfg.agents.defaults.model.vision = target;
42776
- await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
43157
+ await fs57.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
42777
43158
  console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
42778
43159
  await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates[0].name})
42779
43160
  Written to config. Hot-reload will apply.`);
@@ -43181,7 +43562,7 @@ async function startEngine(config2, opts) {
43181
43562
  const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
43182
43563
  initSessionMemory2({
43183
43564
  workspace: config2.workspace,
43184
- stateDir: path59.join(config2.stateDir, "session-memory"),
43565
+ stateDir: path60.join(config2.stateDir, "session-memory"),
43185
43566
  provider,
43186
43567
  model: config2.provider.modelId || config2.model || "deepseek-v4-flash",
43187
43568
  features: config2.profile.features
@@ -43757,10 +44138,12 @@ if (args[0] === "features") {
43757
44138
  const status = getLicenseStatus2(stateDir);
43758
44139
  const allFeatures = listFeatures2();
43759
44140
  console.log("");
43760
- if (status.licensed) {
44141
+ if (status.licenseId) {
43761
44142
  console.log(`\u{1F4C4} License: ${status.licenseId} | ${status.customer} | \u5230\u671F: ${status.expiresAt}`);
44143
+ } else if (status.openMode) {
44144
+ console.log("\u{1F4C4} License: \u65E0 \u2014\u2014 **\u5F00\u653E\u6A21\u5F0F**\uFF0C\u5168\u90E8 feature \u6FC0\u6D3B");
43762
44145
  } else {
43763
- console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F)");
44146
+ console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F \u2014 \u914D\u4E86 devMode=false)");
43764
44147
  }
43765
44148
  console.log("");
43766
44149
  for (const f of allFeatures) {
@@ -43773,15 +44156,10 @@ if (args[0] === "features") {
43773
44156
  } else if (info && !info.active) {
43774
44157
  console.log(`\u23F0 ${f.name} \u2014 \u5DF2\u8FC7\u671F${info.expiresAt ? ` (${info.expiresAt})` : ""}`);
43775
44158
  } else {
43776
- console.log(`\u274C ${f.name} \u2014 \u672A\u5F00\u901A (\u8054\u7CFB\u5F00\u901A)`);
44159
+ console.log(`\u274C ${f.name} \u2014 \u672A\u542F\u7528 (devMode=false \u4E14\u65E0 license)`);
43777
44160
  }
43778
44161
  } 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
- }
44162
+ console.log(`\u26AA ${f.name} \u2014 \u4E0E license \u65E0\u5173\uFF08\u5F00\u4E0D\u5F00\u770B config\uFF09`);
43785
44163
  }
43786
44164
  }
43787
44165
  console.log("");