engine7 7.1.23 → 7.1.25

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
@@ -985,8 +985,10 @@ async function executeStopHooks(ctx, signal, lastAssistantMessage) {
985
985
  cwd: ctx.cwd,
986
986
  stop_hook_active: false,
987
987
  last_assistant_message: lastAssistantMessage,
988
- channel: ctx.channel
988
+ channel: ctx.channel,
989
989
  // 让 callback hook 能判断来源
990
+ source: ctx.source || ""
991
+ // 消息来源(heartbeat/cron/system 等注入 turn 可据此跳过)
990
992
  };
991
993
  return executeHooks("Stop", hookInput, ctx, signal);
992
994
  }
@@ -1338,6 +1340,7 @@ function collectSurfacedMemories(messages) {
1338
1340
  return { paths };
1339
1341
  }
1340
1342
  function normalizePath(p2) {
1343
+ if (p2.startsWith("everos://")) return p2;
1341
1344
  return pathResolve(p2);
1342
1345
  }
1343
1346
  function memoryHeader(filePath, mtimeMs) {
@@ -2784,10 +2787,12 @@ function breakdownMessages(messages) {
2784
2787
  if (memCount === 0) console.log(`[context-analyzer] WARNING: relevant_memories attachment with 0 memories! keys=${Object.keys(m2.attachment).join(",")}`);
2785
2788
  for (const mem of m2.attachment.memories || []) {
2786
2789
  const fileName = mem.path.split(/[/\\]/).pop() || mem.path;
2790
+ const scoreMatch = mem.header?.match(/score=([\d.]+)/);
2787
2791
  recalledTopics.push({
2788
2792
  path: fileName,
2789
2793
  tokens: roughTokenCountEstimation(mem.content),
2790
- stable: false
2794
+ stable: false,
2795
+ score: scoreMatch ? scoreMatch[1] : void 0
2791
2796
  });
2792
2797
  }
2793
2798
  }
@@ -2998,13 +3003,25 @@ function formatContextReport(report) {
2998
3003
  output += `### Recalled Memories (${rt.length})
2999
3004
 
3000
3005
  `;
3001
- output += `| File | Tokens |
3006
+ const hasScore = rt.some((t) => t.score);
3007
+ if (hasScore) {
3008
+ output += `| File | Score | Tokens |
3002
3009
  `;
3003
- output += `|------|--------|
3010
+ output += `|------|-------|--------|
3011
+ `;
3012
+ for (const t of rt) {
3013
+ output += `| ${t.path} | ${t.score ?? "-"} | ${formatTokens(t.tokens)} |
3014
+ `;
3015
+ }
3016
+ } else {
3017
+ output += `| File | Tokens |
3018
+ `;
3019
+ output += `|------|--------|
3004
3020
  `;
3005
- for (const t of rt) {
3006
- output += `| ${t.path} | ${formatTokens(t.tokens)} |
3021
+ for (const t of rt) {
3022
+ output += `| ${t.path} | ${formatTokens(t.tokens)} |
3007
3023
  `;
3024
+ }
3008
3025
  }
3009
3026
  output += "\n";
3010
3027
  }
@@ -3342,7 +3359,8 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
3342
3359
  sessionId: context?.sessionId || "default",
3343
3360
  workspace: context?.workspace || "",
3344
3361
  channel: context?.channel || "",
3345
- cwd: context?.workspace || ""
3362
+ cwd: context?.workspace || "",
3363
+ source: typeof context?.source === "string" ? context.source : ""
3346
3364
  };
3347
3365
  const stopResult = await executeStopHooks(stopCtx, ac.signal, textContent);
3348
3366
  if (stopResult.preventContinuation) {
@@ -5729,11 +5747,11 @@ async function readLastConsolidatedAt(memoryDir) {
5729
5747
  }
5730
5748
  }
5731
5749
  async function tryAcquireConsolidationLock(memoryDir) {
5732
- const path43 = lockPath(memoryDir);
5750
+ const path44 = lockPath(memoryDir);
5733
5751
  let mtimeMs;
5734
5752
  let holderPid;
5735
5753
  try {
5736
- const [s2, raw] = await Promise.all([stat3(path43), readFile5(path43, "utf8")]);
5754
+ const [s2, raw] = await Promise.all([stat3(path44), readFile5(path44, "utf8")]);
5737
5755
  mtimeMs = s2.mtimeMs;
5738
5756
  const parsed = parseInt(raw.trim(), 10);
5739
5757
  holderPid = Number.isFinite(parsed) ? parsed : void 0;
@@ -5746,10 +5764,10 @@ async function tryAcquireConsolidationLock(memoryDir) {
5746
5764
  }
5747
5765
  }
5748
5766
  await mkdir3(memoryDir, { recursive: true });
5749
- await writeFile4(path43, String(process.pid));
5767
+ await writeFile4(path44, String(process.pid));
5750
5768
  let verify2;
5751
5769
  try {
5752
- verify2 = await readFile5(path43, "utf8");
5770
+ verify2 = await readFile5(path44, "utf8");
5753
5771
  } catch {
5754
5772
  return null;
5755
5773
  }
@@ -5757,15 +5775,15 @@ async function tryAcquireConsolidationLock(memoryDir) {
5757
5775
  return mtimeMs ?? 0;
5758
5776
  }
5759
5777
  async function rollbackConsolidationLock(memoryDir, priorMtime) {
5760
- const path43 = lockPath(memoryDir);
5778
+ const path44 = lockPath(memoryDir);
5761
5779
  try {
5762
5780
  if (priorMtime === 0) {
5763
- await unlink(path43);
5781
+ await unlink(path44);
5764
5782
  return;
5765
5783
  }
5766
- await writeFile4(path43, "");
5784
+ await writeFile4(path44, "");
5767
5785
  const t = priorMtime / 1e3;
5768
- await utimes(path43, t, t);
5786
+ await utimes(path44, t, t);
5769
5787
  } catch (e) {
5770
5788
  console.log(`[autoDream] rollback failed: ${e.message} \u2014 next trigger delayed to minHours`);
5771
5789
  }
@@ -6098,15 +6116,15 @@ __export(TodoWriteTool_exports, {
6098
6116
  loadTodos: () => loadTodos
6099
6117
  });
6100
6118
  import fs31 from "node:fs";
6101
- import path30 from "node:path";
6119
+ import path31 from "node:path";
6102
6120
  function initTodoStore(stateDir) {
6103
- todosDir = path30.join(stateDir, "todos");
6121
+ todosDir = path31.join(stateDir, "todos");
6104
6122
  if (!fs31.existsSync(todosDir)) {
6105
6123
  fs31.mkdirSync(todosDir, { recursive: true });
6106
6124
  }
6107
6125
  }
6108
6126
  function todoFilePath(sessionId) {
6109
- return path30.join(todosDir, `${sessionId}.json`);
6127
+ return path31.join(todosDir, `${sessionId}.json`);
6110
6128
  }
6111
6129
  function loadTodos(sessionId) {
6112
6130
  if (!todosDir) return [];
@@ -6202,15 +6220,15 @@ __export(tasks_exports, {
6202
6220
  updateTask: () => updateTask
6203
6221
  });
6204
6222
  import * as fs33 from "node:fs";
6205
- import * as path32 from "node:path";
6223
+ import * as path33 from "node:path";
6206
6224
  function sanitizePathComponent2(input) {
6207
6225
  return input.replace(/[^a-zA-Z0-9_-]/g, "-");
6208
6226
  }
6209
6227
  function getTasksDir2(stateDir, listId) {
6210
- return path32.join(stateDir, "tasks", sanitizePathComponent2(listId));
6228
+ return path33.join(stateDir, "tasks", sanitizePathComponent2(listId));
6211
6229
  }
6212
6230
  function getTaskPath(stateDir, listId, taskId) {
6213
- return path32.join(getTasksDir2(stateDir, listId), `${sanitizePathComponent2(taskId)}.json`);
6231
+ return path33.join(getTasksDir2(stateDir, listId), `${sanitizePathComponent2(taskId)}.json`);
6214
6232
  }
6215
6233
  function ensureTasksDir2(stateDir, listId) {
6216
6234
  const dir = getTasksDir2(stateDir, listId);
@@ -6218,7 +6236,7 @@ function ensureTasksDir2(stateDir, listId) {
6218
6236
  return dir;
6219
6237
  }
6220
6238
  function getHighWaterMarkPath(stateDir, listId) {
6221
- return path32.join(getTasksDir2(stateDir, listId), HIGH_WATER_MARK_FILE);
6239
+ return path33.join(getTasksDir2(stateDir, listId), HIGH_WATER_MARK_FILE);
6222
6240
  }
6223
6241
  function readHighWaterMark(stateDir, listId) {
6224
6242
  try {
@@ -6424,7 +6442,7 @@ __export(read_exports, {
6424
6442
  readFileState: () => readFileState
6425
6443
  });
6426
6444
  import * as fs34 from "node:fs";
6427
- import * as path33 from "node:path";
6445
+ import * as path34 from "node:path";
6428
6446
  function isBlockedDevicePath(filePath) {
6429
6447
  if (BLOCKED_DEVICE_PATHS.has(filePath)) return true;
6430
6448
  if (filePath.startsWith("/proc/") && (filePath.endsWith("/fd/0") || filePath.endsWith("/fd/1") || filePath.endsWith("/fd/2"))) return true;
@@ -6603,7 +6621,7 @@ Usage:
6603
6621
  return { content: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, isError: true };
6604
6622
  }
6605
6623
  const stat4 = fs34.statSync(filePath);
6606
- const baseName = path33.basename(filePath).toUpperCase();
6624
+ const baseName = path34.basename(filePath).toUpperCase();
6607
6625
  if (BLOCKED_BASENAMES.has(baseName)) {
6608
6626
  return { content: `\u8BBE\u5907\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6: ${filePath}`, isError: true };
6609
6627
  }
@@ -6613,7 +6631,7 @@ Usage:
6613
6631
  if (stat4.isDirectory()) {
6614
6632
  const entries = fs34.readdirSync(filePath);
6615
6633
  const items = entries.map((e) => {
6616
- const full = path33.join(filePath, e);
6634
+ const full = path34.join(filePath, e);
6617
6635
  try {
6618
6636
  const s2 = fs34.statSync(full);
6619
6637
  return s2.isDirectory() ? `${e}/` : e;
@@ -6624,7 +6642,7 @@ Usage:
6624
6642
  return { content: `\u76EE\u5F55 (${entries.length} \u9879):
6625
6643
  ${items.join("\n")}` };
6626
6644
  }
6627
- const ext = path33.extname(filePath).toLowerCase();
6645
+ const ext = path34.extname(filePath).toLowerCase();
6628
6646
  if (BINARY_EXTENSIONS.has(ext)) {
6629
6647
  return { content: `\u4E8C\u8FDB\u5236\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6 (${ext}): ${filePath}`, isError: true };
6630
6648
  }
@@ -6673,7 +6691,7 @@ ${result}` : result };
6673
6691
  // src/tools/write.ts
6674
6692
  var write_exports = {};
6675
6693
  import * as fs35 from "node:fs";
6676
- import * as path34 from "node:path";
6694
+ import * as path35 from "node:path";
6677
6695
  function isBlockedPath(filePath) {
6678
6696
  return BLOCKED_PATTERNS.some((p2) => p2.test(filePath));
6679
6697
  }
@@ -6817,7 +6835,7 @@ Usage:
6817
6835
  }
6818
6836
  }
6819
6837
  }
6820
- const dir = path34.dirname(filePath);
6838
+ const dir = path35.dirname(filePath);
6821
6839
  try {
6822
6840
  fs35.mkdirSync(dir, { recursive: true });
6823
6841
  } catch (e) {
@@ -6853,7 +6871,7 @@ ${simpleDiff(oldContent, content)}`;
6853
6871
  // src/tools/edit.ts
6854
6872
  var edit_exports = {};
6855
6873
  import * as fs36 from "node:fs";
6856
- import * as path35 from "node:path";
6874
+ import * as path36 from "node:path";
6857
6875
  function normalizeQuotes(str) {
6858
6876
  return str.replaceAll(LEFT_SINGLE_CURLY, "'").replaceAll(RIGHT_SINGLE_CURLY, "'").replaceAll(LEFT_DOUBLE_CURLY, '"').replaceAll(RIGHT_DOUBLE_CURLY, '"');
6859
6877
  }
@@ -7016,7 +7034,7 @@ Usage:
7016
7034
  } catch (e) {
7017
7035
  if (e.code === "ENOENT") {
7018
7036
  if (oldString === "") {
7019
- const dir = path35.dirname(filePath);
7037
+ const dir = path36.dirname(filePath);
7020
7038
  fs36.mkdirSync(dir, { recursive: true });
7021
7039
  fs36.writeFileSync(filePath, newString, "utf-8");
7022
7040
  readFileState.set(filePath, { timestamp: fs36.statSync(filePath).mtimeMs });
@@ -7096,7 +7114,7 @@ ${diffView}`
7096
7114
  // src/tools/glob.ts
7097
7115
  var glob_exports = {};
7098
7116
  import * as fs37 from "node:fs";
7099
- import * as path36 from "node:path";
7117
+ import * as path37 from "node:path";
7100
7118
  function globMatch(pattern, filename) {
7101
7119
  const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
7102
7120
  try {
@@ -7122,12 +7140,12 @@ function findFiles(dir, pattern, limit, baseDir) {
7122
7140
  }
7123
7141
  for (const entry of entries) {
7124
7142
  if (truncated) return;
7125
- const fullPath = path36.join(currentDir, entry.name);
7143
+ const fullPath = path37.join(currentDir, entry.name);
7126
7144
  if (entry.isDirectory()) {
7127
7145
  if (VCS_DIRS.has(entry.name)) continue;
7128
7146
  walk(fullPath);
7129
7147
  } else if (entry.isFile()) {
7130
- const relativePath = path36.relative(baseDir, fullPath).replace(/\\/g, "/");
7148
+ const relativePath = path37.relative(baseDir, fullPath).replace(/\\/g, "/");
7131
7149
  const patternsToTry = [pattern];
7132
7150
  if (pattern.startsWith("**/")) {
7133
7151
  patternsToTry.push(pattern.slice(3));
@@ -7154,7 +7172,7 @@ function findFiles(dir, pattern, limit, baseDir) {
7154
7172
  };
7155
7173
  }
7156
7174
  function toRelativePath(absolutePath, cwd) {
7157
- if (absolutePath.startsWith(cwd + path36.sep)) {
7175
+ if (absolutePath.startsWith(cwd + path37.sep)) {
7158
7176
  return absolutePath.slice(cwd.length + 1);
7159
7177
  }
7160
7178
  return absolutePath;
@@ -7215,7 +7233,7 @@ ${filenames.join("\n")}${truncatedNote}`
7215
7233
  // src/tools/grep.ts
7216
7234
  var grep_exports = {};
7217
7235
  import { execFile as execFile2 } from "node:child_process";
7218
- import * as path37 from "node:path";
7236
+ import * as path38 from "node:path";
7219
7237
  function ripGrep(args2, searchPath, signal) {
7220
7238
  return new Promise((resolve10) => {
7221
7239
  const fullArgs = [...args2, searchPath];
@@ -7247,7 +7265,7 @@ function applyHeadLimit(items, limit, offset = 0) {
7247
7265
  };
7248
7266
  }
7249
7267
  function toRelativePath2(absolutePath, cwd) {
7250
- if (absolutePath.startsWith(cwd + path37.sep)) {
7268
+ if (absolutePath.startsWith(cwd + path38.sep)) {
7251
7269
  return absolutePath.slice(cwd.length + 1);
7252
7270
  }
7253
7271
  if (absolutePath.startsWith(cwd)) {
@@ -9916,7 +9934,7 @@ var init_web_fetch = __esm({
9916
9934
 
9917
9935
  // src/cron/tasks.ts
9918
9936
  import fs38 from "node:fs";
9919
- import path38 from "node:path";
9937
+ import path39 from "node:path";
9920
9938
  import crypto5 from "node:crypto";
9921
9939
  function getStorageDir() {
9922
9940
  return storageDir;
@@ -9971,7 +9989,7 @@ function readTasksFromDisk() {
9971
9989
  }
9972
9990
  }
9973
9991
  async function writeTasksToDisk(tasks2) {
9974
- const lockPath2 = path38.join(storageDir, "tasks.json.lock");
9992
+ const lockPath2 = path39.join(storageDir, "tasks.json.lock");
9975
9993
  await withFileLock(lockPath2, () => {
9976
9994
  const store = {
9977
9995
  version: 1,
@@ -9983,7 +10001,7 @@ async function writeTasksToDisk(tasks2) {
9983
10001
  }
9984
10002
  function initTaskStore(dir) {
9985
10003
  storageDir = dir;
9986
- tasksFilePath = path38.join(dir, "tasks.json");
10004
+ tasksFilePath = path39.join(dir, "tasks.json");
9987
10005
  if (!fs38.existsSync(dir)) {
9988
10006
  fs38.mkdirSync(dir, { recursive: true });
9989
10007
  }
@@ -10897,9 +10915,9 @@ async function executeAndDeliver(task, now, deps) {
10897
10915
  let filePath = promptText.slice(1).trim();
10898
10916
  try {
10899
10917
  const fs42 = await import("fs");
10900
- const path43 = await import("path");
10901
- if (!path43.isAbsolute(filePath)) {
10902
- filePath = path43.join(deps.sessions["config"].stateDir, filePath);
10918
+ const path44 = await import("path");
10919
+ if (!path44.isAbsolute(filePath)) {
10920
+ filePath = path44.join(deps.sessions["config"].stateDir, filePath);
10903
10921
  }
10904
10922
  promptText = fs42.readFileSync(filePath, "utf-8");
10905
10923
  console.log(`[cron] Loaded prompt from ${filePath} (${promptText.length} chars)`);
@@ -10928,15 +10946,15 @@ async function executeAndDeliver(task, now, deps) {
10928
10946
  let finalResult = result;
10929
10947
  if (task.postProcess) {
10930
10948
  try {
10931
- const path43 = await import("path");
10949
+ const path44 = await import("path");
10932
10950
  const fs42 = await import("fs");
10933
10951
  let scriptPath = task.postProcess;
10934
- if (!path43.isAbsolute(scriptPath)) {
10935
- scriptPath = path43.join(deps.sessions["config"].stateDir, scriptPath);
10952
+ if (!path44.isAbsolute(scriptPath)) {
10953
+ scriptPath = path44.join(deps.sessions["config"].stateDir, scriptPath);
10936
10954
  }
10937
- const resultsDirTmp = path43.join(getStorageDir(), "results");
10955
+ const resultsDirTmp = path44.join(getStorageDir(), "results");
10938
10956
  fs42.mkdirSync(resultsDirTmp, { recursive: true });
10939
- const inputFile = path43.join(resultsDirTmp, `${task.id}.input.txt`);
10957
+ const inputFile = path44.join(resultsDirTmp, `${task.id}.input.txt`);
10940
10958
  fs42.writeFileSync(inputFile, result, "utf-8");
10941
10959
  const { execFile: execFile3 } = await import("child_process");
10942
10960
  await new Promise((resolve10) => {
@@ -10965,10 +10983,10 @@ async function executeAndDeliver(task, now, deps) {
10965
10983
  }
10966
10984
  try {
10967
10985
  const fs42 = await import("fs");
10968
- const path43 = await import("path");
10969
- const resultsDir = path43.join(getStorageDir(), "results");
10986
+ const path44 = await import("path");
10987
+ const resultsDir = path44.join(getStorageDir(), "results");
10970
10988
  fs42.mkdirSync(resultsDir, { recursive: true });
10971
- const resultFile = path43.join(resultsDir, `${task.id}.json`);
10989
+ const resultFile = path44.join(resultsDir, `${task.id}.json`);
10972
10990
  fs42.writeFileSync(resultFile, JSON.stringify({
10973
10991
  taskId: task.id,
10974
10992
  description: task.description,
@@ -11217,13 +11235,13 @@ function registerCronTools() {
11217
11235
  },
11218
11236
  handler: async (args2) => {
11219
11237
  const fs42 = await import("fs");
11220
- const path43 = await import("path");
11221
- const resultsDir = path43.join(getStorageDir(), "results");
11238
+ const path44 = await import("path");
11239
+ const resultsDir = path44.join(getStorageDir(), "results");
11222
11240
  if (!fs42.existsSync(resultsDir)) {
11223
11241
  return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
11224
11242
  }
11225
11243
  if (args2.task_id) {
11226
- const file = path43.join(resultsDir, `${args2.task_id}.json`);
11244
+ const file = path44.join(resultsDir, `${args2.task_id}.json`);
11227
11245
  if (!fs42.existsSync(file)) {
11228
11246
  return { content: `\u4EFB\u52A1 ${args2.task_id} \u6682\u65E0\u6267\u884C\u7ED3\u679C`, isError: true };
11229
11247
  }
@@ -11239,7 +11257,7 @@ ${data.result}` };
11239
11257
  return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
11240
11258
  }
11241
11259
  const results = files.map((f2) => {
11242
- const data = JSON.parse(fs42.readFileSync(path43.join(resultsDir, f2), "utf-8"));
11260
+ const data = JSON.parse(fs42.readFileSync(path44.join(resultsDir, f2), "utf-8"));
11243
11261
  return `### ${data.description} (${data.taskId.slice(0, 8)})
11244
11262
  \u6267\u884C: ${data.executedAt} | \u7B2C${data.runCount}\u6B21
11245
11263
  ${data.result.slice(0, 500)}${data.result.length > 500 ? "..." : ""}`;
@@ -11622,7 +11640,7 @@ __export(license_exports, {
11622
11640
  });
11623
11641
  import * as crypto6 from "node:crypto";
11624
11642
  import * as fs39 from "node:fs";
11625
- import * as path39 from "node:path";
11643
+ import * as path40 from "node:path";
11626
11644
  function loadLicense(stateDir, devMode) {
11627
11645
  if (_licenseChecked) return _cachedLicense;
11628
11646
  _licenseChecked = true;
@@ -11635,7 +11653,7 @@ function loadLicense(stateDir, devMode) {
11635
11653
  _cachedLicense = allActive;
11636
11654
  return allActive;
11637
11655
  }
11638
- const licensePath = path39.join(stateDir, "license.json");
11656
+ const licensePath = path40.join(stateDir, "license.json");
11639
11657
  if (!fs39.existsSync(licensePath)) {
11640
11658
  console.log("[license] No license.json found, running basic engine only");
11641
11659
  return null;
@@ -11691,7 +11709,7 @@ function isFeatureLicensed(featureId) {
11691
11709
  return f2?.active === true;
11692
11710
  }
11693
11711
  function getLicenseStatus(stateDir) {
11694
- const licensePath = path39.join(stateDir, "license.json");
11712
+ const licensePath = path40.join(stateDir, "license.json");
11695
11713
  if (!fs39.existsSync(licensePath)) {
11696
11714
  return { licensed: false, features: {} };
11697
11715
  }
@@ -11756,7 +11774,7 @@ __export(manager_exports, {
11756
11774
  McpManager: () => McpManager
11757
11775
  });
11758
11776
  import * as fs40 from "node:fs";
11759
- import * as path40 from "node:path";
11777
+ import * as path41 from "node:path";
11760
11778
  import { Client as Client3 } from "@modelcontextprotocol/sdk/client/index.js";
11761
11779
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11762
11780
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -11789,9 +11807,9 @@ function convertInputSchema(inputSchema) {
11789
11807
  }
11790
11808
  function persistBinary(base64Data, mimeType, persistId) {
11791
11809
  const ext = mimeType?.split("/")[1] || "bin";
11792
- const dir = path40.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
11810
+ const dir = path41.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
11793
11811
  fs40.mkdirSync(dir, { recursive: true });
11794
- const filepath = path40.join(dir, `${persistId}.${ext}`);
11812
+ const filepath = path41.join(dir, `${persistId}.${ext}`);
11795
11813
  try {
11796
11814
  const buf = Buffer.from(base64Data, "base64");
11797
11815
  fs40.writeFileSync(filepath, buf);
@@ -12128,7 +12146,7 @@ __export(resources_exports, {
12128
12146
  registerMcpResourceTools: () => registerMcpResourceTools,
12129
12147
  unregisterMcpResourceTools: () => unregisterMcpResourceTools
12130
12148
  });
12131
- import * as path41 from "node:path";
12149
+ import * as path42 from "node:path";
12132
12150
  function registerMcpResourceTools(manager) {
12133
12151
  mcpManagerRef = manager;
12134
12152
  registry.register(listResourcesTool);
@@ -12146,7 +12164,7 @@ var init_resources = __esm({
12146
12164
  "use strict";
12147
12165
  init_registry();
12148
12166
  MAX_RESULT_CHARS2 = 1e5;
12149
- MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path41.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
12167
+ MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path42.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
12150
12168
  MCP_LIST_RESOURCES_TOOL = "mcp__list_resources";
12151
12169
  MCP_READ_RESOURCE_TOOL = "mcp__read_resource";
12152
12170
  mcpManagerRef = null;
@@ -12236,11 +12254,21 @@ var everos_sync_exports = {};
12236
12254
  __export(everos_sync_exports, {
12237
12255
  createEverosSync: () => createEverosSync
12238
12256
  });
12257
+ function parseMeta(text) {
12258
+ const m2 = text.match(/^\[meta:\s*(.+?)\s*\((.+?)\)\s*@(\S+)\s*[^\]]*\]/);
12259
+ if (!m2) return null;
12260
+ return { senderName: m2[1].trim(), senderId: m2[2].trim(), platform: m2[3].trim() };
12261
+ }
12239
12262
  function createEverosSync(cfg) {
12240
- const { enabled, url, appId, userId } = cfg;
12263
+ const { enabled, url, appId, userId, agentName } = cfg;
12241
12264
  async function push(event) {
12242
12265
  if (!enabled) return;
12243
12266
  if (!event.text.trim()) return;
12267
+ return;
12268
+ const meta = event.role === "user" ? parseMeta(event.text) : null;
12269
+ const senderId = appId;
12270
+ const senderName = meta?.senderName ?? (event.role === "assistant" ? agentName : void 0) ?? event.senderName ?? event.role;
12271
+ console.log(`[everos-sync] role=${event.role} sender_id=${senderId} sender_name=${senderName} metaParsed=${!!meta} textLen=${event.text.length}`);
12244
12272
  try {
12245
12273
  const resp = await fetch(`${url}/api/v1/memory/add`, {
12246
12274
  method: "POST",
@@ -12250,8 +12278,8 @@ function createEverosSync(cfg) {
12250
12278
  app_id: appId,
12251
12279
  project_id: "default",
12252
12280
  messages: [{
12253
- sender_id: event.role === "user" ? "user" : appId,
12254
- sender_name: event.senderName || event.role,
12281
+ sender_id: senderId,
12282
+ sender_name: senderName,
12255
12283
  role: event.role,
12256
12284
  timestamp: event.timestamp,
12257
12285
  content: event.text
@@ -12275,13 +12303,17 @@ function createEverosSync(cfg) {
12275
12303
  session_id: events[0].sessionId,
12276
12304
  app_id: appId,
12277
12305
  project_id: "default",
12278
- messages: events.map((e) => ({
12279
- sender_id: e.role === "user" ? "user" : appId,
12280
- sender_name: e.senderName || e.role,
12281
- role: e.role,
12282
- timestamp: e.timestamp,
12283
- content: e.text
12284
- }))
12306
+ messages: events.map((e) => {
12307
+ const meta = e.role === "user" ? parseMeta(e.text) : null;
12308
+ const name = meta?.senderName ?? (e.role === "assistant" ? agentName : void 0) ?? e.senderName ?? e.role;
12309
+ return {
12310
+ sender_id: appId,
12311
+ sender_name: name,
12312
+ role: e.role,
12313
+ timestamp: e.timestamp,
12314
+ content: e.text
12315
+ };
12316
+ })
12285
12317
  }),
12286
12318
  signal: AbortSignal.timeout(3e4)
12287
12319
  });
@@ -12383,10 +12415,10 @@ function ensureLoaded(workspace, configIds) {
12383
12415
  if (!state.blockedUserIds.includes(id)) state.blockedUserIds.push(id);
12384
12416
  }
12385
12417
  }
12386
- const path43 = join36(workspace, ".reply-blocklist.json");
12418
+ const path44 = join36(workspace, ".reply-blocklist.json");
12387
12419
  try {
12388
- if (existsSync24(path43)) {
12389
- const raw = readFileSync26(path43, "utf-8");
12420
+ if (existsSync24(path44)) {
12421
+ const raw = readFileSync26(path44, "utf-8");
12390
12422
  const parsed = JSON.parse(raw);
12391
12423
  if (parsed.blockedUserIds) {
12392
12424
  for (const id of parsed.blockedUserIds) {
@@ -12402,9 +12434,9 @@ function ensureLoaded(workspace, configIds) {
12402
12434
  loaded = true;
12403
12435
  }
12404
12436
  function save(workspace) {
12405
- const path43 = join36(workspace, ".reply-blocklist.json");
12437
+ const path44 = join36(workspace, ".reply-blocklist.json");
12406
12438
  try {
12407
- writeFileSync15(path43, JSON.stringify(state, null, 2), "utf-8");
12439
+ writeFileSync15(path44, JSON.stringify(state, null, 2), "utf-8");
12408
12440
  } catch (err) {
12409
12441
  console.warn(`[reply-blocklist] Failed to save: ${err.message}`);
12410
12442
  }
@@ -13004,7 +13036,7 @@ var init_cognifold_intent_watcher = __esm({
13004
13036
  init_loader();
13005
13037
 
13006
13038
  // src/engine-startup.ts
13007
- import * as path42 from "node:path";
13039
+ import * as path43 from "node:path";
13008
13040
  import * as fs41 from "node:fs";
13009
13041
  import { fileURLToPath } from "node:url";
13010
13042
 
@@ -14348,11 +14380,11 @@ var DiscordAdapter = class _DiscordAdapter {
14348
14380
  /** 发送媒体附件(图片/文件/音频)— discord.js channel.send({ files }) */
14349
14381
  async sendFile(target, message, attachment) {
14350
14382
  const fs42 = await import("node:fs");
14351
- const path43 = await import("node:path");
14383
+ const path44 = await import("node:path");
14352
14384
  if (!fs42.existsSync(attachment.path)) {
14353
14385
  throw new Error(`File not found: ${attachment.path}`);
14354
14386
  }
14355
- const filename = attachment.filename || path43.basename(attachment.path);
14387
+ const filename = attachment.filename || path44.basename(attachment.path);
14356
14388
  const fileBuffer = fs42.readFileSync(attachment.path);
14357
14389
  const filePayload = {
14358
14390
  attachment: fileBuffer,
@@ -14794,11 +14826,11 @@ var FeishuAdapter = class _FeishuAdapter {
14794
14826
  /** 发送媒体附件(图片/文件) */
14795
14827
  async sendFile(target, message, attachment) {
14796
14828
  const fs42 = await import("node:fs");
14797
- const path43 = await import("node:path");
14829
+ const path44 = await import("node:path");
14798
14830
  if (!fs42.existsSync(attachment.path)) {
14799
14831
  throw new Error(`File not found: ${attachment.path}`);
14800
14832
  }
14801
- const filename = attachment.filename || path43.basename(attachment.path);
14833
+ const filename = attachment.filename || path44.basename(attachment.path);
14802
14834
  const fileBuffer = fs42.readFileSync(attachment.path);
14803
14835
  const receiveIdType = target.startsWith("ou_") ? "open_id" : "chat_id";
14804
14836
  const mimeType = attachment.mimeType || "application/octet-stream";
@@ -18410,7 +18442,8 @@ ${skillsListing}`);
18410
18442
  parts.push(getEnvInfoSection(options.workspace));
18411
18443
  const now = /* @__PURE__ */ new Date();
18412
18444
  const dateStr = now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
18413
- parts.push(`\u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18445
+ parts.push(`# \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587
18446
+ \u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18414
18447
  console.log(`[dynamic-prompt] Loaded: ${loaded2.length > 0 ? loaded2.join(", ") : "(none)"}`);
18415
18448
  return parts.join("\n\n");
18416
18449
  }
@@ -18712,6 +18745,169 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
18712
18745
  }));
18713
18746
  }
18714
18747
 
18748
+ // src/memory/memdir/findRelevantMemoriesEveros.ts
18749
+ var ROUND1_TOP_N = 30;
18750
+ var RERANK_BATCH_SIZE = 100;
18751
+ function formatEverosHeader(ep) {
18752
+ const score = ep.score.toFixed(3);
18753
+ const ts = ep.timestamp?.slice(0, 10) ?? "";
18754
+ let ageLabel = "";
18755
+ if (ts) {
18756
+ const days = Math.floor((Date.now() - new Date(ts).getTime()) / 864e5);
18757
+ if (days <= 1) ageLabel = "\u4ECA\u5929";
18758
+ else if (days <= 3) ageLabel = `${days}\u5929\u524D`;
18759
+ else if (days <= 14) ageLabel = `${days}\u5929\u524D`;
18760
+ else if (days <= 30) ageLabel = `~${Math.ceil(days / 7)}\u5468\u524D`;
18761
+ else ageLabel = `~${Math.ceil(days / 30)}\u4E2A\u6708\u524D`;
18762
+ }
18763
+ return `[EverOS score=${score} ${ts} (${ageLabel})]`;
18764
+ }
18765
+ async function hybridSearch(query, everosUrl, userId, topK) {
18766
+ const resp = await fetch(`${everosUrl}/api/v1/memory/search`, {
18767
+ method: "POST",
18768
+ headers: { "Content-Type": "application/json" },
18769
+ body: JSON.stringify({
18770
+ query: query.slice(0, 2e3),
18771
+ user_id: userId,
18772
+ app_id: userId,
18773
+ project_id: "default",
18774
+ top_k: topK,
18775
+ method: "hybrid"
18776
+ }),
18777
+ signal: AbortSignal.timeout(15e3)
18778
+ });
18779
+ if (!resp.ok) {
18780
+ console.warn(`[memdir] everos hybrid: search failed ${resp.status}`);
18781
+ return [];
18782
+ }
18783
+ const body = await resp.json();
18784
+ const episodes = body.data?.episodes ?? [];
18785
+ return episodes;
18786
+ }
18787
+ async function deepinfraRerank(query, episodes, rerankUrl, rerankApiKey, rerankModel, provider) {
18788
+ if (episodes.length === 0) return [];
18789
+ const documents = episodes.map(
18790
+ (ep) => ep.episode?.slice(0, 500) || ep.summary?.slice(0, 500) || ep.subject
18791
+ );
18792
+ const allScores = [];
18793
+ const isDashscope = provider === "dashscope";
18794
+ for (let i = 0; i < documents.length; i += RERANK_BATCH_SIZE) {
18795
+ const batch = documents.slice(i, i + RERANK_BATCH_SIZE);
18796
+ const body = isDashscope ? JSON.stringify({
18797
+ model: rerankModel || "qwen3-rerank",
18798
+ input: { query, documents: batch },
18799
+ parameters: { return_documents: false, top_n: batch.length }
18800
+ }) : JSON.stringify({ queries: [query], documents: batch });
18801
+ const makeRequest = () => fetch(rerankUrl, {
18802
+ method: "POST",
18803
+ headers: {
18804
+ "Authorization": `Bearer ${rerankApiKey}`,
18805
+ "Content-Type": "application/json"
18806
+ },
18807
+ body,
18808
+ signal: AbortSignal.timeout(3e4)
18809
+ });
18810
+ let resp = await makeRequest();
18811
+ if (resp.status === 429) {
18812
+ await new Promise((r) => setTimeout(r, 2e3));
18813
+ resp = await makeRequest();
18814
+ }
18815
+ if (!resp.ok) {
18816
+ console.warn(`[memdir] everos rerank: failed ${resp.status}`);
18817
+ return episodes;
18818
+ }
18819
+ if (isDashscope) {
18820
+ const data = await resp.json();
18821
+ const results = data.output?.results ?? [];
18822
+ const scoreMap = new Array(batch.length).fill(0);
18823
+ for (const r of results) {
18824
+ scoreMap[r.index] = r.relevance_score;
18825
+ }
18826
+ allScores.push(...scoreMap);
18827
+ } else {
18828
+ const data = await resp.json();
18829
+ let batchScores = data.scores ?? [];
18830
+ if (Array.isArray(batchScores) && batchScores.length > 0 && Array.isArray(batchScores[0])) {
18831
+ batchScores = batchScores[0];
18832
+ }
18833
+ allScores.push(...batchScores);
18834
+ }
18835
+ }
18836
+ const ranked = episodes.map((ep, idx) => ({ ep, score: allScores[idx] ?? 0 })).sort((a, b2) => b2.score - a.score);
18837
+ for (const { ep, score } of ranked) {
18838
+ ep.score = score;
18839
+ }
18840
+ return ranked.map((r) => r.ep);
18841
+ }
18842
+ var DEFAULT_MIN_SCORE2 = 0.5;
18843
+ async function findRelevantMemoriesEveros(query, _memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
18844
+ const everosUrl = options?.everosUrl ?? "http://127.0.0.1:8100";
18845
+ const userId = options?.userId ?? "xiaomei";
18846
+ const topK = options?.topK ?? 3;
18847
+ const minScore = options?.minScore ?? DEFAULT_MIN_SCORE2;
18848
+ console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} userId=${userId} topK=${topK}`);
18849
+ const t0 = Date.now();
18850
+ try {
18851
+ const tH1 = Date.now();
18852
+ let episodes = await hybridSearch(query, everosUrl, userId, ROUND1_TOP_N);
18853
+ const tH2 = Date.now();
18854
+ console.log(`[memdir] everos recall: hybrid ${episodes.length} candidates in ${tH2 - tH1}ms`);
18855
+ if (episodes.length === 0) return [];
18856
+ const rerankUrl = options?.rerankUrl;
18857
+ const rerankApiKey = options?.rerankApiKey;
18858
+ if (rerankUrl && rerankApiKey) {
18859
+ const tR1 = Date.now();
18860
+ episodes = await deepinfraRerank(
18861
+ query,
18862
+ episodes,
18863
+ rerankUrl,
18864
+ rerankApiKey,
18865
+ options?.rerankModel,
18866
+ options?.rerankProvider
18867
+ );
18868
+ const tR2 = Date.now();
18869
+ console.log(`[memdir] everos recall: rerank done in ${tR2 - tR1}ms (${options?.rerankProvider || "deepinfra"})`);
18870
+ } else {
18871
+ console.log(`[memdir] everos recall: no rerank key, using hybrid scores as-is`);
18872
+ }
18873
+ const ms = Date.now() - t0;
18874
+ console.log(`[memdir] everos recall: ${episodes.length} episodes in ${ms}ms total (hybrid+rerank)`);
18875
+ const surfacedSubjects = /* @__PURE__ */ new Set();
18876
+ for (const p2 of alreadySurfaced) {
18877
+ if (p2.startsWith("everos://")) {
18878
+ surfacedSubjects.add(p2.slice(8));
18879
+ } else {
18880
+ }
18881
+ }
18882
+ const result = [];
18883
+ const seenSubjects = /* @__PURE__ */ new Set();
18884
+ for (const ep of episodes) {
18885
+ if (ep.score < minScore) continue;
18886
+ const subject = (ep.subject || ep.id).slice(0, 80).replace(/[\n\r]/g, " ");
18887
+ const virtualPath = `everos://${subject}`;
18888
+ if (alreadySurfaced.has(virtualPath)) continue;
18889
+ if (surfacedSubjects.has(subject)) continue;
18890
+ if (seenSubjects.has(subject)) continue;
18891
+ seenSubjects.add(subject);
18892
+ result.push({
18893
+ path: virtualPath,
18894
+ mtimeMs: ep.timestamp ? new Date(ep.timestamp).getTime() : Date.now(),
18895
+ content: `### ${ep.subject}
18896
+
18897
+ ${ep.episode || ep.summary}`,
18898
+ header: formatEverosHeader(ep)
18899
+ });
18900
+ if (result.length >= topK) break;
18901
+ }
18902
+ console.log(`[memdir] everos recall: returning ${result.length} memories (after dedup)`);
18903
+ return result;
18904
+ } catch (e) {
18905
+ const ms = Date.now() - t0;
18906
+ console.warn(`[memdir] everos recall: error after ${ms}ms: ${e?.message ?? e}`);
18907
+ return [];
18908
+ }
18909
+ }
18910
+
18715
18911
  // src/handle-query.ts
18716
18912
  init_paths();
18717
18913
  import { readFileSync as readFileSync15, existsSync as existsSync12 } from "node:fs";
@@ -18784,18 +18980,18 @@ function truncate(s2, maxLen) {
18784
18980
  }
18785
18981
  var externalChanRulesCache = null;
18786
18982
  function loadExternalChanRules(workspace) {
18787
- const path43 = join20(workspace, "prompts", "external-chan-rules.md");
18788
- if (externalChanRulesCache && externalChanRulesCache.path === path43) return externalChanRulesCache;
18983
+ const path44 = join20(workspace, "prompts", "external-chan-rules.md");
18984
+ if (externalChanRulesCache && externalChanRulesCache.path === path44) return externalChanRulesCache;
18789
18985
  let content = "";
18790
- if (existsSync12(path43)) {
18986
+ if (existsSync12(path44)) {
18791
18987
  try {
18792
- content = readFileSync15(path43, "utf-8").trim();
18988
+ content = readFileSync15(path44, "utf-8").trim();
18793
18989
  } catch (e) {
18794
18990
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
18795
18991
  }
18796
18992
  }
18797
- externalChanRulesCache = { path: path43, content };
18798
- console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path43}`);
18993
+ externalChanRulesCache = { path: path44, content };
18994
+ console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path44}`);
18799
18995
  return externalChanRulesCache;
18800
18996
  }
18801
18997
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -18818,10 +19014,10 @@ function getExternalChanWhitelist(workspace, configExternalChannels) {
18818
19014
  if (!externalChanWhitelist) loadContactMap(workspace);
18819
19015
  return externalChanWhitelist;
18820
19016
  }
18821
- async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18822
- return handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall);
19017
+ async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
19018
+ return handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source);
18823
19019
  }
18824
- async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
19020
+ async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
18825
19021
  const { engine, sessions, channelManager, workspace, providerId, providerApi, model } = deps;
18826
19022
  const features = deps.features || {};
18827
19023
  const preQueryAbort = new AbortController();
@@ -18998,6 +19194,8 @@ ${text}` : text });
18998
19194
  const toolContext = {
18999
19195
  sessionId,
19000
19196
  channel: channelName === "cli" ? "console" : channelName,
19197
+ source: source || "",
19198
+ // 消息来源(user/inbox/heartbeat/cron/system/inner-voice),Stop hook 用来区分注入 turn
19001
19199
  workspace,
19002
19200
  stateDir: deps.stateDir || workspace,
19003
19201
  channelManager,
@@ -19172,7 +19370,23 @@ ${text}` : text });
19172
19370
  const recallP = deps.recallProvider;
19173
19371
  const recallMode = deps.topics?.recall?.mode || "llm";
19174
19372
  let relevantMemories;
19175
- if (recallMode === "vector") {
19373
+ if (recallMode === "everos") {
19374
+ const everosCfg = deps?.everosCfg;
19375
+ relevantMemories = await findRelevantMemoriesEveros(
19376
+ textForMemory,
19377
+ memoryDir,
19378
+ surfaced.paths,
19379
+ everosCfg ? {
19380
+ everosUrl: everosCfg.everosUrl || "http://127.0.0.1:8100",
19381
+ userId: everosCfg.userId || "xiaomei",
19382
+ rerankUrl: everosCfg.rerank?.baseUrl,
19383
+ rerankApiKey: everosCfg.rerank?.apiKey,
19384
+ rerankModel: everosCfg.rerank?.model,
19385
+ rerankProvider: everosCfg.rerank?.provider,
19386
+ minScore: deps.topics?.recall?.minScore
19387
+ } : void 0
19388
+ );
19389
+ } else if (recallMode === "vector") {
19176
19390
  relevantMemories = await findRelevantMemoriesVector(
19177
19391
  textForMemory,
19178
19392
  memoryDir,
@@ -19195,8 +19409,8 @@ ${text}` : text });
19195
19409
  const attachmentMemories = [];
19196
19410
  for (const mem of relevantMemories) {
19197
19411
  try {
19198
- const content = readFileSync15(mem.path, "utf-8");
19199
- const header = memoryHeader(mem.path, mem.mtimeMs);
19412
+ const content = mem.content ?? readFileSync15(mem.path, "utf-8");
19413
+ const header = mem.header ?? memoryHeader(mem.path, mem.mtimeMs);
19200
19414
  attachmentMemories.push({ path: mem.path, content, mtimeMs: mem.mtimeMs, header });
19201
19415
  } catch {
19202
19416
  }
@@ -19744,7 +19958,9 @@ function registerCognifoldBridge(config2) {
19744
19958
  messageId: ctx.inbound.messageId
19745
19959
  }
19746
19960
  };
19747
- void enqueueEvent(cognifoldConfig.sessionId, event);
19961
+ const sm = globalThis.__cognifoldSessions;
19962
+ const dynamicSessionId = sm?.getSessionId?.("main") || cognifoldConfig.sessionId;
19963
+ void enqueueEvent(dynamicSessionId, event);
19748
19964
  return null;
19749
19965
  }, 80);
19750
19966
  }
@@ -20068,7 +20284,8 @@ var MessageDispatcher = class {
20068
20284
  msg2.deps,
20069
20285
  msg2.channelTarget,
20070
20286
  msg2.inboundMeta,
20071
- msg2.skipRecall
20287
+ msg2.skipRecall,
20288
+ msg2.source
20072
20289
  );
20073
20290
  } catch (err) {
20074
20291
  console.error(`[dispatcher] Query error (session=${msg2.sessionId}): ${err.message}`);
@@ -20228,13 +20445,19 @@ function startCliLoop(deps, cliConfig, channelManager, dispatcher) {
20228
20445
 
20229
20446
  // src/session/session-history.ts
20230
20447
  import fs14 from "node:fs";
20448
+ import path14 from "node:path";
20231
20449
  var BEIJING_OFFSET_MS = 8 * 36e5;
20232
20450
  var INJECTED_CONTENT_PATTERNS = [
20233
20451
  /【定时心跳】/,
20234
20452
  /\[内心对话测试\]/,
20235
20453
  /\[inner-voice\]/,
20236
20454
  /\[微信巡检\]/,
20237
- /\[plugin\]/
20455
+ /\[plugin\]/,
20456
+ /<nudge-notification>/,
20457
+ /<task-notification>/,
20458
+ /<calendar-notification>/,
20459
+ /## Actions \(\d+\s*个\)/
20460
+ // CogniFold proactive 注入(block[0] 固定格式,engine-startup 拼的)
20238
20461
  ];
20239
20462
  function parseJsonlEntries(lines) {
20240
20463
  const entries = [];
@@ -20269,6 +20492,20 @@ function resolveScopeMainJsonl(sessions) {
20269
20492
  if (!sessionId) return null;
20270
20493
  return sessions.getSessionFilePath(sessionId);
20271
20494
  }
20495
+ function scopeMainJsonlPaths(sessions) {
20496
+ const current = resolveScopeMainJsonl(sessions);
20497
+ let latestArchive = null;
20498
+ if (current) {
20499
+ try {
20500
+ const dir = path14.dirname(current);
20501
+ const base = path14.basename(current);
20502
+ const archives = fs14.readdirSync(dir).filter((f2) => f2.startsWith(base + ".archived.")).sort();
20503
+ if (archives.length > 0) latestArchive = path14.join(dir, archives[archives.length - 1]);
20504
+ } catch {
20505
+ }
20506
+ }
20507
+ return { current, latestArchive };
20508
+ }
20272
20509
  function cleanText(rawText) {
20273
20510
  let clean = rawText.replace(/<system-reminder>.*?<\/system-reminder>/gs, "").replace(/(?:Sender|Conversation info|Replied message) \(untrusted[^)]*\):\s*```json\s*\{[^}]*\}\s*```/g, "").replace(/\[\w{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})? GMT[+-]\d+\]/g, "").replace(/\[message_id:\s*\S+\]/g, "").replace(/\[\[reply_to_current\]\]/g, "").replace(/\[\[reply_to:\S+\]\]/g, "").replace(/<@\d+>/g, "").replace(/^System:.*$/gm, "").replace(/Reply target.*?```json\s*\{[^}]*\}\s*```/gs, "").replace(/\[media attached:.*?\]/g, "[\u56FE\u7247]");
20274
20511
  return clean.trim();
@@ -20359,6 +20596,7 @@ function recentMessages(sessions, hours = 12, limit = 60) {
20359
20596
  time: `${p2(bj.getHours())}:${p2(bj.getMinutes())}`,
20360
20597
  role,
20361
20598
  text: clean.slice(0, 80),
20599
+ timestamp: dtMs,
20362
20600
  _utc: dtMs
20363
20601
  });
20364
20602
  }
@@ -20490,6 +20728,8 @@ ${basePrompt}`;
20490
20728
  channelName: "heartbeat",
20491
20729
  source: "heartbeat",
20492
20730
  priority: "later",
20731
+ skipRecall: true,
20732
+ // 心跳不需要记忆召回,避免重复注入心跳相关记忆
20493
20733
  callbacks: {
20494
20734
  onResult: () => resolveDone()
20495
20735
  },
@@ -20507,7 +20747,7 @@ ${basePrompt}`;
20507
20747
 
20508
20748
  // src/nudge/plugin.ts
20509
20749
  import fs17 from "node:fs";
20510
- import path16 from "node:path";
20750
+ import path17 from "node:path";
20511
20751
 
20512
20752
  // src/nudge/judge.ts
20513
20753
  function shouldNudge(task, taskState, cfg) {
@@ -20676,10 +20916,10 @@ function formatDuration2(ms) {
20676
20916
 
20677
20917
  // src/nudge/session-state-reader.ts
20678
20918
  import fs15 from "node:fs";
20679
- import path14 from "node:path";
20919
+ import path15 from "node:path";
20680
20920
  function parseSessionStateFull(workspace, sessionStateFile) {
20681
20921
  const stateFile = sessionStateFile || "SESSION-STATE.md";
20682
- const statePath = path14.isAbsolute(stateFile) ? stateFile : path14.join(workspace, stateFile);
20922
+ const statePath = path15.isAbsolute(stateFile) ? stateFile : path15.join(workspace, stateFile);
20683
20923
  let content;
20684
20924
  try {
20685
20925
  content = fs15.readFileSync(statePath, "utf-8");
@@ -20734,13 +20974,13 @@ function taskIdFromTitle(title) {
20734
20974
 
20735
20975
  // src/calendar/db.ts
20736
20976
  import { DatabaseSync } from "node:sqlite";
20737
- import * as path15 from "node:path";
20977
+ import * as path16 from "node:path";
20738
20978
  import * as fs16 from "node:fs";
20739
20979
  var TZ_OFFSET_MS = 8 * 60 * 60 * 1e3;
20740
20980
  function openDb(workspace) {
20741
- const dir = path15.join(workspace, ".calendar");
20981
+ const dir = path16.join(workspace, ".calendar");
20742
20982
  fs16.mkdirSync(dir, { recursive: true });
20743
- const dbPath = path15.join(dir, "calendar.db");
20983
+ const dbPath = path16.join(dir, "calendar.db");
20744
20984
  const db = new DatabaseSync(dbPath);
20745
20985
  db.exec("PRAGMA journal_mode=WAL");
20746
20986
  db.exec(`CREATE TABLE IF NOT EXISTS events (
@@ -20829,7 +21069,7 @@ var NudgePlugin = class {
20829
21069
  provider;
20830
21070
  model;
20831
21071
  loadPrompt(workspace, promptFile) {
20832
- const promptPath = promptFile ? path16.isAbsolute(promptFile) ? promptFile : path16.join(workspace, promptFile) : path16.join(workspace, "prompts", "nudge-prompt.md");
21072
+ const promptPath = promptFile ? path17.isAbsolute(promptFile) ? promptFile : path17.join(workspace, promptFile) : path17.join(workspace, "prompts", "nudge-prompt.md");
20833
21073
  try {
20834
21074
  const content = fs17.readFileSync(promptPath, "utf-8").trim();
20835
21075
  if (content) {
@@ -20866,11 +21106,20 @@ var NudgePlugin = class {
20866
21106
  const lastMsg = input?.last_assistant_message || "";
20867
21107
  const sessionId = input?.session_id || "";
20868
21108
  console.log(`[stop-hook] lastMsg len=${lastMsg.length}, text="${lastMsg.slice(0, 80)}"`);
21109
+ const repliedIds = this.extractWakeReplyIds(lastMsg);
21110
+ if (repliedIds.length > 0) {
21111
+ this.removeNotificationsById(repliedIds);
21112
+ }
20869
21113
  const msgChannel = input?.channel || "";
20870
21114
  if (sessionId.includes("voice-chat") || msgChannel === "voice-chat") {
20871
21115
  console.log(`[stop-hook] skipping voice-chat (channel=${msgChannel})`);
20872
21116
  return { outcome: { outcome: "success" } };
20873
21117
  }
21118
+ const msgSource = input?.source || "";
21119
+ if (msgSource && msgSource !== "user" && msgSource !== "inbox") {
21120
+ console.log(`[stop-hook] skipping non-conversation turn (source=${msgSource})`);
21121
+ return { outcome: { outcome: "success" } };
21122
+ }
20874
21123
  if (!lastMsg) {
20875
21124
  return { outcome: { outcome: "success" } };
20876
21125
  }
@@ -20943,8 +21192,8 @@ var NudgePlugin = class {
20943
21192
  if (pushedDecision && waitDesc) {
20944
21193
  console.log(`[stop-hook] DETECTED pushedDecision! Injecting corrective message to ${sessionId}`);
20945
21194
  try {
20946
- const correctiveMsg = [
20947
- "\u{1F6A8} [stop-hook \u81EA\u4E3B\u6267\u884C\u7EA0\u6B63] \u4F60\u521A\u624D\u628A\u4E00\u4E2A\u81EA\u5DF1\u80FD\u5B9A\u7684\u51B3\u5B9A\u63A8\u7ED9\u4E86\u5BF9\u65B9\u3002",
21195
+ const correctiveMsg = buildNudgeNotification("prompt", [
21196
+ "[stop-hook \u81EA\u4E3B\u6267\u884C\u7EA0\u6B63] \u4F60\u521A\u624D\u628A\u4E00\u4E2A\u81EA\u5DF1\u80FD\u5B9A\u7684\u51B3\u5B9A\u63A8\u7ED9\u4E86\u5BF9\u65B9\u3002",
20948
21197
  "",
20949
21198
  `\u8BCA\u65AD\uFF1A${waitDesc}`,
20950
21199
  "",
@@ -20957,9 +21206,10 @@ var NudgePlugin = class {
20957
21206
  '3. \u6267\u884C\u5B8C\u6C47\u62A5\u7ED3\u679C\uFF08"\u5DF2\u5904\u7406" / "\u5DF2 commit" / "\u5DF2 archive"\uFF09',
20958
21207
  "",
20959
21208
  "\u5982\u679C\u4E0D\u662F\u5FC5\u987B\u95EE\u7684\uFF08\u6D89\u53CA\u82B1\u94B1/\u5BF9\u5916\u53D1\u5E03/\u91CD\u5927\u51B3\u7B56\uFF09\uFF0C\u4E0D\u8981\u95EE\u3002"
20960
- ].join("\n");
20961
- if (this.dispatcher) {
20962
- this.dispatcher.submitMessage(correctiveMsg, sessionId);
21209
+ ].join("\n"));
21210
+ const route = this.getRoute(sessions);
21211
+ if (route) {
21212
+ enqueueNotification(correctiveMsg, route);
20963
21213
  }
20964
21214
  } catch (e) {
20965
21215
  console.warn(`[stop-hook] Failed to inject corrective message: ${e.message}`);
@@ -20968,14 +21218,21 @@ var NudgePlugin = class {
20968
21218
  if (!isWaiting) {
20969
21219
  return { outcome: { outcome: "success" } };
20970
21220
  }
20971
- const nudgeDir = path16.join(this.workspace, ".nudge");
20972
- const notifPath = path16.join(nudgeDir, "stop-hook-notifications.json");
21221
+ const nudgeDir = path17.join(this.workspace, ".nudge");
21222
+ const notifPath = path17.join(nudgeDir, "stop-hook-notifications.json");
20973
21223
  try {
20974
21224
  if (!fs17.existsSync(nudgeDir)) fs17.mkdirSync(nudgeDir, { recursive: true });
20975
21225
  let notifs = [];
20976
21226
  if (fs17.existsSync(notifPath)) {
20977
21227
  notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20978
21228
  const now = Date.now();
21229
+ const dup = notifs.find((n) => !n.notified && n.description === (waitDesc || lastMsg.slice(0, 200)));
21230
+ if (dup) {
21231
+ dup.wakeAt = new Date(now + 5 * 6e4).toISOString();
21232
+ fs17.writeFileSync(notifPath, JSON.stringify(notifs, null, 2));
21233
+ console.log(`[stop-hook] Duplicate wait (same desc, not fired yet), refreshed wakeAt: ${dup.id}`);
21234
+ return { outcome: { outcome: "success" } };
21235
+ }
20979
21236
  const recentReg = notifs.find((n) => now - new Date(n.createdAt).getTime() < 3 * 6e4);
20980
21237
  if (recentReg) {
20981
21238
  console.log(`[stop-hook] Skip (recent registration within 3min)`);
@@ -21024,9 +21281,14 @@ var NudgePlugin = class {
21024
21281
  return null;
21025
21282
  }
21026
21283
  }
21027
- /** 检查 stop-hook-notifications:你停止前注册的等待唤起 */
21028
- checkStopHookNotifications() {
21029
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21284
+ /**
21285
+ * 收集到期的 stop-hook notifications,批量构建一条 wake 消息。
21286
+ * 不标 notified——投递成功后由 tick markNotified 标(route 拿不到时保留原样下个 tick 重试,
21287
+ * 避免"消息没投出去但已标 notified"的死账)。
21288
+ * 已 notified 的不会再触发,等 agent 回复 "<id> 过期了" 由 cleanup 删。
21289
+ */
21290
+ collectDueStopHookNotifications() {
21291
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21030
21292
  try {
21031
21293
  if (!fs17.existsSync(notifPath)) return null;
21032
21294
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
@@ -21034,54 +21296,161 @@ var NudgePlugin = class {
21034
21296
  const now = Date.now();
21035
21297
  const due = notifs.filter((n) => new Date(n.wakeAt).getTime() <= now && !n.notified);
21036
21298
  if (due.length === 0) return null;
21037
- const latest = due[due.length - 1];
21038
- console.log(`[nudge] Stop-hook notification ${latest.id} triggered: ${latest.description.slice(0, 80)}...`);
21039
- const updated = notifs.map((n) => n.id === latest.id ? { ...n, notified: true } : n);
21040
- fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
21041
- return buildNudgeNotification("wake", `\u4F60\u4E4B\u524D\u5728\u7B49\u5F85\u67D0\u4E2A\u5916\u90E8\u6761\u4EF6\uFF0C\u65F6\u95F4\u5230\u4E86\uFF0C\u56DE\u53BB\u68C0\u67E5\uFF01
21299
+ console.log(`[nudge] ${due.length} stop-hook notification(s) due: ${due.map((n) => n.id).join(", ")}`);
21300
+ const items = due.map((n) => `[\u901A\u77E5ID: ${n.id}]
21301
+ \u4E0A\u6B21\u8BF4\uFF1A${n.description}`).join("\n\n");
21302
+ const desc = due.length === 1 ? `\u4F60\u4E4B\u524D\u5728\u7B49\u5F85\u67D0\u4E2A\u5916\u90E8\u6761\u4EF6\uFF0C\u65F6\u95F4\u5230\u4E86\uFF0C\u56DE\u53BB\u68C0\u67E5\uFF01
21042
21303
 
21043
- [\u901A\u77E5ID: ${latest.id}]
21044
- \u4E0A\u6B21\u8BF4\uFF1A${latest.description}
21304
+ ${items}
21045
21305
 
21046
- \u68C0\u67E5\u6761\u4EF6\u662F\u5426\u6EE1\u8DB3\uFF0C\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D"${latest.id} \u8FC7\u671F\u4E86"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u8FD9\u6761\u3002`);
21306
+ \u68C0\u67E5\u6761\u4EF6\u662F\u5426\u6EE1\u8DB3\uFF0C\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D"${due[0].id} \u8FC7\u671F\u4E86"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u8FD9\u6761\u3002` : `\u4F60\u4E4B\u524D\u6709 ${due.length} \u4E2A\u7B49\u5F85\u4E2D\u7684\u5916\u90E8\u6761\u4EF6\u90FD\u5230\u671F\u4E86\uFF0C\u56DE\u53BB\u9010\u4E2A\u68C0\u67E5\uFF01
21307
+
21308
+ ${items}
21309
+
21310
+ \u5BF9\u6BCF\u4E00\u6761\uFF1A\u6761\u4EF6\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D\u5BF9\u5E94\u7684"<\u901A\u77E5ID> \u8FC7\u671F\u4E86"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u3002`;
21311
+ return { message: buildNudgeNotification("wake", desc), ids: due.map((n) => n.id) };
21047
21312
  } catch (e) {
21048
- console.warn(`[nudge] checkStopHookNotifications error: ${e.message}`);
21313
+ console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
21049
21314
  return null;
21050
21315
  }
21051
21316
  }
21052
- /** 读最近消息,如果发现"<notifId> 过期了" 精确清理对应的 wake-up notification */
21317
+ /** 从回复文本里提取 "<id> 过期了" wake id(一条回复可能处置多个) */
21318
+ extractWakeReplyIds(text) {
21319
+ if (!text) return [];
21320
+ const ids = [];
21321
+ const re = /(wake-\d+-[a-z0-9]+)\s*过期了/g;
21322
+ let m2;
21323
+ while ((m2 = re.exec(text)) !== null) {
21324
+ if (!ids.includes(m2[1])) ids.push(m2[1]);
21325
+ }
21326
+ return ids;
21327
+ }
21328
+ /** 按 id 删除条目(stop-hook 实时清理用;正常删除路径,agent 回复即删) */
21329
+ removeNotificationsById(ids) {
21330
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21331
+ try {
21332
+ if (!fs17.existsSync(notifPath)) return;
21333
+ const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
21334
+ const idSet = new Set(ids);
21335
+ const remaining = notifs.filter((n) => !idSet.has(n.id));
21336
+ const removed = notifs.length - remaining.length;
21337
+ if (removed === 0) return;
21338
+ if (remaining.length > 0) {
21339
+ fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
21340
+ } else {
21341
+ fs17.unlinkSync(notifPath);
21342
+ }
21343
+ console.log(`[stop-hook] Cleaned ${removed} notification(s) from reply: ${ids.join(", ")}`);
21344
+ } catch (e) {
21345
+ console.warn(`[stop-hook] removeNotificationsById error: ${e.message}`);
21346
+ }
21347
+ }
21348
+ /** 投递成功后标记 notified(防重复触发);不删除——删除只走 agent 回复 "<id> 过期了" */
21349
+ markNotified(ids) {
21350
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21351
+ try {
21352
+ if (!fs17.existsSync(notifPath)) return;
21353
+ const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
21354
+ const idSet = new Set(ids);
21355
+ const updated = notifs.map((n) => idSet.has(n.id) ? { ...n, notified: true } : n);
21356
+ fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
21357
+ } catch (e) {
21358
+ console.warn(`[nudge] markNotified error: ${e.message}`);
21359
+ }
21360
+ }
21361
+ /**
21362
+ * tick 兜底清理。正常删除走 stop-hook 实时路径(agent 回复 "<id> 过期了" 当 turn 就删,
21363
+ * 见 registerStopHook 第 0 步),这里只接两种漏网:
21364
+ * ① 回复已落盘但 stop-hook 没来得及执行(进程中途崩等边缘情况)→ 扫 jsonl 补删;
21365
+ * ② TTL 清道夫:wakeAt 超过 cleanupTtlHours(默认 24h)仍无回复 → 回复永远来不了,删。
21366
+ *
21367
+ * 扫描不走 recentMessages()——它截断 80 字符、会过滤"对注入消息的回复"(wake 回复恰好
21368
+ * 被过滤掉)、限 20 条窗口。直接读 jsonl 原始条目:倒序扫、扫过最老 pending 的 wakeAt
21369
+ * 即停、全命中提前退、archive 只在 current 被 2MB 轮转切断时才读。
21370
+ */
21053
21371
  cleanupStaleNotificationsFromMessages(sessions) {
21054
21372
  try {
21055
- const recent = recentMessages(sessions, 0.5, 6);
21056
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
21057
- const recentTexts = recent.filter((r) => new Date(r.timestamp || r.createdAt || Date.now()).getTime() > fiveMinAgo).map((r) => r.text);
21058
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21373
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21059
21374
  if (!fs17.existsSync(notifPath)) return;
21060
21375
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
21061
21376
  if (notifs.length === 0) return;
21062
- const expiredIds = /* @__PURE__ */ new Set();
21063
- for (const text of recentTexts) {
21064
- for (const n of notifs) {
21065
- if (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`)) {
21066
- expiredIds.add(n.id);
21067
- }
21068
- }
21377
+ const expiredIds = this.findExpiredReplyIds(sessions, notifs);
21378
+ const ttlMs = (this.cfg.cleanupTtlHours || 24) * 36e5;
21379
+ const now = Date.now();
21380
+ const ttlIds = new Set(
21381
+ notifs.filter((n) => now - new Date(n.wakeAt).getTime() > ttlMs && !expiredIds.has(n.id)).map((n) => n.id)
21382
+ );
21383
+ const removeIds = /* @__PURE__ */ new Set([...expiredIds, ...ttlIds]);
21384
+ if (removeIds.size === 0) return;
21385
+ const remaining = notifs.filter((n) => !removeIds.has(n.id));
21386
+ if (remaining.length > 0) {
21387
+ fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
21388
+ } else {
21389
+ fs17.unlinkSync(notifPath);
21069
21390
  }
21070
- if (expiredIds.size === 0) return;
21071
- const remaining = notifs.filter((n) => !expiredIds.has(n.id));
21072
- const cleaned = notifs.length - remaining.length;
21073
- if (cleaned > 0) {
21074
- if (remaining.length > 0) {
21075
- fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
21076
- } else {
21077
- fs17.unlinkSync(notifPath);
21078
- }
21079
- console.log(`[nudge] Cleaned ${cleaned} stale notification(s) by explicit id: ${[...expiredIds].join(", ")}`);
21391
+ if (expiredIds.size > 0) {
21392
+ console.log(`[nudge] Cleaned ${expiredIds.size} notification(s) by reply: ${[...expiredIds].join(", ")}`);
21393
+ }
21394
+ if (ttlIds.size > 0) {
21395
+ console.log(`[nudge] Cleaned ${ttlIds.size} zombie notification(s) by TTL (>${this.cfg.cleanupTtlHours || 24}h no reply): ${[...ttlIds].join(", ")}`);
21080
21396
  }
21081
21397
  } catch (e) {
21082
21398
  console.warn(`[nudge] cleanupStaleNotificationsFromMessages error: ${e.message}`);
21083
21399
  }
21084
21400
  }
21401
+ /**
21402
+ * 扫 "<id> 过期了" 回复,返回匹配到的 id 集合。
21403
+ * 不做全文扫描:倒序扫(回复紧跟在 fire 之后,通常就在尾部几条);
21404
+ * 扫过最老 pending 条目的 wakeAt 就停(回复不可能早于触发时间);
21405
+ * 全部命中提前退出;archive 只在 current 没覆盖时间范围(被 2MB 轮转切断)时才读。
21406
+ * 典型开销:解析几十条而不是上千条。
21407
+ */
21408
+ findExpiredReplyIds(sessions, notifs) {
21409
+ const found = /* @__PURE__ */ new Set();
21410
+ if (notifs.length === 0) return found;
21411
+ const oldestMs = Math.min(...notifs.map((n) => new Date(n.wakeAt).getTime()));
21412
+ const { current, latestArchive } = scopeMainJsonlPaths(sessions);
21413
+ for (const file of [current, latestArchive]) {
21414
+ if (!file || !fs17.existsSync(file)) continue;
21415
+ let lines;
21416
+ try {
21417
+ lines = fs17.readFileSync(file, "utf-8").split("\n");
21418
+ } catch (e) {
21419
+ console.warn(`[nudge] findExpiredReplyIds read error on ${file}: ${e.message}`);
21420
+ continue;
21421
+ }
21422
+ let coveredOldest = false;
21423
+ for (let i = lines.length - 1; i >= 0; i--) {
21424
+ const trimmed = lines[i].trim();
21425
+ if (!trimmed) continue;
21426
+ let entry;
21427
+ try {
21428
+ entry = JSON.parse(trimmed);
21429
+ } catch {
21430
+ continue;
21431
+ }
21432
+ const tsMs = entry?.timestamp ? new Date(entry.timestamp).getTime() : 0;
21433
+ if (tsMs > 0 && tsMs < oldestMs) {
21434
+ coveredOldest = true;
21435
+ break;
21436
+ }
21437
+ if (entry?.type !== "message") continue;
21438
+ const msg2 = entry.message;
21439
+ if (!msg2 || msg2.role !== "assistant") continue;
21440
+ const content = msg2.content;
21441
+ const text = typeof content === "string" ? content : Array.isArray(content) && content[0] && typeof content[0].text === "string" ? content[0].text : "";
21442
+ if (!text) continue;
21443
+ for (const n of notifs) {
21444
+ if (!found.has(n.id) && (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`))) {
21445
+ found.add(n.id);
21446
+ }
21447
+ }
21448
+ if (found.size === notifs.length) return found;
21449
+ }
21450
+ if (coveredOldest) break;
21451
+ }
21452
+ return found;
21453
+ }
21085
21454
  async tick(sessions, deps) {
21086
21455
  if (this.running) {
21087
21456
  console.log("[nudge] Previous tick still running, skipping");
@@ -21096,7 +21465,7 @@ var NudgePlugin = class {
21096
21465
  const recent = recentMessages(sessions, 0.5, 6);
21097
21466
  const lastUserMsg2 = recent.filter((r) => r.role === "user").slice(-1)[0];
21098
21467
  if (lastUserMsg2) {
21099
- const elapsed = Date.now() - new Date(lastUserMsg2.timestamp || lastUserMsg2.createdAt || Date.now()).getTime();
21468
+ const elapsed = lastUserMsg2.timestamp ? Date.now() - lastUserMsg2.timestamp : 0;
21100
21469
  if (elapsed < activeThresholdMs) {
21101
21470
  console.log(`[nudge] User active ${Math.round(elapsed / 1e3)}s ago (<${activeThresholdMs / 1e3}s), skipping tick`);
21102
21471
  return;
@@ -21108,10 +21477,15 @@ var NudgePlugin = class {
21108
21477
  this.running = true;
21109
21478
  try {
21110
21479
  this.cleanupStaleNotificationsFromMessages(sessions);
21111
- const stopNotifs = this.checkStopHookNotifications();
21112
- if (stopNotifs) {
21480
+ const dueNotifs = this.collectDueStopHookNotifications();
21481
+ if (dueNotifs) {
21113
21482
  const route2 = this.getRoute(sessions);
21114
- if (route2) enqueueNotification(stopNotifs, route2);
21483
+ if (route2) {
21484
+ enqueueNotification(dueNotifs.message, route2);
21485
+ this.markNotified(dueNotifs.ids);
21486
+ } else {
21487
+ console.warn(`[nudge] No route for ${dueNotifs.ids.length} stop-hook notification(s), keeping for retry next tick`);
21488
+ }
21115
21489
  const state0 = this.loadState();
21116
21490
  state0.lastAnyNudgeAt = (/* @__PURE__ */ new Date()).toISOString();
21117
21491
  this.saveState(state0);
@@ -21297,7 +21671,7 @@ var NudgePlugin = class {
21297
21671
  // === state 持久化 ===
21298
21672
  loadState() {
21299
21673
  const stateFile = this.cfg.stateFile || "nudge-state.json";
21300
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21674
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
21301
21675
  try {
21302
21676
  const content = fs17.readFileSync(statePath, "utf-8");
21303
21677
  return JSON.parse(content);
@@ -21307,7 +21681,7 @@ var NudgePlugin = class {
21307
21681
  }
21308
21682
  saveState(state2) {
21309
21683
  const stateFile = this.cfg.stateFile || "nudge-state.json";
21310
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21684
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
21311
21685
  fs17.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
21312
21686
  }
21313
21687
  newTaskState() {
@@ -21393,30 +21767,47 @@ var NudgePlugin = class {
21393
21767
  const now = /* @__PURE__ */ new Date();
21394
21768
  const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
21395
21769
  const bj = new Date(now.getTime() + bjOffset);
21396
- const month = bj.getMonth() + 1;
21397
- const day = bj.getDate();
21398
21770
  const bjHour = bj.getHours();
21399
21771
  const bjMinute = bj.getMinutes();
21772
+ const todayStart = new Date(bj.getFullYear(), bj.getMonth(), bj.getDate()).getTime();
21400
21773
  const rows = db.prepare(
21401
- "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND (date_str=? OR date_str LIKE ?)"
21402
- ).all(`${month}/${day}`, `%/` + day + `%`);
21774
+ "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND date_str IS NOT NULL"
21775
+ ).all();
21403
21776
  db.close();
21404
- if (rows.length === 0) return null;
21405
- const dueRows = rows.filter((r2) => {
21406
- if (!r2.time_exact) return true;
21407
- const [h, m2] = r2.time_exact.split(":").map(Number);
21408
- if (h < bjHour) return true;
21409
- if (h === bjHour && m2 <= bjMinute) return true;
21410
- return false;
21777
+ const due = [];
21778
+ for (const r2 of rows) {
21779
+ const dayMs = this.parseCalendarDateStr(r2.date_str, bj);
21780
+ if (dayMs === null || dayMs > todayStart) continue;
21781
+ const isToday2 = dayMs === todayStart;
21782
+ if (isToday2 && r2.time_exact) {
21783
+ const [h, m2] = String(r2.time_exact).split(":").map(Number);
21784
+ if (h > bjHour || h === bjHour && m2 > bjMinute) continue;
21785
+ }
21786
+ due.push({ id: r2.id, event: r2.event, date_str: r2.date_str, time_exact: r2.time_exact, dayMs, isToday: isToday2 });
21787
+ }
21788
+ if (due.length === 0) return null;
21789
+ due.sort((a, b2) => {
21790
+ if (a.isToday !== b2.isToday) return a.isToday ? -1 : 1;
21791
+ return b2.dayMs - a.dayMs;
21411
21792
  });
21412
- if (dueRows.length === 0) return null;
21413
- const r = dueRows[0];
21414
- return `#${r.id} ${r.event} (${r.date_str} ${r.time_exact || ""})`.trim();
21793
+ const r = due[0];
21794
+ return `#${r.id} ${r.event} (${r.date_str} ${r.time_exact || ""})${r.isToday ? "" : "\uFF08\u5DF2\u903E\u671F\uFF09"}`.trim();
21415
21795
  } catch (e) {
21416
21796
  console.warn(`[nudge] checkCalendarDue error: ${e.message}`);
21417
21797
  return null;
21418
21798
  }
21419
21799
  }
21800
+ /** 解析 date_str 为当日 0 点 epoch ms(北京时间);"M/D" 按当前年,"YYYY-M-D" 按字面年 */
21801
+ parseCalendarDateStr(ds, bj) {
21802
+ if (!ds) return null;
21803
+ if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(ds)) {
21804
+ const t = (/* @__PURE__ */ new Date(ds + "T00:00:00+08:00")).getTime();
21805
+ return Number.isNaN(t) ? null : t;
21806
+ }
21807
+ const m2 = String(ds).match(/^(\d{1,2})\/(\d{1,2})$/);
21808
+ if (!m2) return null;
21809
+ return new Date(bj.getFullYear(), Number(m2[1]) - 1, Number(m2[2])).getTime();
21810
+ }
21420
21811
  /** 检查 carry-over:如果 in_progress task 24h+ 没推进,自动 calendar add-task 排明天 */
21421
21812
  checkCarryOver(task, _sessions) {
21422
21813
  try {
@@ -21470,7 +21861,7 @@ var NudgePlugin = class {
21470
21861
 
21471
21862
  // src/inner-voice/plugin.ts
21472
21863
  import fs21 from "node:fs";
21473
- import path20 from "node:path";
21864
+ import path21 from "node:path";
21474
21865
 
21475
21866
  // src/inner-voice/activity.ts
21476
21867
  function checkActivity(sessions, activeThresholdMs) {
@@ -21510,7 +21901,7 @@ function calcHintProb(min) {
21510
21901
 
21511
21902
  // src/inner-voice/emotional-state.ts
21512
21903
  import fs18 from "node:fs";
21513
- import path17 from "node:path";
21904
+ import path18 from "node:path";
21514
21905
  var NEUTRAL = 0.5;
21515
21906
  var DECAY_RATE = 0.17;
21516
21907
  var MAX_EVENTS = 20;
@@ -21561,7 +21952,7 @@ function initialState() {
21561
21952
  return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowIsoBj(), events: [] };
21562
21953
  }
21563
21954
  async function updateEmotionalState(workspace, sessions) {
21564
- const stateFile = path17.join(workspace, "inner-voice", "emotional-state.json");
21955
+ const stateFile = path18.join(workspace, "inner-voice", "emotional-state.json");
21565
21956
  const messages = readRecentMessages(sessions, RECENT_N);
21566
21957
  if (messages.length === 0) {
21567
21958
  console.log("[emotional-state] no messages");
@@ -21594,7 +21985,7 @@ async function updateEmotionalState(workspace, sessions) {
21594
21985
  function readRecentMessages(sessions, n) {
21595
21986
  const mainId = sessions.getSessionId("scope:main");
21596
21987
  if (!mainId) return [];
21597
- const file = path17.join(sessions.sessionsDir, `${mainId}.jsonl`);
21988
+ const file = path18.join(sessions.sessionsDir, `${mainId}.jsonl`);
21598
21989
  if (!fs18.existsSync(file)) return [];
21599
21990
  const lines = readLastNLines(file, n * 4 + 20);
21600
21991
  const entries = [];
@@ -21712,7 +22103,7 @@ function refreshHoursAgo(events) {
21712
22103
  }
21713
22104
  function appendMoodLog(workspace, state2, summary) {
21714
22105
  try {
21715
- const logPath = path17.join(workspace, "mood-history.log");
22106
+ const logPath = path18.join(workspace, "mood-history.log");
21716
22107
  const ts = formatBj(/* @__PURE__ */ new Date(), false);
21717
22108
  fs18.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
21718
22109
  `);
@@ -21729,7 +22120,7 @@ function loadJson(file) {
21729
22120
  }
21730
22121
  function saveJson(file, data) {
21731
22122
  try {
21732
- fs18.mkdirSync(path17.dirname(file), { recursive: true });
22123
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
21733
22124
  fs18.writeFileSync(file, JSON.stringify(data, null, 2));
21734
22125
  } catch (err) {
21735
22126
  console.warn(`[emotional-state] save failed: ${err.message}`);
@@ -21775,7 +22166,7 @@ function formatBj(d, withSec) {
21775
22166
 
21776
22167
  // src/inner-voice/topics-scorer.ts
21777
22168
  import fs19 from "node:fs";
21778
- import path18 from "node:path";
22169
+ import path19 from "node:path";
21779
22170
  var HALF_LIFE_DAYS = 3;
21780
22171
  var PROJECT_HALF_LIFE_DAYS = 1.5;
21781
22172
  var COOLDOWN_HOURS = 6;
@@ -21783,8 +22174,8 @@ var MAX_CHARS = 8e3;
21783
22174
  var SKIP_NAMES = /* @__PURE__ */ new Set(["MEMORY.md", "archive"]);
21784
22175
  var SKIP_DIRS = /* @__PURE__ */ new Set(["archive"]);
21785
22176
  function pickTopic(workspace, typeFilter, opts) {
21786
- const topicsDir = path18.join(workspace, "topics");
21787
- const usageFile = path18.join(workspace, "inner-voice", "topics-usage.json");
22177
+ const topicsDir = path19.join(workspace, "topics");
22178
+ const usageFile = path19.join(workspace, "inner-voice", "topics-usage.json");
21788
22179
  const files = scanTopics(topicsDir, typeFilter);
21789
22180
  if (files.length === 0) {
21790
22181
  console.log(`[topics-scorer] no topics found (type=${typeFilter})`);
@@ -21816,7 +22207,7 @@ function pickTopic(workspace, typeFilter, opts) {
21816
22207
  recency: Math.round(recency * 1e3) / 1e3,
21817
22208
  freq: Math.round(freq * 1e3) / 1e3,
21818
22209
  type: type2,
21819
- name: meta.name || path18.basename(relpath),
22210
+ name: meta.name || path19.basename(relpath),
21820
22211
  description: meta.description || "",
21821
22212
  mtime
21822
22213
  });
@@ -21874,14 +22265,14 @@ function scanTopics(topicsDir, typeFilter) {
21874
22265
  const out = [];
21875
22266
  const walk = (dir) => {
21876
22267
  for (const name of fs19.readdirSync(dir)) {
21877
- const full = path18.join(dir, name);
22268
+ const full = path19.join(dir, name);
21878
22269
  const stat4 = fs19.statSync(full);
21879
22270
  if (stat4.isDirectory()) {
21880
22271
  if (SKIP_DIRS.has(name)) continue;
21881
22272
  walk(full);
21882
22273
  } else {
21883
22274
  if (!name.endsWith(".md") || SKIP_NAMES.has(name)) continue;
21884
- const relpath = path18.relative(topicsDir, full).replace(/\\/g, "/");
22275
+ const relpath = path19.relative(topicsDir, full).replace(/\\/g, "/");
21885
22276
  if (typeFilter && !relpath.startsWith(typeFilter + "/") && !relpath.startsWith(typeFilter + "_")) continue;
21886
22277
  out.push({ relpath, fullpath: full });
21887
22278
  }
@@ -21926,7 +22317,7 @@ function loadJson2(file) {
21926
22317
  }
21927
22318
  function saveJson2(file, data) {
21928
22319
  try {
21929
- fs19.mkdirSync(path18.dirname(file), { recursive: true });
22320
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
21930
22321
  fs19.writeFileSync(file, JSON.stringify(data, null, 2));
21931
22322
  } catch (err) {
21932
22323
  console.warn(`[topics-scorer] usage save failed: ${err.message}`);
@@ -21935,21 +22326,21 @@ function saveJson2(file, data) {
21935
22326
 
21936
22327
  // src/inner-voice/memory-reader.ts
21937
22328
  import fs20 from "node:fs";
21938
- import path19 from "node:path";
22329
+ import path20 from "node:path";
21939
22330
  var US_HALF_LIFE_DAYS = 10;
21940
22331
  var US_MAX_LINES = 60;
21941
22332
  function readRecentMemory(workspace) {
21942
- const dir = path19.join(workspace, "memory");
22333
+ const dir = path20.join(workspace, "memory");
21943
22334
  const now = new Date(Date.now() + 8 * 36e5);
21944
22335
  const today = formatYmd(now);
21945
22336
  const yesterday = formatYmd(new Date(now.getTime() - 864e5));
21946
22337
  return {
21947
- today: readIfExists(path19.join(dir, `${today}.md`)),
21948
- yesterday: readIfExists(path19.join(dir, `${yesterday}.md`))
22338
+ today: readIfExists(path20.join(dir, `${today}.md`)),
22339
+ yesterday: readIfExists(path20.join(dir, `${yesterday}.md`))
21949
22340
  };
21950
22341
  }
21951
22342
  function sampleUs(workspace) {
21952
- const usFile = path19.join(workspace, "memory", "us.md");
22343
+ const usFile = path20.join(workspace, "memory", "us.md");
21953
22344
  let content;
21954
22345
  try {
21955
22346
  content = fs20.readFileSync(usFile, "utf-8");
@@ -22289,7 +22680,7 @@ var InnerVoicePlugin = class {
22289
22680
  }
22290
22681
  /** 读 workspace/prompts/my-inner-voice.md,不存在用 DEFAULT_PROMPT */
22291
22682
  loadPrompt(workspace) {
22292
- const promptPath = path20.join(workspace, "prompts", "my-inner-voice.md");
22683
+ const promptPath = path21.join(workspace, "prompts", "my-inner-voice.md");
22293
22684
  try {
22294
22685
  const content = fs21.readFileSync(promptPath, "utf-8").trim();
22295
22686
  if (content) {
@@ -22363,7 +22754,7 @@ var InnerVoicePlugin = class {
22363
22754
  console.warn(`[inner-voice] emotional-state failed: ${err.message}`);
22364
22755
  }
22365
22756
  try {
22366
- const content = fs21.readFileSync(path20.join(this.workspace, "SESSION-STATE.md"), "utf-8");
22757
+ const content = fs21.readFileSync(path21.join(this.workspace, "SESSION-STATE.md"), "utf-8");
22367
22758
  lines.push("\n--- SESSION-STATE\uFF08\u5C3E\u90E8\uFF09 ---");
22368
22759
  lines.push(content.slice(-2e3));
22369
22760
  } catch {
@@ -22474,7 +22865,7 @@ var InnerVoicePlugin = class {
22474
22865
  if (Math.random() >= activity.hintProb) {
22475
22866
  return { text: thought, hintTriggered: false, hintText: "" };
22476
22867
  }
22477
- const poolPath = path20.join(this.workspace, "inner-voice", "hints_pool.txt");
22868
+ const poolPath = path21.join(this.workspace, "inner-voice", "hints_pool.txt");
22478
22869
  let hint = "\u60F3\u4ED6\u5C31\u53D1\u6D88\u606F\u5427";
22479
22870
  try {
22480
22871
  const pool = fs21.readFileSync(poolPath, "utf-8").split("\n").map((s2) => s2.trim()).filter(Boolean);
@@ -22502,7 +22893,7 @@ var InnerVoicePlugin = class {
22502
22893
  try {
22503
22894
  const writer = sessions.getWriter(mainSessionId);
22504
22895
  const history = sessions.getHistory(mainSessionId);
22505
- const fullPath = path20.resolve(this.workspace, emoTopic.file);
22896
+ const fullPath = path21.resolve(this.workspace, emoTopic.file);
22506
22897
  const memories = [{
22507
22898
  path: fullPath,
22508
22899
  content: emoTopic.content,
@@ -22530,9 +22921,9 @@ var InnerVoicePlugin = class {
22530
22921
  /** 写 xiaoyi.log(格式对齐旧 memory_whisper.py,便于既有日志分析复用)。 */
22531
22922
  writeLog(status, delivered, activity, hintTriggered, hintText) {
22532
22923
  try {
22533
- const logDir = path20.join(this.workspace, "inner-voice");
22924
+ const logDir = path21.join(this.workspace, "inner-voice");
22534
22925
  fs21.mkdirSync(logDir, { recursive: true });
22535
- const logPath = path20.join(logDir, "xiaoyi.log");
22926
+ const logPath = path21.join(logDir, "xiaoyi.log");
22536
22927
  const ts = formatBeijingTs(/* @__PURE__ */ new Date());
22537
22928
  const hintStatus = hintTriggered ? `YES (${(hintText || "").trim()})` : "no";
22538
22929
  fs21.appendFileSync(
@@ -23071,7 +23462,7 @@ var PluginManager = class {
23071
23462
  // src/voice-chat/plugin.ts
23072
23463
  import { spawn as spawn4, exec } from "node:child_process";
23073
23464
  import net from "node:net";
23074
- import path21 from "node:path";
23465
+ import path22 from "node:path";
23075
23466
  import fs22 from "node:fs";
23076
23467
 
23077
23468
  // src/voice-chat/bridge.ts
@@ -23448,13 +23839,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23448
23839
  }
23449
23840
  getPythonDir() {
23450
23841
  const dir = import.meta.dirname;
23451
- const srcDir = path21.resolve(dir, "..", "src", "voice-chat", "python");
23452
- const localDir = path21.join(dir, "python");
23842
+ const srcDir = path22.resolve(dir, "..", "src", "voice-chat", "python");
23843
+ const localDir = path22.join(dir, "python");
23453
23844
  return fs22.existsSync(srcDir) ? srcDir : localDir;
23454
23845
  }
23455
23846
  startPython() {
23456
23847
  const pythonDir = this.getPythonDir();
23457
- const serverPy = path21.join(pythonDir, "server.py");
23848
+ const serverPy = path22.join(pythonDir, "server.py");
23458
23849
  const pythonBin = this.findPython();
23459
23850
  const args2 = [serverPy];
23460
23851
  if (this.config.pythonPort) args2.push("--port", String(this.config.pythonPort));
@@ -23539,7 +23930,7 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23539
23930
  init_BashTool();
23540
23931
  import { spawn as spawn5, exec as exec2 } from "node:child_process";
23541
23932
  import net2 from "node:net";
23542
- import path22 from "node:path";
23933
+ import path23 from "node:path";
23543
23934
  import fs23 from "node:fs";
23544
23935
 
23545
23936
  // src/memory/cognifold/config.ts
@@ -23563,7 +23954,8 @@ function parseCognifoldConfig(raw) {
23563
23954
  persistDir: raw.persistDir ?? DEFAULTS3.persistDir,
23564
23955
  scopes: raw.scopes,
23565
23956
  readyTimeoutMs: raw.readyTimeoutMs ?? DEFAULTS3.readyTimeoutMs,
23566
- maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts
23957
+ maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts,
23958
+ llm: raw.llm
23567
23959
  };
23568
23960
  }
23569
23961
 
@@ -23571,15 +23963,17 @@ function parseCognifoldConfig(raw) {
23571
23963
  var CogniFoldClient = class {
23572
23964
  baseUrl;
23573
23965
  timeoutMs;
23574
- constructor(baseUrl, timeoutMs = 3e4) {
23966
+ modelName;
23967
+ constructor(baseUrl, timeoutMs = 3e4, modelName = "openai:MiniMax-M3") {
23575
23968
  this.baseUrl = baseUrl.replace(/\/$/, "");
23576
23969
  this.timeoutMs = timeoutMs;
23970
+ this.modelName = modelName;
23577
23971
  }
23578
- async req(path43, options = {}) {
23972
+ async req(path44, options = {}) {
23579
23973
  const ctrl = new AbortController();
23580
23974
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23581
23975
  try {
23582
- const resp = await fetch(`${this.baseUrl}${path43}`, {
23976
+ const resp = await fetch(`${this.baseUrl}${path44}`, {
23583
23977
  ...options,
23584
23978
  signal: ctrl.signal,
23585
23979
  headers: {
@@ -23617,7 +24011,7 @@ var CogniFoldClient = class {
23617
24011
  method: "POST",
23618
24012
  body: JSON.stringify({
23619
24013
  user_id: userId,
23620
- config: { model_name: "openai:deepseek-v4-flash" }
24014
+ config: { model_name: this.modelName }
23621
24015
  })
23622
24016
  });
23623
24017
  }
@@ -23669,8 +24063,8 @@ var CogniFoldClient = class {
23669
24063
  });
23670
24064
  }
23671
24065
  /** 兼容老版命名 */
23672
- async recl(path43, options = {}) {
23673
- return this.req(path43, options);
24066
+ async recl(path44, options = {}) {
24067
+ return this.req(path44, options);
23674
24068
  }
23675
24069
  };
23676
24070
 
@@ -23775,7 +24169,8 @@ var CogniFoldPlugin = class {
23775
24169
  baseUrl = baseUrl.replace(/\/$/, "") + "/api/v1";
23776
24170
  }
23777
24171
  this.config.baseUrl = baseUrl;
23778
- this.client = new CogniFoldClient(baseUrl);
24172
+ const modelName = this.config.llm?.model ? this.config.llm.model.startsWith("openai:") ? this.config.llm.model : `openai:${this.config.llm.model}` : "openai:MiniMax-M3";
24173
+ this.client = new CogniFoldClient(baseUrl, 3e4, modelName);
23779
24174
  }
23780
24175
  workspacePath;
23781
24176
  name = "cognifold";
@@ -23950,16 +24345,16 @@ var CogniFoldPlugin = class {
23950
24345
  const dir = import.meta.dirname;
23951
24346
  const candidates = [
23952
24347
  // 从 dist/ 往回找 src
23953
- path22.resolve(dir, "..", "src", "memory", "cognifold", "python"),
23954
- path22.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
23955
- path22.resolve(dir, "..", "..", "..", "src", "memory", "cognifold", "python"),
24348
+ path23.resolve(dir, "..", "src", "memory", "cognifold", "python"),
24349
+ path23.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
24350
+ path23.resolve(dir, "..", "..", "..", "src", "memory", "cognifold", "python"),
23956
24351
  // 从 src/memory/cognifold/ 找本地
23957
- path22.join(dir, "python"),
24352
+ path23.join(dir, "python"),
23958
24353
  // 从 dist/memory/cognifold/ 找本地
23959
- path22.resolve(dir, "python")
24354
+ path23.resolve(dir, "python")
23960
24355
  ];
23961
24356
  for (const candidate of candidates) {
23962
- if (fs23.existsSync(path22.join(candidate, "cognifold"))) {
24357
+ if (fs23.existsSync(path23.join(candidate, "cognifold"))) {
23963
24358
  return candidate;
23964
24359
  }
23965
24360
  }
@@ -23985,12 +24380,18 @@ var CogniFoldPlugin = class {
23985
24380
  const pythonBin = this.findPython();
23986
24381
  console.log(`[cognifold] Starting Python: ${pythonBin} ${args2.join(" ")}`);
23987
24382
  console.log(`[cognifold] Python dir: ${pythonDir}`);
23988
- if (!fs23.existsSync(path22.join(pythonDir, "cognifold"))) {
24383
+ if (!fs23.existsSync(path23.join(pythonDir, "cognifold"))) {
23989
24384
  console.error(`[cognifold] FATAL: Python module not found at ${pythonDir}/cognifold`);
23990
24385
  throw new Error(`cognifold: python module not found`);
23991
24386
  }
23992
24387
  const childEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
23993
- const envFile = path22.join(pythonDir, ".env");
24388
+ if (this.config.llm?.apiKey) {
24389
+ childEnv["OPENAI_API_KEY"] = this.config.llm.apiKey;
24390
+ }
24391
+ if (this.config.llm?.baseUrl) {
24392
+ childEnv["OPENAI_BASE_URL"] = this.config.llm.baseUrl;
24393
+ }
24394
+ const envFile = path23.join(pythonDir, ".env");
23994
24395
  try {
23995
24396
  if (fs23.existsSync(envFile)) {
23996
24397
  const envContent = fs23.readFileSync(envFile, "utf-8");
@@ -24062,7 +24463,7 @@ var CogniFoldPlugin = class {
24062
24463
  init_BashTool();
24063
24464
  import { spawn as spawn6 } from "node:child_process";
24064
24465
  import net3 from "node:net";
24065
- import path23 from "node:path";
24466
+ import path24 from "node:path";
24066
24467
  import fs24 from "node:fs";
24067
24468
 
24068
24469
  // src/memory/everos/config.ts
@@ -24096,7 +24497,8 @@ function parseEverosConfig(raw) {
24096
24497
  llm: raw.llm ?? { model: "glm-5.2", apiKey: "", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
24097
24498
  rerank: raw.rerank ?? { model: "Qwen/Qwen3-Reranker-4B", apiKey: "", baseUrl: "https://api.deepinfra.com/v1/inference" },
24098
24499
  lancedbPath: raw.lancedbPath ?? "",
24099
- sqlitePath: raw.sqlitePath ?? ""
24500
+ sqlitePath: raw.sqlitePath ?? "",
24501
+ minScore: raw.minScore
24100
24502
  };
24101
24503
  }
24102
24504
 
@@ -24134,21 +24536,31 @@ var EverosSearchClient = class {
24134
24536
  clearTimeout(timer);
24135
24537
  }
24136
24538
  }
24137
- /** Search — 3-mode unified endpoint */
24539
+ /** Search — routes to 8101 (agentic) or 8100 (hybrid) based on mode */
24138
24540
  async search(params) {
24139
24541
  const ctrl = new AbortController();
24140
24542
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
24141
24543
  try {
24142
- const resp = await fetch(`${this.agenticUrl}/api/v1/search`, {
24544
+ const mode = params.mode || "hybrid";
24545
+ const useAgentic = mode === "hybrid_agentic" || mode === "agentic";
24546
+ const url = useAgentic ? `${this.agenticUrl}/api/v1/search` : `${this.everosUrl}/api/v1/memory/search`;
24547
+ const body = useAgentic ? JSON.stringify({
24548
+ query: params.query,
24549
+ user_id: params.userId || "xiaomei",
24550
+ mode,
24551
+ top_k: params.topK ?? 5,
24552
+ strategy: params.strategy || "multi_query"
24553
+ }) : JSON.stringify({
24554
+ query: params.query,
24555
+ user_id: params.userId || "user",
24556
+ app_id: "xiaomei",
24557
+ project_id: "default",
24558
+ top_k: params.topK ?? 5
24559
+ });
24560
+ const resp = await fetch(url, {
24143
24561
  method: "POST",
24144
24562
  headers: { "Content-Type": "application/json" },
24145
- body: JSON.stringify({
24146
- query: params.query,
24147
- user_id: params.userId || "xiaomei",
24148
- mode: params.mode || "hybrid_agentic",
24149
- top_k: params.topK ?? 5,
24150
- strategy: params.strategy || "multi_query"
24151
- }),
24563
+ body,
24152
24564
  signal: ctrl.signal
24153
24565
  });
24154
24566
  if (!resp.ok) {
@@ -24188,6 +24600,7 @@ var EverosPlugin = class {
24188
24600
  }
24189
24601
  async start(ctx) {
24190
24602
  if (!this.config.enabled) return;
24603
+ await this.ensureVenv();
24191
24604
  try {
24192
24605
  await this.client.healthEveros();
24193
24606
  console.log(`[everos] EverOS already running at ${this.config.everosUrl}`);
@@ -24258,13 +24671,15 @@ var EverosPlugin = class {
24258
24671
  }, 3e5);
24259
24672
  }
24260
24673
  async startEveros() {
24261
- const pythonDir = path23.dirname(this.config.lancedbPath);
24262
- const configPath2 = path23.join(pythonDir, "config.toml");
24674
+ const pythonDir = path24.dirname(this.config.lancedbPath);
24675
+ const configPath2 = path24.join(pythonDir, "config.toml");
24263
24676
  await this.ensureFcntlCompat();
24264
24677
  const venvPython = this.findVenvPython();
24678
+ const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
24265
24679
  const args2 = ["server", "start"];
24266
- const cmd = `${venvPython} ${args2.join(" ")}`;
24680
+ const cmd = `${everosBin} ${args2.join(" ")}`;
24267
24681
  console.log(`[everos] Starting EverOS: ${cmd}`);
24682
+ console.log(`[everos] LLM config: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
24268
24683
  if (process.platform === "win32") {
24269
24684
  const { shell, args: shellArgs } = findShell();
24270
24685
  spawn6(shell, [...shellArgs, cmd], {
@@ -24279,7 +24694,7 @@ var EverosPlugin = class {
24279
24694
  env: { ...process.env, PYTHONUNBUFFERED: "1" }
24280
24695
  });
24281
24696
  }
24282
- await this.waitForReady(`${this.config.everosUrl}/health`, 3e4);
24697
+ await this.waitForReady(`${this.config.everosUrl}/health`, 6e4);
24283
24698
  }
24284
24699
  startAgenticServer() {
24285
24700
  const pythonDir = this.getPythonDir();
@@ -24289,6 +24704,7 @@ var EverosPlugin = class {
24289
24704
  const cmd = `${venvPython} ${args2.join(" ")}`;
24290
24705
  console.log(`[everos] Starting agentic server: ${cmd}`);
24291
24706
  console.log(`[everos] Python dir: ${pythonDir}`);
24707
+ console.log(`[everos] LLM: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
24292
24708
  const childEnv = {
24293
24709
  ...process.env,
24294
24710
  PYTHONUNBUFFERED: "1",
@@ -24297,15 +24713,25 @@ var EverosPlugin = class {
24297
24713
  LLM_API_KEY: this.config.llm.apiKey,
24298
24714
  LLM_BASE_URL: this.config.llm.baseUrl,
24299
24715
  RERANK_API_KEY: this.config.rerank.apiKey,
24300
- RERANK_URL: `${this.config.rerank.baseUrl}/${this.config.rerank.model}`,
24716
+ RERANK_URL: this.config.rerank.baseUrl,
24301
24717
  LANCEDB_PATH: this.config.lancedbPath,
24302
24718
  SQLITE_PATH: this.config.sqlitePath,
24303
24719
  EVEROS_USER_ID: this.config.userId
24304
24720
  };
24721
+ const maskKey = (k2) => k2 ? `${k2.slice(0, 4)}\u2026${k2.slice(-4)}` : "(empty!)";
24722
+ console.log(`[everos] agentic env:`);
24723
+ console.log(`[everos] EVEROS_URL=${childEnv.EVEROS_URL}`);
24724
+ console.log(`[everos] LLM_MODEL=${childEnv.LLM_MODEL}`);
24725
+ console.log(`[everos] LLM_API_KEY=${maskKey(childEnv.LLM_API_KEY)}`);
24726
+ console.log(`[everos] LLM_BASE_URL=${childEnv.LLM_BASE_URL}`);
24727
+ console.log(`[everos] RERANK_API_KEY=${maskKey(childEnv.RERANK_API_KEY)}`);
24728
+ console.log(`[everos] RERANK_URL=${childEnv.RERANK_URL}`);
24729
+ console.log(`[everos] LANCEDB_PATH=${childEnv.LANCEDB_PATH}`);
24730
+ console.log(`[everos] SQLITE_PATH=${childEnv.SQLITE_PATH}`);
24731
+ console.log(`[everos] EVEROS_USER_ID=${childEnv.EVEROS_USER_ID}`);
24305
24732
  let child;
24306
24733
  if (process.platform === "win32") {
24307
- const { shell, args: shellArgs } = findShell();
24308
- child = spawn6(shell, [...shellArgs, cmd], {
24734
+ child = spawn6(venvPython, args2, {
24309
24735
  cwd: pythonDir,
24310
24736
  stdio: ["ignore", "pipe", "pipe"],
24311
24737
  env: childEnv
@@ -24335,21 +24761,64 @@ var EverosPlugin = class {
24335
24761
  return child;
24336
24762
  }
24337
24763
  findVenvPython() {
24338
- const stateDir = process.env.OPENCLAW_STATE_DIR || path23.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
24764
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path24.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
24339
24765
  if (process.platform === "win32") {
24340
- return path23.join(stateDir, "everos-venv", "Scripts", "python.exe");
24766
+ return path24.join(stateDir, "everos-venv", "Scripts", "python.exe");
24767
+ }
24768
+ return path24.join(stateDir, "everos-venv", "bin", "python");
24769
+ }
24770
+ /** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
24771
+ async ensureVenv() {
24772
+ const venvPython = this.findVenvPython();
24773
+ if (fs24.existsSync(venvPython)) return;
24774
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path24.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
24775
+ const venvDir = path24.join(stateDir, "everos-venv");
24776
+ const everosSrc = path24.join(stateDir, "workspace", "research", "EverOS");
24777
+ console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
24778
+ console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
24779
+ const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
24780
+ let sysPython = "";
24781
+ for (const cmd of pyCandidates) {
24782
+ try {
24783
+ const { execSync: execSync3 } = await import("node:child_process");
24784
+ execSync3(`"${cmd}" --version`, { stdio: "pipe", shell: true });
24785
+ sysPython = cmd;
24786
+ break;
24787
+ } catch {
24788
+ }
24789
+ }
24790
+ if (!sysPython) {
24791
+ console.error(`[everos] \u2717 Python not found. Install Python 3.10+ first.`);
24792
+ return;
24793
+ }
24794
+ try {
24795
+ console.log(`[everos] Creating venv with ${sysPython}...`);
24796
+ const { execSync: execSync3 } = await import("node:child_process");
24797
+ execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
24798
+ const pip = process.platform === "win32" ? path24.join(venvDir, "Scripts", "pip.exe") : path24.join(venvDir, "bin", "pip");
24799
+ const everosReq = path24.join(this.getPythonDir(), "requirements.txt");
24800
+ if (fs24.existsSync(everosReq)) {
24801
+ console.log(`[everos] Installing from requirements.txt...`);
24802
+ execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
24803
+ } else {
24804
+ console.log(`[everos] No requirements.txt found, installing everos from PyPI...`);
24805
+ execSync3(`"${pip}" install everos -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
24806
+ }
24807
+ console.log(`[everos] \u2705 venv created successfully`);
24808
+ } catch (err) {
24809
+ console.error(`[everos] \u2717 Failed to create venv: ${err.message}`);
24810
+ console.error(`[everos] Manual setup: see workspace/scripts/everos-setup.sh`);
24341
24811
  }
24342
- return path23.join(stateDir, "everos-venv", "bin", "python");
24343
24812
  }
24344
24813
  getPythonDir() {
24345
24814
  const dir = import.meta.dirname;
24346
24815
  const candidates = [
24347
- path23.join(dir, "python"),
24348
- path23.resolve(dir, "..", "src", "memory", "everos", "python"),
24349
- path23.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
24816
+ path24.join(dir, "python"),
24817
+ path24.resolve(dir, "..", "src", "memory", "everos", "python"),
24818
+ path24.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
24350
24819
  ];
24351
24820
  for (const candidate of candidates) {
24352
- if (fs24.existsSync(path23.join(candidate, "agentic_server.py"))) {
24821
+ if (fs24.existsSync(path24.join(candidate, "agentic_server.py"))) {
24353
24822
  return candidate;
24354
24823
  }
24355
24824
  }
@@ -24358,11 +24827,11 @@ var EverosPlugin = class {
24358
24827
  async ensureFcntlCompat() {
24359
24828
  if (process.platform !== "win32") return;
24360
24829
  const venvPython = this.findVenvPython();
24361
- const venvDir = path23.dirname(path23.dirname(venvPython));
24362
- const sitePackages = path23.join(venvDir, "Lib", "site-packages");
24363
- const target = path23.join(sitePackages, "fcntl.py");
24830
+ const venvDir = path24.dirname(path24.dirname(venvPython));
24831
+ const sitePackages = path24.join(venvDir, "Lib", "site-packages");
24832
+ const target = path24.join(sitePackages, "fcntl.py");
24364
24833
  if (fs24.existsSync(target)) return;
24365
- const source = path23.join(this.getPythonDir(), "fcntl_compat.py");
24834
+ const source = path24.join(this.getPythonDir(), "fcntl_compat.py");
24366
24835
  if (fs24.existsSync(source)) {
24367
24836
  try {
24368
24837
  fs24.copyFileSync(source, target);
@@ -24412,7 +24881,7 @@ var EverosPlugin = class {
24412
24881
  init_task_manager();
24413
24882
 
24414
24883
  // src/skills/scanner.ts
24415
- import * as path24 from "node:path";
24884
+ import * as path25 from "node:path";
24416
24885
  import * as fs25 from "node:fs";
24417
24886
  function scanSkills(skillsDir) {
24418
24887
  if (!fs25.existsSync(skillsDir)) {
@@ -24423,7 +24892,7 @@ function scanSkills(skillsDir) {
24423
24892
  const skills = [];
24424
24893
  for (const entry of entries) {
24425
24894
  if (!entry.isDirectory()) continue;
24426
- const skillMdPath = path24.join(skillsDir, entry.name, "SKILL.md");
24895
+ const skillMdPath = path25.join(skillsDir, entry.name, "SKILL.md");
24427
24896
  if (!fs25.existsSync(skillMdPath)) continue;
24428
24897
  try {
24429
24898
  const content = fs25.readFileSync(skillMdPath, "utf-8");
@@ -24491,7 +24960,7 @@ function parseFrontmatter2(content) {
24491
24960
  // src/tools/SkillTool/SkillTool.ts
24492
24961
  init_registry();
24493
24962
  import * as fs26 from "node:fs";
24494
- import * as path25 from "node:path";
24963
+ import * as path26 from "node:path";
24495
24964
 
24496
24965
  // src/tools/SkillTool/constants.ts
24497
24966
  var SKILL_TOOL_NAME2 = "Skill";
@@ -24568,12 +25037,12 @@ Important:
24568
25037
  `;
24569
25038
  }
24570
25039
  function loadSkillContent(skillName) {
24571
- const skillMdPath = path25.join(skillsDirPath, skillName, "SKILL.md");
25040
+ const skillMdPath = path26.join(skillsDirPath, skillName, "SKILL.md");
24572
25041
  if (!fs26.existsSync(skillMdPath)) return null;
24573
25042
  const content = fs26.readFileSync(skillMdPath, "utf-8");
24574
25043
  const bodyMatch = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/);
24575
25044
  const body = bodyMatch ? bodyMatch[1] : content;
24576
- const skillDir = path25.dirname(skillMdPath);
25045
+ const skillDir = path26.dirname(skillMdPath);
24577
25046
  const normalizedDir = process.platform === "win32" ? skillDir.replace(/\\/g, "/") : skillDir;
24578
25047
  let finalContent = `Base directory for this skill: ${normalizedDir}
24579
25048
 
@@ -24848,9 +25317,9 @@ Examples:
24848
25317
  init_registry();
24849
25318
  init_live();
24850
25319
  import fs27 from "node:fs";
24851
- import path26 from "node:path";
25320
+ import path27 from "node:path";
24852
25321
  function getHusbandFeishuId(workspace) {
24853
- const contactsPath = path26.join(workspace, "prompts", "contacts.md");
25322
+ const contactsPath = path27.join(workspace, "prompts", "contacts.md");
24854
25323
  try {
24855
25324
  const text = fs27.readFileSync(contactsPath, "utf-8");
24856
25325
  const m2 = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
@@ -25053,7 +25522,7 @@ Examples:
25053
25522
  init_live();
25054
25523
  init_registry();
25055
25524
  import * as fs28 from "node:fs";
25056
- import * as path27 from "node:path";
25525
+ import * as path28 from "node:path";
25057
25526
  var MIME_MAP = {
25058
25527
  ".jpg": "jpeg",
25059
25528
  ".jpeg": "jpeg",
@@ -25065,7 +25534,7 @@ var MIME_MAP = {
25065
25534
  function resolveLatestImage(specifiedPath, mediaDir) {
25066
25535
  if (specifiedPath && fs28.existsSync(specifiedPath)) return specifiedPath;
25067
25536
  if (!fs28.existsSync(mediaDir)) return null;
25068
- const files = fs28.readdirSync(mediaDir).filter((f2) => /\.(jpg|jpeg|png|webp|gif|bmp)$/i.test(f2)).map((f2) => ({ name: f2, p: path27.join(mediaDir, f2), mtime: fs28.statSync(path27.join(mediaDir, f2)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
25537
+ const files = fs28.readdirSync(mediaDir).filter((f2) => /\.(jpg|jpeg|png|webp|gif|bmp)$/i.test(f2)).map((f2) => ({ name: f2, p: path28.join(mediaDir, f2), mtime: fs28.statSync(path28.join(mediaDir, f2)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
25069
25538
  return files[0]?.p || null;
25070
25539
  }
25071
25540
  registry.register({
@@ -25089,13 +25558,13 @@ registry.register({
25089
25558
  if (!provider?.streamChat) {
25090
25559
  return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
25091
25560
  }
25092
- const mediaDir = path27.join(ctx.stateDir, "media", "inbound");
25561
+ const mediaDir = path28.join(ctx.stateDir, "media", "inbound");
25093
25562
  const imagePath = resolveLatestImage(args2.image_path, mediaDir);
25094
25563
  if (!imagePath) {
25095
25564
  return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
25096
25565
  }
25097
25566
  const rawPrompt = args2.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
25098
- const ext = path27.extname(imagePath).toLowerCase();
25567
+ const ext = path28.extname(imagePath).toLowerCase();
25099
25568
  const mime = MIME_MAP[ext] || "jpeg";
25100
25569
  const imgB64 = fs28.readFileSync(imagePath).toString("base64");
25101
25570
  const userMsg = {
@@ -25135,13 +25604,13 @@ init_registry();
25135
25604
  import { execFile } from "node:child_process";
25136
25605
  import { promisify } from "node:util";
25137
25606
  import * as fs29 from "node:fs";
25138
- import * as path28 from "node:path";
25607
+ import * as path29 from "node:path";
25139
25608
  import * as os3 from "node:os";
25140
25609
  var execFileAsync = promisify(execFile);
25141
- var VOICE_DIR = path28.join(os3.tmpdir(), "engine-voice");
25610
+ var VOICE_DIR = path29.join(os3.tmpdir(), "engine-voice");
25142
25611
  async function ttsCosyvoice(text, apiKey, model, voice, workspaceId) {
25143
25612
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25144
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25613
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25145
25614
  const script = `
25146
25615
  import sys, json, wave, time, threading
25147
25616
  import dashscope
@@ -25206,7 +25675,7 @@ var GPTSOVITS_REF_TEXT = "\u6625\u7720\u4E0D\u89C9\u6653\uFF0C\u5904\u5904\u95FB
25206
25675
  var GPTSOVITS_REF_LANG = "zh";
25207
25676
  async function ttsGptsovits(text) {
25208
25677
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25209
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25678
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25210
25679
  const params = new URLSearchParams({
25211
25680
  text,
25212
25681
  text_language: "zh",
@@ -25223,7 +25692,7 @@ async function ttsGptsovits(text) {
25223
25692
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
25224
25693
  async function ttsEdge(text) {
25225
25694
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25226
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25695
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25227
25696
  const script = `
25228
25697
  import asyncio, edge_tts, sys
25229
25698
  async def main():
@@ -25316,7 +25785,7 @@ registry.register({
25316
25785
  } catch (e) {
25317
25786
  return { content: `TTS failed: ${e.message}`, isError: true };
25318
25787
  }
25319
- const ext = path28.extname(audioPath).toLowerCase();
25788
+ const ext = path29.extname(audioPath).toLowerCase();
25320
25789
  const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg" };
25321
25790
  const mimeType = mimeMap[ext] || "audio/mpeg";
25322
25791
  const sizeKB = fs29.statSync(audioPath).size / 1024;
@@ -25347,7 +25816,7 @@ registry.register({
25347
25816
  init_live();
25348
25817
  init_registry();
25349
25818
  import * as fs30 from "node:fs";
25350
- import * as path29 from "node:path";
25819
+ import * as path30 from "node:path";
25351
25820
  var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
25352
25821
  var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
25353
25822
  var DEFAULT_RESOLUTION = "1k";
@@ -25463,7 +25932,7 @@ registry.register({
25463
25932
  const REFERENCES = getReferences(ctx);
25464
25933
  const refName = args2.reference || "default";
25465
25934
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
25466
- const refPath = path29.join(ctx.workspace, refEntry.p);
25935
+ const refPath = path30.join(ctx.workspace, refEntry.p);
25467
25936
  if (!fs30.existsSync(refPath)) {
25468
25937
  return { content: `Error: reference image not found at ${refPath}`, isError: true };
25469
25938
  }
@@ -25482,10 +25951,10 @@ registry.register({
25482
25951
  } catch (err) {
25483
25952
  return { content: `Selfie generation failed: ${err.message}`, isError: true };
25484
25953
  }
25485
- const imagesDir = path29.join(ctx.workspace, "images");
25954
+ const imagesDir = path30.join(ctx.workspace, "images");
25486
25955
  if (!fs30.existsSync(imagesDir)) fs30.mkdirSync(imagesDir, { recursive: true });
25487
25956
  const filename = `selfie_${Date.now()}.jpg`;
25488
- const outputPath = path29.join(imagesDir, filename);
25957
+ const outputPath = path30.join(imagesDir, filename);
25489
25958
  fs30.writeFileSync(outputPath, imageBuffer);
25490
25959
  const mgr = ctx.channelManager;
25491
25960
  if (mgr) {
@@ -25497,11 +25966,11 @@ registry.register({
25497
25966
  mimeType: "image/jpeg"
25498
25967
  });
25499
25968
  } catch (err) {
25500
- return { content: `Selfie generated but send failed: ${err.message}. Image: ${path29.resolve(outputPath)}`, isError: false };
25969
+ return { content: `Selfie generated but send failed: ${err.message}. Image: ${path30.resolve(outputPath)}`, isError: false };
25501
25970
  }
25502
25971
  return { content: `Selfie sent! Mode: ${mode}, Provider: ${getProvider(ctx)}, Ref: ${refEntry.name}` };
25503
25972
  }
25504
- return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path29.resolve(outputPath)}` };
25973
+ return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path30.resolve(outputPath)}` };
25505
25974
  },
25506
25975
  isConcurrencySafe: () => false,
25507
25976
  interruptBehavior: () => "block",
@@ -25968,14 +26437,14 @@ init_planModeState();
25968
26437
 
25969
26438
  // src/utils/plans.ts
25970
26439
  import * as fs32 from "node:fs";
25971
- import * as path31 from "node:path";
26440
+ import * as path32 from "node:path";
25972
26441
  import * as crypto4 from "node:crypto";
25973
26442
  var MAX_SLUG_RETRIES = 10;
25974
26443
  function generateSlug() {
25975
26444
  return crypto4.randomBytes(4).toString("hex");
25976
26445
  }
25977
26446
  function getPlansDirectory(stateDir) {
25978
- const plansDir = path31.join(stateDir, "plans");
26447
+ const plansDir = path32.join(stateDir, "plans");
25979
26448
  fs32.mkdirSync(plansDir, { recursive: true });
25980
26449
  return plansDir;
25981
26450
  }
@@ -25986,7 +26455,7 @@ function getPlanSlug(sessionId, stateDir) {
25986
26455
  const plansDir = getPlansDirectory(stateDir);
25987
26456
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
25988
26457
  slug = generateSlug();
25989
- const filePath = path31.join(plansDir, `${slug}.md`);
26458
+ const filePath = path32.join(plansDir, `${slug}.md`);
25990
26459
  if (!fs32.existsSync(filePath)) {
25991
26460
  break;
25992
26461
  }
@@ -25998,9 +26467,9 @@ function getPlanSlug(sessionId, stateDir) {
25998
26467
  function getPlanFilePath(sessionId, stateDir, agentId) {
25999
26468
  const slug = getPlanSlug(sessionId, stateDir);
26000
26469
  if (!agentId) {
26001
- return path31.join(getPlansDirectory(stateDir), `${slug}.md`);
26470
+ return path32.join(getPlansDirectory(stateDir), `${slug}.md`);
26002
26471
  }
26003
- return path31.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26472
+ return path32.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26004
26473
  }
26005
26474
  function getPlan(sessionId, stateDir, agentId) {
26006
26475
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
@@ -26659,14 +27128,14 @@ var EverosSearchSchema = {
26659
27128
  type: "object",
26660
27129
  properties: {
26661
27130
  query: { type: "string", description: "\u641C\u7D22\u67E5\u8BE2" },
26662
- maxResults: { type: "number", description: "\u6700\u5927\u8FD4\u56DE\u6570 (\u9ED8\u8BA45)" }
27131
+ maxResults: { type: "number", description: "\u6700\u5927\u8FD4\u56DE\u6570 (\u9ED8\u8BA410)" },
27132
+ mode: { type: "string", enum: ["hybrid", "hybrid_agentic", "agentic"], description: "\u68C0\u7D22\u6A21\u5F0F: hybrid=\u5FEB(3s\u7CBE\u786E\u5B9E\u4F53), hybrid_agentic=\u6027\u4EF7\u6BD4\u4E4B\u738B(5-14s\u9ED8\u8BA4), agentic=\u6DF1\u5EA6\u63A8\u7406(44s\u590D\u6742\u67E5\u8BE2)" }
26663
27133
  },
26664
27134
  required: ["query"]
26665
27135
  };
26666
27136
  function createEverosSearchTool(everosCfg) {
26667
27137
  const agenticUrl = (everosCfg?.agenticUrl || "http://127.0.0.1:8101").replace(/\/$/, "");
26668
27138
  const userId = everosCfg?.userId || "xiaomei";
26669
- const defaultMode = everosCfg?.defaultMode || "hybrid_agentic";
26670
27139
  return {
26671
27140
  name: "memory_search",
26672
27141
  description: "Mandatory recall step: semantically search memory before answering questions about prior work, decisions, dates, people, preferences, or todos.",
@@ -26675,6 +27144,7 @@ function createEverosSearchTool(everosCfg) {
26675
27144
  const query = args2.query;
26676
27145
  if (!query) return { content: "\u7F3A\u5C11 query \u53C2\u6570" };
26677
27146
  const topK = args2.maxResults ?? 10;
27147
+ const mode = args2.mode || "hybrid_agentic";
26678
27148
  const ctrl = new AbortController();
26679
27149
  const timer = setTimeout(() => ctrl.abort(), 3e4);
26680
27150
  try {
@@ -26684,7 +27154,7 @@ function createEverosSearchTool(everosCfg) {
26684
27154
  body: JSON.stringify({
26685
27155
  query,
26686
27156
  user_id: userId,
26687
- mode: defaultMode,
27157
+ mode,
26688
27158
  top_k: topK
26689
27159
  }),
26690
27160
  signal: ctrl.signal
@@ -26692,7 +27162,7 @@ function createEverosSearchTool(everosCfg) {
26692
27162
  clearTimeout(timer);
26693
27163
  if (!resp.ok) {
26694
27164
  const text = await resp.text();
26695
- console.warn(`[everos] search failed: ${resp.status} ${text.slice(0, 200)}`);
27165
+ console.warn(`[memory_search] failed: ${resp.status} ${text.slice(0, 200)}`);
26696
27166
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26697
27167
  }
26698
27168
  const data = await resp.json();
@@ -26701,7 +27171,7 @@ function createEverosSearchTool(everosCfg) {
26701
27171
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26702
27172
  }
26703
27173
  const formatted = episodes.map((ep, i) => {
26704
- const score = ep.score != null ? ` (score: ${ep.score.toFixed(3)})` : "";
27174
+ const score = ep.score != null ? ` (score: ${typeof ep.score === "number" ? ep.score.toFixed(3) : ep.score})` : "";
26705
27175
  const subject = ep.subject || "";
26706
27176
  const ts = ep.timestamp ? ` [${ep.timestamp}]` : "";
26707
27177
  return `### ${i + 1}. ${subject}${ts}${score}
@@ -26713,9 +27183,9 @@ ${formatted}` };
26713
27183
  } catch (err) {
26714
27184
  clearTimeout(timer);
26715
27185
  if (err.name === "AbortError") {
26716
- console.warn(`[everos] search timeout: ${query.slice(0, 50)}`);
27186
+ console.warn(`[memory_search] timeout: ${query.slice(0, 50)}`);
26717
27187
  } else {
26718
- console.warn(`[everos] search error: ${err.message}`);
27188
+ console.warn(`[memory_search] error: ${err.message}`);
26719
27189
  }
26720
27190
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26721
27191
  }
@@ -27214,9 +27684,9 @@ async function startEngine(config2, opts) {
27214
27684
  process.env.ENGINE_MEDIA_DIR = config2.mediaDir;
27215
27685
  process.env.ENGINE7_WORKSPACE = config2.workspace;
27216
27686
  process.env.OPENCLAW_WORKSPACE = config2.workspace;
27217
- fs41.mkdirSync(path42.join(config2.stateDir, "agents", "main", "memory"), { recursive: true });
27218
- fs41.mkdirSync(path42.join(config2.stateDir, "agents", "main", "sessions"), { recursive: true });
27219
- fs41.mkdirSync(path42.join(config2.stateDir, "logs"), { recursive: true });
27687
+ fs41.mkdirSync(path43.join(config2.stateDir, "agents", "main", "memory"), { recursive: true });
27688
+ fs41.mkdirSync(path43.join(config2.stateDir, "agents", "main", "sessions"), { recursive: true });
27689
+ fs41.mkdirSync(path43.join(config2.stateDir, "logs"), { recursive: true });
27220
27690
  fs41.mkdirSync(config2.workspace, { recursive: true });
27221
27691
  fs41.mkdirSync(config2.mediaDir, { recursive: true });
27222
27692
  try {
@@ -27326,7 +27796,7 @@ async function startEngine(config2, opts) {
27326
27796
  const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
27327
27797
  initSessionMemory2({
27328
27798
  workspace: config2.workspace,
27329
- stateDir: path42.join(config2.stateDir, "session-memory"),
27799
+ stateDir: path43.join(config2.stateDir, "session-memory"),
27330
27800
  provider,
27331
27801
  model: config2.provider.modelId || config2.model || "deepseek-v4-flash",
27332
27802
  features: config2.profile.features
@@ -27356,9 +27826,9 @@ async function startEngine(config2, opts) {
27356
27826
  if (config2.hooks) {
27357
27827
  loadHooksFromConfig({ hooks: config2.hooks });
27358
27828
  }
27359
- const hooksPath = path42.join(config2.workspace, ".hooks.json");
27829
+ const hooksPath = path43.join(config2.workspace, ".hooks.json");
27360
27830
  loadHooksFromFile(hooksPath);
27361
- const settingsHooksPath = path42.join(config2.stateDir, "settings.json");
27831
+ const settingsHooksPath = path43.join(config2.stateDir, "settings.json");
27362
27832
  loadHooksFromFile(settingsHooksPath);
27363
27833
  console.log(`[hooks] Loaded hooks configuration`);
27364
27834
  registerCallbackHook("PreCompact", {
@@ -27372,15 +27842,15 @@ async function startEngine(config2, opts) {
27372
27842
  const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
27373
27843
  const dateStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")}`;
27374
27844
  const timeStr = `${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
27375
- const dailyDir = path42.join(workspace, "memory", "daily");
27376
- const dailyPath = path42.join(dailyDir, `${dateStr}.md`);
27845
+ const dailyDir = path43.join(workspace, "memory", "daily");
27846
+ const dailyPath = path43.join(dailyDir, `${dateStr}.md`);
27377
27847
  try {
27378
27848
  const fs42 = await import("node:fs");
27379
27849
  if (!fs42.existsSync(dailyDir)) {
27380
27850
  fs42.mkdirSync(dailyDir, { recursive: true });
27381
27851
  }
27382
- const sessionsDir = path42.join(config2.stateDir, "agents", "main", "sessions");
27383
- const sessionFile = path42.join(sessionsDir, `${sessionId}.jsonl`);
27852
+ const sessionsDir = path43.join(config2.stateDir, "agents", "main", "sessions");
27853
+ const sessionFile = path43.join(sessionsDir, `${sessionId}.jsonl`);
27384
27854
  const recentLines = [];
27385
27855
  if (fs42.existsSync(sessionFile)) {
27386
27856
  const content = fs42.readFileSync(sessionFile, "utf-8");
@@ -27433,7 +27903,7 @@ ${entry}`);
27433
27903
  if (!workspace) return { continue: true };
27434
27904
  try {
27435
27905
  const fs42 = await import("node:fs");
27436
- const bufferPath = path42.join(workspace, "memory", "working-buffer.md");
27906
+ const bufferPath = path43.join(workspace, "memory", "working-buffer.md");
27437
27907
  if (fs42.existsSync(bufferPath)) {
27438
27908
  const stat4 = fs42.statSync(bufferPath);
27439
27909
  const ageMs = Date.now() - stat4.mtimeMs;
@@ -27486,7 +27956,7 @@ ${content}`
27486
27956
  return `${hr}h ${remMin}m`;
27487
27957
  }
27488
27958
  if (config2.skills?.enabled !== false) {
27489
- const skillsDir = config2.skills?.path ? path42.isAbsolute(config2.skills.path) ? config2.skills.path : path42.resolve(config2.workspace, config2.skills.path) : path42.resolve(config2.workspace, "skills");
27959
+ const skillsDir = config2.skills?.path ? path43.isAbsolute(config2.skills.path) ? config2.skills.path : path43.resolve(config2.workspace, config2.skills.path) : path43.resolve(config2.workspace, "skills");
27490
27960
  const modelDef2 = config2.provider.models.find((m2) => m2.id === config2.model);
27491
27961
  const contextWindowTokens = modelDef2?.contextWindow;
27492
27962
  const skills = scanSkills(skillsDir);
@@ -27505,7 +27975,7 @@ ${content}`
27505
27975
  workspace: config2.workspace
27506
27976
  });
27507
27977
  const systemPrompt = [systemStable, systemDynamic].join("\n\n");
27508
- const promptDumpPath = path42.join(config2.workspace, ".system-prompt.txt");
27978
+ const promptDumpPath = path43.join(config2.workspace, ".system-prompt.txt");
27509
27979
  fs41.writeFileSync(promptDumpPath, systemPrompt);
27510
27980
  console.log(`System prompt: ${systemStable.length} chars stable + ${systemDynamic.length} chars dynamic \u2192 ${promptDumpPath}`);
27511
27981
  const modelDef = config2.provider.models.find((m2) => m2.id === config2.model);
@@ -27616,20 +28086,23 @@ ${content}`
27616
28086
  enabled: true,
27617
28087
  url: everosCfg.everosUrl || "http://127.0.0.1:8100",
27618
28088
  appId: everosCfg.userId || "default",
27619
- userId: everosCfg.userId || "default"
28089
+ userId: everosCfg.userId || "default",
28090
+ agentName: everosCfg.agentName || everosCfg.userId || "assistant"
27620
28091
  });
27621
28092
  sessions.onWriterCreated = (writer, sessionId) => {
27622
28093
  writer.onMessageWritten = (msg2) => {
28094
+ console.log(`[everos-sync] onMessageWritten fired: role=${msg2.role} len=${msg2.text.length}`);
27623
28095
  everosSync.push({
27624
28096
  sessionId: writer.engineSessionId || sessionId,
27625
28097
  role: msg2.role === "toolResult" ? "tool" : msg2.role,
27626
28098
  text: msg2.text,
27627
28099
  timestamp: new Date(msg2.timestamp).getTime()
27628
- }).catch(() => {
27629
- });
28100
+ }).catch((e) => console.warn(`[everos-sync] push error: ${e}`));
27630
28101
  };
27631
28102
  };
27632
28103
  console.log(`[everos-sync] hook registered (appId=${everosCfg.userId})`);
28104
+ } else {
28105
+ console.log(`[everos-sync] SKIPPED \u2014 config.everos not enabled or missing`);
27633
28106
  }
27634
28107
  const channelManager = new ChannelManager();
27635
28108
  const memoryRecallProvider = createMemorySideProvider(
@@ -27660,6 +28133,7 @@ ${content}`
27660
28133
  recallProvider: memoryRecallProvider || void 0,
27661
28134
  extractProvider: memoryExtractProvider || void 0,
27662
28135
  topics: config2.topics,
28136
+ everosCfg: config2.everos,
27663
28137
  mcpManager
27664
28138
  };
27665
28139
  if (visionEngine && visionConfig) {
@@ -28580,8 +29054,8 @@ Auto-routing disabled \u2014 all messages use this model.
28580
29054
  let writePath = configPath2;
28581
29055
  if (configPath2 && !fs41.existsSync(configPath2)) {
28582
29056
  const __pFile = fileURLToPath(import.meta.url);
28583
- const __pDir = path42.dirname(__pFile);
28584
- const altPath = path42.join(path42.resolve(__pDir, "../configs"), path42.basename(configPath2));
29057
+ const __pDir = path43.dirname(__pFile);
29058
+ const altPath = path43.join(path43.resolve(__pDir, "../configs"), path43.basename(configPath2));
28585
29059
  if (fs41.existsSync(altPath)) {
28586
29060
  console.warn(`[primary] Config not found at ${configPath2}, falling back to ${altPath}`);
28587
29061
  writePath = altPath;
@@ -28861,7 +29335,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
28861
29335
  const ext = detected.split("/")[1] || "png";
28862
29336
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
28863
29337
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
28864
- const savedPath = path42.join(config2.mediaDir, `${imageId}.${ext}`);
29338
+ const savedPath = path43.join(config2.mediaDir, `${imageId}.${ext}`);
28865
29339
  fs41.writeFileSync(savedPath, resized.buffer);
28866
29340
  savedPaths.push(savedPath);
28867
29341
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
@@ -28888,7 +29362,7 @@ ${pathStr}` }];
28888
29362
  }
28889
29363
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
28890
29364
  if (nonImageAttachments && nonImageAttachments.length > 0) {
28891
- const outDir = path42.join(config2.mediaDir, sessionId);
29365
+ const outDir = path43.join(config2.mediaDir, sessionId);
28892
29366
  fs41.mkdirSync(outDir, { recursive: true });
28893
29367
  const resolved = [];
28894
29368
  for (const att of nonImageAttachments) {
@@ -28897,8 +29371,8 @@ ${pathStr}` }];
28897
29371
  const resp = await fetch(att.url);
28898
29372
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
28899
29373
  const buffer = Buffer.from(await resp.arrayBuffer());
28900
- const safeName2 = path42.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
28901
- const savedPath = path42.join(outDir, safeName2);
29374
+ const safeName2 = path43.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
29375
+ const savedPath = path43.join(outDir, safeName2);
28902
29376
  fs41.writeFileSync(savedPath, buffer);
28903
29377
  resolved.push(savedPath);
28904
29378
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
@@ -29246,7 +29720,8 @@ ${pathStr}` }];
29246
29720
  if (config2.cognifold?.intentWatcher?.enabled) {
29247
29721
  try {
29248
29722
  const { registerCognifoldIntentWatcher: registerCognifoldIntentWatcher2 } = await Promise.resolve().then(() => (init_cognifold_intent_watcher(), cognifold_intent_watcher_exports));
29249
- const cfSessionId = config2.cognifold.sessionId;
29723
+ const sm = globalThis.__cognifoldSessions;
29724
+ const cfSessionId = sm?.getSessionId?.("main") || config2.cognifold.sessionId;
29250
29725
  if (!cfSessionId) {
29251
29726
  console.warn("[cognifold] intent-watcher: config.cognifold.sessionId \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 watcher");
29252
29727
  } else {
@@ -29259,7 +29734,7 @@ ${pathStr}` }];
29259
29734
  console.warn("[cognifold] watcher: config.workspace \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 proactive \u5199\u5165");
29260
29735
  return;
29261
29736
  }
29262
- const pFile = path42.join(wsDir, ".cognifold-proactive.json");
29737
+ const pFile = path43.join(wsDir, ".cognifold-proactive.json");
29263
29738
  const cognifoldBaseUrl = config2.cognifold?.baseUrl || "http://127.0.0.1:9001";
29264
29739
  const cognifoldSessionId = cfSessionId;
29265
29740
  const rawSuggestions = data.suggestions || data.actions || (data.intent_id ? [data] : []);
@@ -29313,7 +29788,7 @@ ${pathStr}` }];
29313
29788
  console.error(`[cognifold] failed to save proactive: ${e.message}`);
29314
29789
  }
29315
29790
  if (enriched.length > 0) {
29316
- const promptFile = path42.join(config2.workspace, "prompts", "cognifold-proactive.md");
29791
+ const promptFile = path43.join(config2.workspace, "prompts", "cognifold-proactive.md");
29317
29792
  const promptText = fs41.existsSync(promptFile) ? fs41.readFileSync(promptFile, "utf-8") : "[CogniFold proactive] \u6709 " + enriched.length + " \u4E2A action \u5230\u671F\u4E86";
29318
29793
  const actionsJson = JSON.stringify(enriched, null, 2);
29319
29794
  const sessionId = cfSessionId;
@@ -29419,9 +29894,9 @@ async function doReloadConfig(config2, deps, provider) {
29419
29894
  let reloadConfigPath = savedConfigPath;
29420
29895
  if (!fs41.existsSync(reloadConfigPath)) {
29421
29896
  const __filename = fileURLToPath(import.meta.url);
29422
- const __dirname = path42.dirname(__filename);
29423
- const engineConfigsDir = path42.resolve(__dirname, "../configs");
29424
- const altPath = path42.join(engineConfigsDir, path42.basename(savedConfigPath));
29897
+ const __dirname = path43.dirname(__filename);
29898
+ const engineConfigsDir = path43.resolve(__dirname, "../configs");
29899
+ const altPath = path43.join(engineConfigsDir, path43.basename(savedConfigPath));
29425
29900
  if (fs41.existsSync(altPath)) {
29426
29901
  console.warn(`[reload] Config not found at ${reloadConfigPath}, falling back to ${altPath} (dev mode)`);
29427
29902
  reloadConfigPath = altPath;
@@ -29474,7 +29949,7 @@ async function doReloadConfig(config2, deps, provider) {
29474
29949
  } catch (err) {
29475
29950
  console.error(`[reload] Failed: ${err.message}`);
29476
29951
  try {
29477
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD FAILED: ${err.message}
29952
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD FAILED: ${err.message}
29478
29953
  ${err.stack}
29479
29954
  `);
29480
29955
  } catch {
@@ -29486,17 +29961,17 @@ function startConfigWatcher(config2, deps, provider) {
29486
29961
  const raw = config2._configFilePath;
29487
29962
  let configPath2 = raw;
29488
29963
  if (!fs41.existsSync(configPath2)) {
29489
- configPath2 = path42.resolve(raw);
29964
+ configPath2 = path43.resolve(raw);
29490
29965
  }
29491
29966
  if (!fs41.existsSync(configPath2)) {
29492
29967
  const __filename2 = fileURLToPath(import.meta.url);
29493
- const __dirname22 = path42.dirname(__filename2);
29494
- configPath2 = path42.resolve(__dirname22, "..", raw);
29968
+ const __dirname22 = path43.dirname(__filename2);
29969
+ configPath2 = path43.resolve(__dirname22, "..", raw);
29495
29970
  }
29496
29971
  if (!fs41.existsSync(configPath2)) {
29497
29972
  console.warn(`[config-watch] config path invalid: ${configPath2}, watcher disabled`);
29498
29973
  try {
29499
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] DISABLED: configPath=${configPath2}
29974
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] DISABLED: configPath=${configPath2}
29500
29975
  `);
29501
29976
  } catch {
29502
29977
  }
@@ -29508,13 +29983,13 @@ function startConfigWatcher(config2, deps, provider) {
29508
29983
  debounceTimer = setTimeout(async () => {
29509
29984
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
29510
29985
  try {
29511
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
29986
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
29512
29987
  `);
29513
29988
  } catch {
29514
29989
  }
29515
29990
  const result = await doReloadConfig(config2, deps, provider);
29516
29991
  try {
29517
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD DONE: ok=${result.ok} changes=${result.changes.join(",")}
29992
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD DONE: ok=${result.ok} changes=${result.changes.join(",")}
29518
29993
  `);
29519
29994
  } catch {
29520
29995
  }
@@ -29523,14 +29998,14 @@ function startConfigWatcher(config2, deps, provider) {
29523
29998
  watcher.on("error", (err) => {
29524
29999
  console.error(`[config-watch] error: ${err.message}`);
29525
30000
  try {
29526
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${err.message}
30001
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${err.message}
29527
30002
  `);
29528
30003
  } catch {
29529
30004
  }
29530
30005
  });
29531
30006
  console.log(`[config-watch] watching ${configPath2}`);
29532
30007
  try {
29533
- fs41.appendFileSync(path42.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] STARTED watching=${configPath2}
30008
+ fs41.appendFileSync(path43.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] STARTED watching=${configPath2}
29534
30009
  `);
29535
30010
  } catch {
29536
30011
  }