engine7 7.1.22 → 7.1.24

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,22 +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
- const osPlatform = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : process.platform;
18414
- const contextParts = [`\u5F53\u524D\u65F6\u95F4: ${dateStr}`, `\u7CFB\u7EDF: ${osPlatform} (${process.platform})`];
18415
- const meta = options.inboundMeta;
18416
- if (meta) {
18417
- contextParts.push(`\u6765\u6E90: ${meta.channel}`);
18418
- if (meta.channelType) contextParts.push(`\u6D88\u606F\u7C7B\u578B: ${meta.channelType === "dm" ? "\u79C1\u4FE1" : "\u7FA4\u804A"}`);
18419
- if (meta.channel_id) contextParts.push(`\u9891\u9053ID: ${meta.channel_id}`);
18420
- contextParts.push(`\u53D1\u9001\u8005ID: ${meta.from}`);
18421
- if (meta.fromName) contextParts.push(`\u53D1\u9001\u8005\u540D\u79F0: ${meta.fromName}`);
18422
- if (meta.messageId) contextParts.push(`\u6D88\u606FID: ${meta.messageId}`);
18423
- } else if (options.channel) {
18424
- contextParts.push(`\u6765\u6E90: ${options.channel}`);
18425
- }
18426
- if (options.sessionId) contextParts.push(`\u4F1A\u8BDDID: ${options.sessionId}`);
18427
18445
  parts.push(`# \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587
18428
- ${contextParts.join("\n")}`);
18446
+ \u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18429
18447
  console.log(`[dynamic-prompt] Loaded: ${loaded2.length > 0 ? loaded2.join(", ") : "(none)"}`);
18430
18448
  return parts.join("\n\n");
18431
18449
  }
@@ -18727,6 +18745,169 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
18727
18745
  }));
18728
18746
  }
18729
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
+
18730
18911
  // src/handle-query.ts
18731
18912
  init_paths();
18732
18913
  import { readFileSync as readFileSync15, existsSync as existsSync12 } from "node:fs";
@@ -18799,18 +18980,18 @@ function truncate(s2, maxLen) {
18799
18980
  }
18800
18981
  var externalChanRulesCache = null;
18801
18982
  function loadExternalChanRules(workspace) {
18802
- const path43 = join20(workspace, "prompts", "external-chan-rules.md");
18803
- 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;
18804
18985
  let content = "";
18805
- if (existsSync12(path43)) {
18986
+ if (existsSync12(path44)) {
18806
18987
  try {
18807
- content = readFileSync15(path43, "utf-8").trim();
18988
+ content = readFileSync15(path44, "utf-8").trim();
18808
18989
  } catch (e) {
18809
18990
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
18810
18991
  }
18811
18992
  }
18812
- externalChanRulesCache = { path: path43, content };
18813
- 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}`);
18814
18995
  return externalChanRulesCache;
18815
18996
  }
18816
18997
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -18833,10 +19014,10 @@ function getExternalChanWhitelist(workspace, configExternalChannels) {
18833
19014
  if (!externalChanWhitelist) loadContactMap(workspace);
18834
19015
  return externalChanWhitelist;
18835
19016
  }
18836
- async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18837
- 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);
18838
19019
  }
18839
- async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
19020
+ async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
18840
19021
  const { engine, sessions, channelManager, workspace, providerId, providerApi, model } = deps;
18841
19022
  const features = deps.features || {};
18842
19023
  const preQueryAbort = new AbortController();
@@ -19013,6 +19194,8 @@ ${text}` : text });
19013
19194
  const toolContext = {
19014
19195
  sessionId,
19015
19196
  channel: channelName === "cli" ? "console" : channelName,
19197
+ source: source || "",
19198
+ // 消息来源(user/inbox/heartbeat/cron/system/inner-voice),Stop hook 用来区分注入 turn
19016
19199
  workspace,
19017
19200
  stateDir: deps.stateDir || workspace,
19018
19201
  channelManager,
@@ -19187,7 +19370,23 @@ ${text}` : text });
19187
19370
  const recallP = deps.recallProvider;
19188
19371
  const recallMode = deps.topics?.recall?.mode || "llm";
19189
19372
  let relevantMemories;
19190
- 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") {
19191
19390
  relevantMemories = await findRelevantMemoriesVector(
19192
19391
  textForMemory,
19193
19392
  memoryDir,
@@ -19210,8 +19409,8 @@ ${text}` : text });
19210
19409
  const attachmentMemories = [];
19211
19410
  for (const mem of relevantMemories) {
19212
19411
  try {
19213
- const content = readFileSync15(mem.path, "utf-8");
19214
- 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);
19215
19414
  attachmentMemories.push({ path: mem.path, content, mtimeMs: mem.mtimeMs, header });
19216
19415
  } catch {
19217
19416
  }
@@ -19759,7 +19958,9 @@ function registerCognifoldBridge(config2) {
19759
19958
  messageId: ctx.inbound.messageId
19760
19959
  }
19761
19960
  };
19762
- void enqueueEvent(cognifoldConfig.sessionId, event);
19961
+ const sm = globalThis.__cognifoldSessions;
19962
+ const dynamicSessionId = sm?.getSessionId?.("main") || cognifoldConfig.sessionId;
19963
+ void enqueueEvent(dynamicSessionId, event);
19763
19964
  return null;
19764
19965
  }, 80);
19765
19966
  }
@@ -20083,7 +20284,8 @@ var MessageDispatcher = class {
20083
20284
  msg2.deps,
20084
20285
  msg2.channelTarget,
20085
20286
  msg2.inboundMeta,
20086
- msg2.skipRecall
20287
+ msg2.skipRecall,
20288
+ msg2.source
20087
20289
  );
20088
20290
  } catch (err) {
20089
20291
  console.error(`[dispatcher] Query error (session=${msg2.sessionId}): ${err.message}`);
@@ -20243,13 +20445,19 @@ function startCliLoop(deps, cliConfig, channelManager, dispatcher) {
20243
20445
 
20244
20446
  // src/session/session-history.ts
20245
20447
  import fs14 from "node:fs";
20448
+ import path14 from "node:path";
20246
20449
  var BEIJING_OFFSET_MS = 8 * 36e5;
20247
20450
  var INJECTED_CONTENT_PATTERNS = [
20248
20451
  /【定时心跳】/,
20249
20452
  /\[内心对话测试\]/,
20250
20453
  /\[inner-voice\]/,
20251
20454
  /\[微信巡检\]/,
20252
- /\[plugin\]/
20455
+ /\[plugin\]/,
20456
+ /<nudge-notification>/,
20457
+ /<task-notification>/,
20458
+ /<calendar-notification>/,
20459
+ /## Actions \(\d+\s*个\)/
20460
+ // CogniFold proactive 注入(block[0] 固定格式,engine-startup 拼的)
20253
20461
  ];
20254
20462
  function parseJsonlEntries(lines) {
20255
20463
  const entries = [];
@@ -20284,6 +20492,20 @@ function resolveScopeMainJsonl(sessions) {
20284
20492
  if (!sessionId) return null;
20285
20493
  return sessions.getSessionFilePath(sessionId);
20286
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
+ }
20287
20509
  function cleanText(rawText) {
20288
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]");
20289
20511
  return clean.trim();
@@ -20374,6 +20596,7 @@ function recentMessages(sessions, hours = 12, limit = 60) {
20374
20596
  time: `${p2(bj.getHours())}:${p2(bj.getMinutes())}`,
20375
20597
  role,
20376
20598
  text: clean.slice(0, 80),
20599
+ timestamp: dtMs,
20377
20600
  _utc: dtMs
20378
20601
  });
20379
20602
  }
@@ -20505,6 +20728,8 @@ ${basePrompt}`;
20505
20728
  channelName: "heartbeat",
20506
20729
  source: "heartbeat",
20507
20730
  priority: "later",
20731
+ skipRecall: true,
20732
+ // 心跳不需要记忆召回,避免重复注入心跳相关记忆
20508
20733
  callbacks: {
20509
20734
  onResult: () => resolveDone()
20510
20735
  },
@@ -20522,7 +20747,7 @@ ${basePrompt}`;
20522
20747
 
20523
20748
  // src/nudge/plugin.ts
20524
20749
  import fs17 from "node:fs";
20525
- import path16 from "node:path";
20750
+ import path17 from "node:path";
20526
20751
 
20527
20752
  // src/nudge/judge.ts
20528
20753
  function shouldNudge(task, taskState, cfg) {
@@ -20691,10 +20916,10 @@ function formatDuration2(ms) {
20691
20916
 
20692
20917
  // src/nudge/session-state-reader.ts
20693
20918
  import fs15 from "node:fs";
20694
- import path14 from "node:path";
20919
+ import path15 from "node:path";
20695
20920
  function parseSessionStateFull(workspace, sessionStateFile) {
20696
20921
  const stateFile = sessionStateFile || "SESSION-STATE.md";
20697
- const statePath = path14.isAbsolute(stateFile) ? stateFile : path14.join(workspace, stateFile);
20922
+ const statePath = path15.isAbsolute(stateFile) ? stateFile : path15.join(workspace, stateFile);
20698
20923
  let content;
20699
20924
  try {
20700
20925
  content = fs15.readFileSync(statePath, "utf-8");
@@ -20749,13 +20974,13 @@ function taskIdFromTitle(title) {
20749
20974
 
20750
20975
  // src/calendar/db.ts
20751
20976
  import { DatabaseSync } from "node:sqlite";
20752
- import * as path15 from "node:path";
20977
+ import * as path16 from "node:path";
20753
20978
  import * as fs16 from "node:fs";
20754
20979
  var TZ_OFFSET_MS = 8 * 60 * 60 * 1e3;
20755
20980
  function openDb(workspace) {
20756
- const dir = path15.join(workspace, ".calendar");
20981
+ const dir = path16.join(workspace, ".calendar");
20757
20982
  fs16.mkdirSync(dir, { recursive: true });
20758
- const dbPath = path15.join(dir, "calendar.db");
20983
+ const dbPath = path16.join(dir, "calendar.db");
20759
20984
  const db = new DatabaseSync(dbPath);
20760
20985
  db.exec("PRAGMA journal_mode=WAL");
20761
20986
  db.exec(`CREATE TABLE IF NOT EXISTS events (
@@ -20844,7 +21069,7 @@ var NudgePlugin = class {
20844
21069
  provider;
20845
21070
  model;
20846
21071
  loadPrompt(workspace, promptFile) {
20847
- 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");
20848
21073
  try {
20849
21074
  const content = fs17.readFileSync(promptPath, "utf-8").trim();
20850
21075
  if (content) {
@@ -20881,11 +21106,20 @@ var NudgePlugin = class {
20881
21106
  const lastMsg = input?.last_assistant_message || "";
20882
21107
  const sessionId = input?.session_id || "";
20883
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
+ }
20884
21113
  const msgChannel = input?.channel || "";
20885
21114
  if (sessionId.includes("voice-chat") || msgChannel === "voice-chat") {
20886
21115
  console.log(`[stop-hook] skipping voice-chat (channel=${msgChannel})`);
20887
21116
  return { outcome: { outcome: "success" } };
20888
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
+ }
20889
21123
  if (!lastMsg) {
20890
21124
  return { outcome: { outcome: "success" } };
20891
21125
  }
@@ -20958,8 +21192,8 @@ var NudgePlugin = class {
20958
21192
  if (pushedDecision && waitDesc) {
20959
21193
  console.log(`[stop-hook] DETECTED pushedDecision! Injecting corrective message to ${sessionId}`);
20960
21194
  try {
20961
- const correctiveMsg = [
20962
- "\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",
20963
21197
  "",
20964
21198
  `\u8BCA\u65AD\uFF1A${waitDesc}`,
20965
21199
  "",
@@ -20972,9 +21206,10 @@ var NudgePlugin = class {
20972
21206
  '3. \u6267\u884C\u5B8C\u6C47\u62A5\u7ED3\u679C\uFF08"\u5DF2\u5904\u7406" / "\u5DF2 commit" / "\u5DF2 archive"\uFF09',
20973
21207
  "",
20974
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"
20975
- ].join("\n");
20976
- if (this.dispatcher) {
20977
- this.dispatcher.submitMessage(correctiveMsg, sessionId);
21209
+ ].join("\n"));
21210
+ const route = this.getRoute(sessions);
21211
+ if (route) {
21212
+ enqueueNotification(correctiveMsg, route);
20978
21213
  }
20979
21214
  } catch (e) {
20980
21215
  console.warn(`[stop-hook] Failed to inject corrective message: ${e.message}`);
@@ -20983,14 +21218,21 @@ var NudgePlugin = class {
20983
21218
  if (!isWaiting) {
20984
21219
  return { outcome: { outcome: "success" } };
20985
21220
  }
20986
- const nudgeDir = path16.join(this.workspace, ".nudge");
20987
- 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");
20988
21223
  try {
20989
21224
  if (!fs17.existsSync(nudgeDir)) fs17.mkdirSync(nudgeDir, { recursive: true });
20990
21225
  let notifs = [];
20991
21226
  if (fs17.existsSync(notifPath)) {
20992
21227
  notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20993
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
+ }
20994
21236
  const recentReg = notifs.find((n) => now - new Date(n.createdAt).getTime() < 3 * 6e4);
20995
21237
  if (recentReg) {
20996
21238
  console.log(`[stop-hook] Skip (recent registration within 3min)`);
@@ -21039,9 +21281,14 @@ var NudgePlugin = class {
21039
21281
  return null;
21040
21282
  }
21041
21283
  }
21042
- /** 检查 stop-hook-notifications:你停止前注册的等待唤起 */
21043
- checkStopHookNotifications() {
21044
- 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");
21045
21292
  try {
21046
21293
  if (!fs17.existsSync(notifPath)) return null;
21047
21294
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
@@ -21049,54 +21296,161 @@ var NudgePlugin = class {
21049
21296
  const now = Date.now();
21050
21297
  const due = notifs.filter((n) => new Date(n.wakeAt).getTime() <= now && !n.notified);
21051
21298
  if (due.length === 0) return null;
21052
- const latest = due[due.length - 1];
21053
- console.log(`[nudge] Stop-hook notification ${latest.id} triggered: ${latest.description.slice(0, 80)}...`);
21054
- const updated = notifs.map((n) => n.id === latest.id ? { ...n, notified: true } : n);
21055
- fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
21056
- 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
21057
21303
 
21058
- [\u901A\u77E5ID: ${latest.id}]
21059
- \u4E0A\u6B21\u8BF4\uFF1A${latest.description}
21304
+ ${items}
21060
21305
 
21061
- \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) };
21062
21312
  } catch (e) {
21063
- console.warn(`[nudge] checkStopHookNotifications error: ${e.message}`);
21313
+ console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
21064
21314
  return null;
21065
21315
  }
21066
21316
  }
21067
- /** 读最近消息,如果发现"<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
+ */
21068
21371
  cleanupStaleNotificationsFromMessages(sessions) {
21069
21372
  try {
21070
- const recent = recentMessages(sessions, 0.5, 6);
21071
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
21072
- const recentTexts = recent.filter((r) => new Date(r.timestamp || r.createdAt || Date.now()).getTime() > fiveMinAgo).map((r) => r.text);
21073
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21373
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21074
21374
  if (!fs17.existsSync(notifPath)) return;
21075
21375
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
21076
21376
  if (notifs.length === 0) return;
21077
- const expiredIds = /* @__PURE__ */ new Set();
21078
- for (const text of recentTexts) {
21079
- for (const n of notifs) {
21080
- if (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`)) {
21081
- expiredIds.add(n.id);
21082
- }
21083
- }
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);
21084
21390
  }
21085
- if (expiredIds.size === 0) return;
21086
- const remaining = notifs.filter((n) => !expiredIds.has(n.id));
21087
- const cleaned = notifs.length - remaining.length;
21088
- if (cleaned > 0) {
21089
- if (remaining.length > 0) {
21090
- fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
21091
- } else {
21092
- fs17.unlinkSync(notifPath);
21093
- }
21094
- 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(", ")}`);
21095
21396
  }
21096
21397
  } catch (e) {
21097
21398
  console.warn(`[nudge] cleanupStaleNotificationsFromMessages error: ${e.message}`);
21098
21399
  }
21099
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
+ }
21100
21454
  async tick(sessions, deps) {
21101
21455
  if (this.running) {
21102
21456
  console.log("[nudge] Previous tick still running, skipping");
@@ -21111,7 +21465,7 @@ var NudgePlugin = class {
21111
21465
  const recent = recentMessages(sessions, 0.5, 6);
21112
21466
  const lastUserMsg2 = recent.filter((r) => r.role === "user").slice(-1)[0];
21113
21467
  if (lastUserMsg2) {
21114
- const elapsed = Date.now() - new Date(lastUserMsg2.timestamp || lastUserMsg2.createdAt || Date.now()).getTime();
21468
+ const elapsed = lastUserMsg2.timestamp ? Date.now() - lastUserMsg2.timestamp : 0;
21115
21469
  if (elapsed < activeThresholdMs) {
21116
21470
  console.log(`[nudge] User active ${Math.round(elapsed / 1e3)}s ago (<${activeThresholdMs / 1e3}s), skipping tick`);
21117
21471
  return;
@@ -21123,10 +21477,15 @@ var NudgePlugin = class {
21123
21477
  this.running = true;
21124
21478
  try {
21125
21479
  this.cleanupStaleNotificationsFromMessages(sessions);
21126
- const stopNotifs = this.checkStopHookNotifications();
21127
- if (stopNotifs) {
21480
+ const dueNotifs = this.collectDueStopHookNotifications();
21481
+ if (dueNotifs) {
21128
21482
  const route2 = this.getRoute(sessions);
21129
- 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
+ }
21130
21489
  const state0 = this.loadState();
21131
21490
  state0.lastAnyNudgeAt = (/* @__PURE__ */ new Date()).toISOString();
21132
21491
  this.saveState(state0);
@@ -21312,7 +21671,7 @@ var NudgePlugin = class {
21312
21671
  // === state 持久化 ===
21313
21672
  loadState() {
21314
21673
  const stateFile = this.cfg.stateFile || "nudge-state.json";
21315
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21674
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
21316
21675
  try {
21317
21676
  const content = fs17.readFileSync(statePath, "utf-8");
21318
21677
  return JSON.parse(content);
@@ -21322,7 +21681,7 @@ var NudgePlugin = class {
21322
21681
  }
21323
21682
  saveState(state2) {
21324
21683
  const stateFile = this.cfg.stateFile || "nudge-state.json";
21325
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21684
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
21326
21685
  fs17.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
21327
21686
  }
21328
21687
  newTaskState() {
@@ -21408,30 +21767,47 @@ var NudgePlugin = class {
21408
21767
  const now = /* @__PURE__ */ new Date();
21409
21768
  const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
21410
21769
  const bj = new Date(now.getTime() + bjOffset);
21411
- const month = bj.getMonth() + 1;
21412
- const day = bj.getDate();
21413
21770
  const bjHour = bj.getHours();
21414
21771
  const bjMinute = bj.getMinutes();
21772
+ const todayStart = new Date(bj.getFullYear(), bj.getMonth(), bj.getDate()).getTime();
21415
21773
  const rows = db.prepare(
21416
- "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND (date_str=? OR date_str LIKE ?)"
21417
- ).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();
21418
21776
  db.close();
21419
- if (rows.length === 0) return null;
21420
- const dueRows = rows.filter((r2) => {
21421
- if (!r2.time_exact) return true;
21422
- const [h, m2] = r2.time_exact.split(":").map(Number);
21423
- if (h < bjHour) return true;
21424
- if (h === bjHour && m2 <= bjMinute) return true;
21425
- 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;
21426
21792
  });
21427
- if (dueRows.length === 0) return null;
21428
- const r = dueRows[0];
21429
- 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();
21430
21795
  } catch (e) {
21431
21796
  console.warn(`[nudge] checkCalendarDue error: ${e.message}`);
21432
21797
  return null;
21433
21798
  }
21434
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
+ }
21435
21811
  /** 检查 carry-over:如果 in_progress task 24h+ 没推进,自动 calendar add-task 排明天 */
21436
21812
  checkCarryOver(task, _sessions) {
21437
21813
  try {
@@ -21485,7 +21861,7 @@ var NudgePlugin = class {
21485
21861
 
21486
21862
  // src/inner-voice/plugin.ts
21487
21863
  import fs21 from "node:fs";
21488
- import path20 from "node:path";
21864
+ import path21 from "node:path";
21489
21865
 
21490
21866
  // src/inner-voice/activity.ts
21491
21867
  function checkActivity(sessions, activeThresholdMs) {
@@ -21525,7 +21901,7 @@ function calcHintProb(min) {
21525
21901
 
21526
21902
  // src/inner-voice/emotional-state.ts
21527
21903
  import fs18 from "node:fs";
21528
- import path17 from "node:path";
21904
+ import path18 from "node:path";
21529
21905
  var NEUTRAL = 0.5;
21530
21906
  var DECAY_RATE = 0.17;
21531
21907
  var MAX_EVENTS = 20;
@@ -21576,7 +21952,7 @@ function initialState() {
21576
21952
  return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowIsoBj(), events: [] };
21577
21953
  }
21578
21954
  async function updateEmotionalState(workspace, sessions) {
21579
- const stateFile = path17.join(workspace, "inner-voice", "emotional-state.json");
21955
+ const stateFile = path18.join(workspace, "inner-voice", "emotional-state.json");
21580
21956
  const messages = readRecentMessages(sessions, RECENT_N);
21581
21957
  if (messages.length === 0) {
21582
21958
  console.log("[emotional-state] no messages");
@@ -21609,7 +21985,7 @@ async function updateEmotionalState(workspace, sessions) {
21609
21985
  function readRecentMessages(sessions, n) {
21610
21986
  const mainId = sessions.getSessionId("scope:main");
21611
21987
  if (!mainId) return [];
21612
- const file = path17.join(sessions.sessionsDir, `${mainId}.jsonl`);
21988
+ const file = path18.join(sessions.sessionsDir, `${mainId}.jsonl`);
21613
21989
  if (!fs18.existsSync(file)) return [];
21614
21990
  const lines = readLastNLines(file, n * 4 + 20);
21615
21991
  const entries = [];
@@ -21727,7 +22103,7 @@ function refreshHoursAgo(events) {
21727
22103
  }
21728
22104
  function appendMoodLog(workspace, state2, summary) {
21729
22105
  try {
21730
- const logPath = path17.join(workspace, "mood-history.log");
22106
+ const logPath = path18.join(workspace, "mood-history.log");
21731
22107
  const ts = formatBj(/* @__PURE__ */ new Date(), false);
21732
22108
  fs18.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
21733
22109
  `);
@@ -21744,7 +22120,7 @@ function loadJson(file) {
21744
22120
  }
21745
22121
  function saveJson(file, data) {
21746
22122
  try {
21747
- fs18.mkdirSync(path17.dirname(file), { recursive: true });
22123
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
21748
22124
  fs18.writeFileSync(file, JSON.stringify(data, null, 2));
21749
22125
  } catch (err) {
21750
22126
  console.warn(`[emotional-state] save failed: ${err.message}`);
@@ -21790,7 +22166,7 @@ function formatBj(d, withSec) {
21790
22166
 
21791
22167
  // src/inner-voice/topics-scorer.ts
21792
22168
  import fs19 from "node:fs";
21793
- import path18 from "node:path";
22169
+ import path19 from "node:path";
21794
22170
  var HALF_LIFE_DAYS = 3;
21795
22171
  var PROJECT_HALF_LIFE_DAYS = 1.5;
21796
22172
  var COOLDOWN_HOURS = 6;
@@ -21798,8 +22174,8 @@ var MAX_CHARS = 8e3;
21798
22174
  var SKIP_NAMES = /* @__PURE__ */ new Set(["MEMORY.md", "archive"]);
21799
22175
  var SKIP_DIRS = /* @__PURE__ */ new Set(["archive"]);
21800
22176
  function pickTopic(workspace, typeFilter, opts) {
21801
- const topicsDir = path18.join(workspace, "topics");
21802
- 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");
21803
22179
  const files = scanTopics(topicsDir, typeFilter);
21804
22180
  if (files.length === 0) {
21805
22181
  console.log(`[topics-scorer] no topics found (type=${typeFilter})`);
@@ -21831,7 +22207,7 @@ function pickTopic(workspace, typeFilter, opts) {
21831
22207
  recency: Math.round(recency * 1e3) / 1e3,
21832
22208
  freq: Math.round(freq * 1e3) / 1e3,
21833
22209
  type: type2,
21834
- name: meta.name || path18.basename(relpath),
22210
+ name: meta.name || path19.basename(relpath),
21835
22211
  description: meta.description || "",
21836
22212
  mtime
21837
22213
  });
@@ -21889,14 +22265,14 @@ function scanTopics(topicsDir, typeFilter) {
21889
22265
  const out = [];
21890
22266
  const walk = (dir) => {
21891
22267
  for (const name of fs19.readdirSync(dir)) {
21892
- const full = path18.join(dir, name);
22268
+ const full = path19.join(dir, name);
21893
22269
  const stat4 = fs19.statSync(full);
21894
22270
  if (stat4.isDirectory()) {
21895
22271
  if (SKIP_DIRS.has(name)) continue;
21896
22272
  walk(full);
21897
22273
  } else {
21898
22274
  if (!name.endsWith(".md") || SKIP_NAMES.has(name)) continue;
21899
- const relpath = path18.relative(topicsDir, full).replace(/\\/g, "/");
22275
+ const relpath = path19.relative(topicsDir, full).replace(/\\/g, "/");
21900
22276
  if (typeFilter && !relpath.startsWith(typeFilter + "/") && !relpath.startsWith(typeFilter + "_")) continue;
21901
22277
  out.push({ relpath, fullpath: full });
21902
22278
  }
@@ -21941,7 +22317,7 @@ function loadJson2(file) {
21941
22317
  }
21942
22318
  function saveJson2(file, data) {
21943
22319
  try {
21944
- fs19.mkdirSync(path18.dirname(file), { recursive: true });
22320
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
21945
22321
  fs19.writeFileSync(file, JSON.stringify(data, null, 2));
21946
22322
  } catch (err) {
21947
22323
  console.warn(`[topics-scorer] usage save failed: ${err.message}`);
@@ -21950,21 +22326,21 @@ function saveJson2(file, data) {
21950
22326
 
21951
22327
  // src/inner-voice/memory-reader.ts
21952
22328
  import fs20 from "node:fs";
21953
- import path19 from "node:path";
22329
+ import path20 from "node:path";
21954
22330
  var US_HALF_LIFE_DAYS = 10;
21955
22331
  var US_MAX_LINES = 60;
21956
22332
  function readRecentMemory(workspace) {
21957
- const dir = path19.join(workspace, "memory");
22333
+ const dir = path20.join(workspace, "memory");
21958
22334
  const now = new Date(Date.now() + 8 * 36e5);
21959
22335
  const today = formatYmd(now);
21960
22336
  const yesterday = formatYmd(new Date(now.getTime() - 864e5));
21961
22337
  return {
21962
- today: readIfExists(path19.join(dir, `${today}.md`)),
21963
- yesterday: readIfExists(path19.join(dir, `${yesterday}.md`))
22338
+ today: readIfExists(path20.join(dir, `${today}.md`)),
22339
+ yesterday: readIfExists(path20.join(dir, `${yesterday}.md`))
21964
22340
  };
21965
22341
  }
21966
22342
  function sampleUs(workspace) {
21967
- const usFile = path19.join(workspace, "memory", "us.md");
22343
+ const usFile = path20.join(workspace, "memory", "us.md");
21968
22344
  let content;
21969
22345
  try {
21970
22346
  content = fs20.readFileSync(usFile, "utf-8");
@@ -22304,7 +22680,7 @@ var InnerVoicePlugin = class {
22304
22680
  }
22305
22681
  /** 读 workspace/prompts/my-inner-voice.md,不存在用 DEFAULT_PROMPT */
22306
22682
  loadPrompt(workspace) {
22307
- const promptPath = path20.join(workspace, "prompts", "my-inner-voice.md");
22683
+ const promptPath = path21.join(workspace, "prompts", "my-inner-voice.md");
22308
22684
  try {
22309
22685
  const content = fs21.readFileSync(promptPath, "utf-8").trim();
22310
22686
  if (content) {
@@ -22378,7 +22754,7 @@ var InnerVoicePlugin = class {
22378
22754
  console.warn(`[inner-voice] emotional-state failed: ${err.message}`);
22379
22755
  }
22380
22756
  try {
22381
- 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");
22382
22758
  lines.push("\n--- SESSION-STATE\uFF08\u5C3E\u90E8\uFF09 ---");
22383
22759
  lines.push(content.slice(-2e3));
22384
22760
  } catch {
@@ -22489,7 +22865,7 @@ var InnerVoicePlugin = class {
22489
22865
  if (Math.random() >= activity.hintProb) {
22490
22866
  return { text: thought, hintTriggered: false, hintText: "" };
22491
22867
  }
22492
- const poolPath = path20.join(this.workspace, "inner-voice", "hints_pool.txt");
22868
+ const poolPath = path21.join(this.workspace, "inner-voice", "hints_pool.txt");
22493
22869
  let hint = "\u60F3\u4ED6\u5C31\u53D1\u6D88\u606F\u5427";
22494
22870
  try {
22495
22871
  const pool = fs21.readFileSync(poolPath, "utf-8").split("\n").map((s2) => s2.trim()).filter(Boolean);
@@ -22517,7 +22893,7 @@ var InnerVoicePlugin = class {
22517
22893
  try {
22518
22894
  const writer = sessions.getWriter(mainSessionId);
22519
22895
  const history = sessions.getHistory(mainSessionId);
22520
- const fullPath = path20.resolve(this.workspace, emoTopic.file);
22896
+ const fullPath = path21.resolve(this.workspace, emoTopic.file);
22521
22897
  const memories = [{
22522
22898
  path: fullPath,
22523
22899
  content: emoTopic.content,
@@ -22545,9 +22921,9 @@ var InnerVoicePlugin = class {
22545
22921
  /** 写 xiaoyi.log(格式对齐旧 memory_whisper.py,便于既有日志分析复用)。 */
22546
22922
  writeLog(status, delivered, activity, hintTriggered, hintText) {
22547
22923
  try {
22548
- const logDir = path20.join(this.workspace, "inner-voice");
22924
+ const logDir = path21.join(this.workspace, "inner-voice");
22549
22925
  fs21.mkdirSync(logDir, { recursive: true });
22550
- const logPath = path20.join(logDir, "xiaoyi.log");
22926
+ const logPath = path21.join(logDir, "xiaoyi.log");
22551
22927
  const ts = formatBeijingTs(/* @__PURE__ */ new Date());
22552
22928
  const hintStatus = hintTriggered ? `YES (${(hintText || "").trim()})` : "no";
22553
22929
  fs21.appendFileSync(
@@ -23086,7 +23462,7 @@ var PluginManager = class {
23086
23462
  // src/voice-chat/plugin.ts
23087
23463
  import { spawn as spawn4, exec } from "node:child_process";
23088
23464
  import net from "node:net";
23089
- import path21 from "node:path";
23465
+ import path22 from "node:path";
23090
23466
  import fs22 from "node:fs";
23091
23467
 
23092
23468
  // src/voice-chat/bridge.ts
@@ -23463,13 +23839,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23463
23839
  }
23464
23840
  getPythonDir() {
23465
23841
  const dir = import.meta.dirname;
23466
- const srcDir = path21.resolve(dir, "..", "src", "voice-chat", "python");
23467
- const localDir = path21.join(dir, "python");
23842
+ const srcDir = path22.resolve(dir, "..", "src", "voice-chat", "python");
23843
+ const localDir = path22.join(dir, "python");
23468
23844
  return fs22.existsSync(srcDir) ? srcDir : localDir;
23469
23845
  }
23470
23846
  startPython() {
23471
23847
  const pythonDir = this.getPythonDir();
23472
- const serverPy = path21.join(pythonDir, "server.py");
23848
+ const serverPy = path22.join(pythonDir, "server.py");
23473
23849
  const pythonBin = this.findPython();
23474
23850
  const args2 = [serverPy];
23475
23851
  if (this.config.pythonPort) args2.push("--port", String(this.config.pythonPort));
@@ -23554,7 +23930,7 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23554
23930
  init_BashTool();
23555
23931
  import { spawn as spawn5, exec as exec2 } from "node:child_process";
23556
23932
  import net2 from "node:net";
23557
- import path22 from "node:path";
23933
+ import path23 from "node:path";
23558
23934
  import fs23 from "node:fs";
23559
23935
 
23560
23936
  // src/memory/cognifold/config.ts
@@ -23578,7 +23954,8 @@ function parseCognifoldConfig(raw) {
23578
23954
  persistDir: raw.persistDir ?? DEFAULTS3.persistDir,
23579
23955
  scopes: raw.scopes,
23580
23956
  readyTimeoutMs: raw.readyTimeoutMs ?? DEFAULTS3.readyTimeoutMs,
23581
- maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts
23957
+ maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts,
23958
+ llm: raw.llm
23582
23959
  };
23583
23960
  }
23584
23961
 
@@ -23586,15 +23963,17 @@ function parseCognifoldConfig(raw) {
23586
23963
  var CogniFoldClient = class {
23587
23964
  baseUrl;
23588
23965
  timeoutMs;
23589
- constructor(baseUrl, timeoutMs = 3e4) {
23966
+ modelName;
23967
+ constructor(baseUrl, timeoutMs = 3e4, modelName = "openai:MiniMax-M3") {
23590
23968
  this.baseUrl = baseUrl.replace(/\/$/, "");
23591
23969
  this.timeoutMs = timeoutMs;
23970
+ this.modelName = modelName;
23592
23971
  }
23593
- async req(path43, options = {}) {
23972
+ async req(path44, options = {}) {
23594
23973
  const ctrl = new AbortController();
23595
23974
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23596
23975
  try {
23597
- const resp = await fetch(`${this.baseUrl}${path43}`, {
23976
+ const resp = await fetch(`${this.baseUrl}${path44}`, {
23598
23977
  ...options,
23599
23978
  signal: ctrl.signal,
23600
23979
  headers: {
@@ -23632,7 +24011,7 @@ var CogniFoldClient = class {
23632
24011
  method: "POST",
23633
24012
  body: JSON.stringify({
23634
24013
  user_id: userId,
23635
- config: { model_name: "openai:deepseek-v4-flash" }
24014
+ config: { model_name: this.modelName }
23636
24015
  })
23637
24016
  });
23638
24017
  }
@@ -23684,8 +24063,8 @@ var CogniFoldClient = class {
23684
24063
  });
23685
24064
  }
23686
24065
  /** 兼容老版命名 */
23687
- async recl(path43, options = {}) {
23688
- return this.req(path43, options);
24066
+ async recl(path44, options = {}) {
24067
+ return this.req(path44, options);
23689
24068
  }
23690
24069
  };
23691
24070
 
@@ -23790,7 +24169,8 @@ var CogniFoldPlugin = class {
23790
24169
  baseUrl = baseUrl.replace(/\/$/, "") + "/api/v1";
23791
24170
  }
23792
24171
  this.config.baseUrl = baseUrl;
23793
- 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);
23794
24174
  }
23795
24175
  workspacePath;
23796
24176
  name = "cognifold";
@@ -23965,16 +24345,16 @@ var CogniFoldPlugin = class {
23965
24345
  const dir = import.meta.dirname;
23966
24346
  const candidates = [
23967
24347
  // 从 dist/ 往回找 src
23968
- path22.resolve(dir, "..", "src", "memory", "cognifold", "python"),
23969
- path22.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
23970
- 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"),
23971
24351
  // 从 src/memory/cognifold/ 找本地
23972
- path22.join(dir, "python"),
24352
+ path23.join(dir, "python"),
23973
24353
  // 从 dist/memory/cognifold/ 找本地
23974
- path22.resolve(dir, "python")
24354
+ path23.resolve(dir, "python")
23975
24355
  ];
23976
24356
  for (const candidate of candidates) {
23977
- if (fs23.existsSync(path22.join(candidate, "cognifold"))) {
24357
+ if (fs23.existsSync(path23.join(candidate, "cognifold"))) {
23978
24358
  return candidate;
23979
24359
  }
23980
24360
  }
@@ -24000,12 +24380,18 @@ var CogniFoldPlugin = class {
24000
24380
  const pythonBin = this.findPython();
24001
24381
  console.log(`[cognifold] Starting Python: ${pythonBin} ${args2.join(" ")}`);
24002
24382
  console.log(`[cognifold] Python dir: ${pythonDir}`);
24003
- if (!fs23.existsSync(path22.join(pythonDir, "cognifold"))) {
24383
+ if (!fs23.existsSync(path23.join(pythonDir, "cognifold"))) {
24004
24384
  console.error(`[cognifold] FATAL: Python module not found at ${pythonDir}/cognifold`);
24005
24385
  throw new Error(`cognifold: python module not found`);
24006
24386
  }
24007
24387
  const childEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
24008
- 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");
24009
24395
  try {
24010
24396
  if (fs23.existsSync(envFile)) {
24011
24397
  const envContent = fs23.readFileSync(envFile, "utf-8");
@@ -24077,7 +24463,7 @@ var CogniFoldPlugin = class {
24077
24463
  init_BashTool();
24078
24464
  import { spawn as spawn6 } from "node:child_process";
24079
24465
  import net3 from "node:net";
24080
- import path23 from "node:path";
24466
+ import path24 from "node:path";
24081
24467
  import fs24 from "node:fs";
24082
24468
 
24083
24469
  // src/memory/everos/config.ts
@@ -24111,7 +24497,8 @@ function parseEverosConfig(raw) {
24111
24497
  llm: raw.llm ?? { model: "glm-5.2", apiKey: "", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
24112
24498
  rerank: raw.rerank ?? { model: "Qwen/Qwen3-Reranker-4B", apiKey: "", baseUrl: "https://api.deepinfra.com/v1/inference" },
24113
24499
  lancedbPath: raw.lancedbPath ?? "",
24114
- sqlitePath: raw.sqlitePath ?? ""
24500
+ sqlitePath: raw.sqlitePath ?? "",
24501
+ minScore: raw.minScore
24115
24502
  };
24116
24503
  }
24117
24504
 
@@ -24149,21 +24536,31 @@ var EverosSearchClient = class {
24149
24536
  clearTimeout(timer);
24150
24537
  }
24151
24538
  }
24152
- /** Search — 3-mode unified endpoint */
24539
+ /** Search — routes to 8101 (agentic) or 8100 (hybrid) based on mode */
24153
24540
  async search(params) {
24154
24541
  const ctrl = new AbortController();
24155
24542
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
24156
24543
  try {
24157
- 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, {
24158
24561
  method: "POST",
24159
24562
  headers: { "Content-Type": "application/json" },
24160
- body: JSON.stringify({
24161
- query: params.query,
24162
- user_id: params.userId || "xiaomei",
24163
- mode: params.mode || "hybrid_agentic",
24164
- top_k: params.topK ?? 5,
24165
- strategy: params.strategy || "multi_query"
24166
- }),
24563
+ body,
24167
24564
  signal: ctrl.signal
24168
24565
  });
24169
24566
  if (!resp.ok) {
@@ -24203,6 +24600,7 @@ var EverosPlugin = class {
24203
24600
  }
24204
24601
  async start(ctx) {
24205
24602
  if (!this.config.enabled) return;
24603
+ await this.ensureVenv();
24206
24604
  try {
24207
24605
  await this.client.healthEveros();
24208
24606
  console.log(`[everos] EverOS already running at ${this.config.everosUrl}`);
@@ -24273,13 +24671,15 @@ var EverosPlugin = class {
24273
24671
  }, 3e5);
24274
24672
  }
24275
24673
  async startEveros() {
24276
- const pythonDir = path23.dirname(this.config.lancedbPath);
24277
- const configPath2 = path23.join(pythonDir, "config.toml");
24674
+ const pythonDir = path24.dirname(this.config.lancedbPath);
24675
+ const configPath2 = path24.join(pythonDir, "config.toml");
24278
24676
  await this.ensureFcntlCompat();
24279
24677
  const venvPython = this.findVenvPython();
24678
+ const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
24280
24679
  const args2 = ["server", "start"];
24281
- const cmd = `${venvPython} ${args2.join(" ")}`;
24680
+ const cmd = `${everosBin} ${args2.join(" ")}`;
24282
24681
  console.log(`[everos] Starting EverOS: ${cmd}`);
24682
+ console.log(`[everos] LLM config: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
24283
24683
  if (process.platform === "win32") {
24284
24684
  const { shell, args: shellArgs } = findShell();
24285
24685
  spawn6(shell, [...shellArgs, cmd], {
@@ -24294,7 +24694,7 @@ var EverosPlugin = class {
24294
24694
  env: { ...process.env, PYTHONUNBUFFERED: "1" }
24295
24695
  });
24296
24696
  }
24297
- await this.waitForReady(`${this.config.everosUrl}/health`, 3e4);
24697
+ await this.waitForReady(`${this.config.everosUrl}/health`, 6e4);
24298
24698
  }
24299
24699
  startAgenticServer() {
24300
24700
  const pythonDir = this.getPythonDir();
@@ -24304,6 +24704,7 @@ var EverosPlugin = class {
24304
24704
  const cmd = `${venvPython} ${args2.join(" ")}`;
24305
24705
  console.log(`[everos] Starting agentic server: ${cmd}`);
24306
24706
  console.log(`[everos] Python dir: ${pythonDir}`);
24707
+ console.log(`[everos] LLM: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
24307
24708
  const childEnv = {
24308
24709
  ...process.env,
24309
24710
  PYTHONUNBUFFERED: "1",
@@ -24312,15 +24713,25 @@ var EverosPlugin = class {
24312
24713
  LLM_API_KEY: this.config.llm.apiKey,
24313
24714
  LLM_BASE_URL: this.config.llm.baseUrl,
24314
24715
  RERANK_API_KEY: this.config.rerank.apiKey,
24315
- RERANK_URL: `${this.config.rerank.baseUrl}/${this.config.rerank.model}`,
24716
+ RERANK_URL: this.config.rerank.baseUrl,
24316
24717
  LANCEDB_PATH: this.config.lancedbPath,
24317
24718
  SQLITE_PATH: this.config.sqlitePath,
24318
24719
  EVEROS_USER_ID: this.config.userId
24319
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}`);
24320
24732
  let child;
24321
24733
  if (process.platform === "win32") {
24322
- const { shell, args: shellArgs } = findShell();
24323
- child = spawn6(shell, [...shellArgs, cmd], {
24734
+ child = spawn6(venvPython, args2, {
24324
24735
  cwd: pythonDir,
24325
24736
  stdio: ["ignore", "pipe", "pipe"],
24326
24737
  env: childEnv
@@ -24350,21 +24761,64 @@ var EverosPlugin = class {
24350
24761
  return child;
24351
24762
  }
24352
24763
  findVenvPython() {
24353
- 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");
24354
24765
  if (process.platform === "win32") {
24355
- 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`);
24356
24811
  }
24357
- return path23.join(stateDir, "everos-venv", "bin", "python");
24358
24812
  }
24359
24813
  getPythonDir() {
24360
24814
  const dir = import.meta.dirname;
24361
24815
  const candidates = [
24362
- path23.join(dir, "python"),
24363
- path23.resolve(dir, "..", "src", "memory", "everos", "python"),
24364
- 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")
24365
24819
  ];
24366
24820
  for (const candidate of candidates) {
24367
- if (fs24.existsSync(path23.join(candidate, "agentic_server.py"))) {
24821
+ if (fs24.existsSync(path24.join(candidate, "agentic_server.py"))) {
24368
24822
  return candidate;
24369
24823
  }
24370
24824
  }
@@ -24373,11 +24827,11 @@ var EverosPlugin = class {
24373
24827
  async ensureFcntlCompat() {
24374
24828
  if (process.platform !== "win32") return;
24375
24829
  const venvPython = this.findVenvPython();
24376
- const venvDir = path23.dirname(path23.dirname(venvPython));
24377
- const sitePackages = path23.join(venvDir, "Lib", "site-packages");
24378
- 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");
24379
24833
  if (fs24.existsSync(target)) return;
24380
- const source = path23.join(this.getPythonDir(), "fcntl_compat.py");
24834
+ const source = path24.join(this.getPythonDir(), "fcntl_compat.py");
24381
24835
  if (fs24.existsSync(source)) {
24382
24836
  try {
24383
24837
  fs24.copyFileSync(source, target);
@@ -24427,7 +24881,7 @@ var EverosPlugin = class {
24427
24881
  init_task_manager();
24428
24882
 
24429
24883
  // src/skills/scanner.ts
24430
- import * as path24 from "node:path";
24884
+ import * as path25 from "node:path";
24431
24885
  import * as fs25 from "node:fs";
24432
24886
  function scanSkills(skillsDir) {
24433
24887
  if (!fs25.existsSync(skillsDir)) {
@@ -24438,7 +24892,7 @@ function scanSkills(skillsDir) {
24438
24892
  const skills = [];
24439
24893
  for (const entry of entries) {
24440
24894
  if (!entry.isDirectory()) continue;
24441
- const skillMdPath = path24.join(skillsDir, entry.name, "SKILL.md");
24895
+ const skillMdPath = path25.join(skillsDir, entry.name, "SKILL.md");
24442
24896
  if (!fs25.existsSync(skillMdPath)) continue;
24443
24897
  try {
24444
24898
  const content = fs25.readFileSync(skillMdPath, "utf-8");
@@ -24506,7 +24960,7 @@ function parseFrontmatter2(content) {
24506
24960
  // src/tools/SkillTool/SkillTool.ts
24507
24961
  init_registry();
24508
24962
  import * as fs26 from "node:fs";
24509
- import * as path25 from "node:path";
24963
+ import * as path26 from "node:path";
24510
24964
 
24511
24965
  // src/tools/SkillTool/constants.ts
24512
24966
  var SKILL_TOOL_NAME2 = "Skill";
@@ -24583,12 +25037,12 @@ Important:
24583
25037
  `;
24584
25038
  }
24585
25039
  function loadSkillContent(skillName) {
24586
- const skillMdPath = path25.join(skillsDirPath, skillName, "SKILL.md");
25040
+ const skillMdPath = path26.join(skillsDirPath, skillName, "SKILL.md");
24587
25041
  if (!fs26.existsSync(skillMdPath)) return null;
24588
25042
  const content = fs26.readFileSync(skillMdPath, "utf-8");
24589
25043
  const bodyMatch = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/);
24590
25044
  const body = bodyMatch ? bodyMatch[1] : content;
24591
- const skillDir = path25.dirname(skillMdPath);
25045
+ const skillDir = path26.dirname(skillMdPath);
24592
25046
  const normalizedDir = process.platform === "win32" ? skillDir.replace(/\\/g, "/") : skillDir;
24593
25047
  let finalContent = `Base directory for this skill: ${normalizedDir}
24594
25048
 
@@ -24863,9 +25317,9 @@ Examples:
24863
25317
  init_registry();
24864
25318
  init_live();
24865
25319
  import fs27 from "node:fs";
24866
- import path26 from "node:path";
25320
+ import path27 from "node:path";
24867
25321
  function getHusbandFeishuId(workspace) {
24868
- const contactsPath = path26.join(workspace, "prompts", "contacts.md");
25322
+ const contactsPath = path27.join(workspace, "prompts", "contacts.md");
24869
25323
  try {
24870
25324
  const text = fs27.readFileSync(contactsPath, "utf-8");
24871
25325
  const m2 = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
@@ -25068,7 +25522,7 @@ Examples:
25068
25522
  init_live();
25069
25523
  init_registry();
25070
25524
  import * as fs28 from "node:fs";
25071
- import * as path27 from "node:path";
25525
+ import * as path28 from "node:path";
25072
25526
  var MIME_MAP = {
25073
25527
  ".jpg": "jpeg",
25074
25528
  ".jpeg": "jpeg",
@@ -25080,7 +25534,7 @@ var MIME_MAP = {
25080
25534
  function resolveLatestImage(specifiedPath, mediaDir) {
25081
25535
  if (specifiedPath && fs28.existsSync(specifiedPath)) return specifiedPath;
25082
25536
  if (!fs28.existsSync(mediaDir)) return null;
25083
- 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);
25084
25538
  return files[0]?.p || null;
25085
25539
  }
25086
25540
  registry.register({
@@ -25104,13 +25558,13 @@ registry.register({
25104
25558
  if (!provider?.streamChat) {
25105
25559
  return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
25106
25560
  }
25107
- const mediaDir = path27.join(ctx.stateDir, "media", "inbound");
25561
+ const mediaDir = path28.join(ctx.stateDir, "media", "inbound");
25108
25562
  const imagePath = resolveLatestImage(args2.image_path, mediaDir);
25109
25563
  if (!imagePath) {
25110
25564
  return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
25111
25565
  }
25112
25566
  const rawPrompt = args2.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
25113
- const ext = path27.extname(imagePath).toLowerCase();
25567
+ const ext = path28.extname(imagePath).toLowerCase();
25114
25568
  const mime = MIME_MAP[ext] || "jpeg";
25115
25569
  const imgB64 = fs28.readFileSync(imagePath).toString("base64");
25116
25570
  const userMsg = {
@@ -25150,13 +25604,13 @@ init_registry();
25150
25604
  import { execFile } from "node:child_process";
25151
25605
  import { promisify } from "node:util";
25152
25606
  import * as fs29 from "node:fs";
25153
- import * as path28 from "node:path";
25607
+ import * as path29 from "node:path";
25154
25608
  import * as os3 from "node:os";
25155
25609
  var execFileAsync = promisify(execFile);
25156
- var VOICE_DIR = path28.join(os3.tmpdir(), "engine-voice");
25610
+ var VOICE_DIR = path29.join(os3.tmpdir(), "engine-voice");
25157
25611
  async function ttsCosyvoice(text, apiKey, model, voice, workspaceId) {
25158
25612
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25159
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25613
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25160
25614
  const script = `
25161
25615
  import sys, json, wave, time, threading
25162
25616
  import dashscope
@@ -25221,7 +25675,7 @@ var GPTSOVITS_REF_TEXT = "\u6625\u7720\u4E0D\u89C9\u6653\uFF0C\u5904\u5904\u95FB
25221
25675
  var GPTSOVITS_REF_LANG = "zh";
25222
25676
  async function ttsGptsovits(text) {
25223
25677
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25224
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25678
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25225
25679
  const params = new URLSearchParams({
25226
25680
  text,
25227
25681
  text_language: "zh",
@@ -25238,7 +25692,7 @@ async function ttsGptsovits(text) {
25238
25692
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
25239
25693
  async function ttsEdge(text) {
25240
25694
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
25241
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25695
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25242
25696
  const script = `
25243
25697
  import asyncio, edge_tts, sys
25244
25698
  async def main():
@@ -25331,7 +25785,7 @@ registry.register({
25331
25785
  } catch (e) {
25332
25786
  return { content: `TTS failed: ${e.message}`, isError: true };
25333
25787
  }
25334
- const ext = path28.extname(audioPath).toLowerCase();
25788
+ const ext = path29.extname(audioPath).toLowerCase();
25335
25789
  const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg" };
25336
25790
  const mimeType = mimeMap[ext] || "audio/mpeg";
25337
25791
  const sizeKB = fs29.statSync(audioPath).size / 1024;
@@ -25362,7 +25816,7 @@ registry.register({
25362
25816
  init_live();
25363
25817
  init_registry();
25364
25818
  import * as fs30 from "node:fs";
25365
- import * as path29 from "node:path";
25819
+ import * as path30 from "node:path";
25366
25820
  var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
25367
25821
  var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
25368
25822
  var DEFAULT_RESOLUTION = "1k";
@@ -25478,7 +25932,7 @@ registry.register({
25478
25932
  const REFERENCES = getReferences(ctx);
25479
25933
  const refName = args2.reference || "default";
25480
25934
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
25481
- const refPath = path29.join(ctx.workspace, refEntry.p);
25935
+ const refPath = path30.join(ctx.workspace, refEntry.p);
25482
25936
  if (!fs30.existsSync(refPath)) {
25483
25937
  return { content: `Error: reference image not found at ${refPath}`, isError: true };
25484
25938
  }
@@ -25497,10 +25951,10 @@ registry.register({
25497
25951
  } catch (err) {
25498
25952
  return { content: `Selfie generation failed: ${err.message}`, isError: true };
25499
25953
  }
25500
- const imagesDir = path29.join(ctx.workspace, "images");
25954
+ const imagesDir = path30.join(ctx.workspace, "images");
25501
25955
  if (!fs30.existsSync(imagesDir)) fs30.mkdirSync(imagesDir, { recursive: true });
25502
25956
  const filename = `selfie_${Date.now()}.jpg`;
25503
- const outputPath = path29.join(imagesDir, filename);
25957
+ const outputPath = path30.join(imagesDir, filename);
25504
25958
  fs30.writeFileSync(outputPath, imageBuffer);
25505
25959
  const mgr = ctx.channelManager;
25506
25960
  if (mgr) {
@@ -25512,11 +25966,11 @@ registry.register({
25512
25966
  mimeType: "image/jpeg"
25513
25967
  });
25514
25968
  } catch (err) {
25515
- 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 };
25516
25970
  }
25517
25971
  return { content: `Selfie sent! Mode: ${mode}, Provider: ${getProvider(ctx)}, Ref: ${refEntry.name}` };
25518
25972
  }
25519
- 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)}` };
25520
25974
  },
25521
25975
  isConcurrencySafe: () => false,
25522
25976
  interruptBehavior: () => "block",
@@ -25983,14 +26437,14 @@ init_planModeState();
25983
26437
 
25984
26438
  // src/utils/plans.ts
25985
26439
  import * as fs32 from "node:fs";
25986
- import * as path31 from "node:path";
26440
+ import * as path32 from "node:path";
25987
26441
  import * as crypto4 from "node:crypto";
25988
26442
  var MAX_SLUG_RETRIES = 10;
25989
26443
  function generateSlug() {
25990
26444
  return crypto4.randomBytes(4).toString("hex");
25991
26445
  }
25992
26446
  function getPlansDirectory(stateDir) {
25993
- const plansDir = path31.join(stateDir, "plans");
26447
+ const plansDir = path32.join(stateDir, "plans");
25994
26448
  fs32.mkdirSync(plansDir, { recursive: true });
25995
26449
  return plansDir;
25996
26450
  }
@@ -26001,7 +26455,7 @@ function getPlanSlug(sessionId, stateDir) {
26001
26455
  const plansDir = getPlansDirectory(stateDir);
26002
26456
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
26003
26457
  slug = generateSlug();
26004
- const filePath = path31.join(plansDir, `${slug}.md`);
26458
+ const filePath = path32.join(plansDir, `${slug}.md`);
26005
26459
  if (!fs32.existsSync(filePath)) {
26006
26460
  break;
26007
26461
  }
@@ -26013,9 +26467,9 @@ function getPlanSlug(sessionId, stateDir) {
26013
26467
  function getPlanFilePath(sessionId, stateDir, agentId) {
26014
26468
  const slug = getPlanSlug(sessionId, stateDir);
26015
26469
  if (!agentId) {
26016
- return path31.join(getPlansDirectory(stateDir), `${slug}.md`);
26470
+ return path32.join(getPlansDirectory(stateDir), `${slug}.md`);
26017
26471
  }
26018
- return path31.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26472
+ return path32.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26019
26473
  }
26020
26474
  function getPlan(sessionId, stateDir, agentId) {
26021
26475
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
@@ -26674,14 +27128,14 @@ var EverosSearchSchema = {
26674
27128
  type: "object",
26675
27129
  properties: {
26676
27130
  query: { type: "string", description: "\u641C\u7D22\u67E5\u8BE2" },
26677
- 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)" }
26678
27133
  },
26679
27134
  required: ["query"]
26680
27135
  };
26681
27136
  function createEverosSearchTool(everosCfg) {
26682
27137
  const agenticUrl = (everosCfg?.agenticUrl || "http://127.0.0.1:8101").replace(/\/$/, "");
26683
27138
  const userId = everosCfg?.userId || "xiaomei";
26684
- const defaultMode = everosCfg?.defaultMode || "hybrid_agentic";
26685
27139
  return {
26686
27140
  name: "memory_search",
26687
27141
  description: "Mandatory recall step: semantically search memory before answering questions about prior work, decisions, dates, people, preferences, or todos.",
@@ -26690,6 +27144,7 @@ function createEverosSearchTool(everosCfg) {
26690
27144
  const query = args2.query;
26691
27145
  if (!query) return { content: "\u7F3A\u5C11 query \u53C2\u6570" };
26692
27146
  const topK = args2.maxResults ?? 10;
27147
+ const mode = args2.mode || "hybrid_agentic";
26693
27148
  const ctrl = new AbortController();
26694
27149
  const timer = setTimeout(() => ctrl.abort(), 3e4);
26695
27150
  try {
@@ -26699,7 +27154,7 @@ function createEverosSearchTool(everosCfg) {
26699
27154
  body: JSON.stringify({
26700
27155
  query,
26701
27156
  user_id: userId,
26702
- mode: defaultMode,
27157
+ mode,
26703
27158
  top_k: topK
26704
27159
  }),
26705
27160
  signal: ctrl.signal
@@ -26707,7 +27162,7 @@ function createEverosSearchTool(everosCfg) {
26707
27162
  clearTimeout(timer);
26708
27163
  if (!resp.ok) {
26709
27164
  const text = await resp.text();
26710
- console.warn(`[everos] search failed: ${resp.status} ${text.slice(0, 200)}`);
27165
+ console.warn(`[memory_search] failed: ${resp.status} ${text.slice(0, 200)}`);
26711
27166
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26712
27167
  }
26713
27168
  const data = await resp.json();
@@ -26716,7 +27171,7 @@ function createEverosSearchTool(everosCfg) {
26716
27171
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26717
27172
  }
26718
27173
  const formatted = episodes.map((ep, i) => {
26719
- 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})` : "";
26720
27175
  const subject = ep.subject || "";
26721
27176
  const ts = ep.timestamp ? ` [${ep.timestamp}]` : "";
26722
27177
  return `### ${i + 1}. ${subject}${ts}${score}
@@ -26728,9 +27183,9 @@ ${formatted}` };
26728
27183
  } catch (err) {
26729
27184
  clearTimeout(timer);
26730
27185
  if (err.name === "AbortError") {
26731
- console.warn(`[everos] search timeout: ${query.slice(0, 50)}`);
27186
+ console.warn(`[memory_search] timeout: ${query.slice(0, 50)}`);
26732
27187
  } else {
26733
- console.warn(`[everos] search error: ${err.message}`);
27188
+ console.warn(`[memory_search] error: ${err.message}`);
26734
27189
  }
26735
27190
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26736
27191
  }
@@ -27229,9 +27684,9 @@ async function startEngine(config2, opts) {
27229
27684
  process.env.ENGINE_MEDIA_DIR = config2.mediaDir;
27230
27685
  process.env.ENGINE7_WORKSPACE = config2.workspace;
27231
27686
  process.env.OPENCLAW_WORKSPACE = config2.workspace;
27232
- fs41.mkdirSync(path42.join(config2.stateDir, "agents", "main", "memory"), { recursive: true });
27233
- fs41.mkdirSync(path42.join(config2.stateDir, "agents", "main", "sessions"), { recursive: true });
27234
- 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 });
27235
27690
  fs41.mkdirSync(config2.workspace, { recursive: true });
27236
27691
  fs41.mkdirSync(config2.mediaDir, { recursive: true });
27237
27692
  try {
@@ -27341,7 +27796,7 @@ async function startEngine(config2, opts) {
27341
27796
  const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
27342
27797
  initSessionMemory2({
27343
27798
  workspace: config2.workspace,
27344
- stateDir: path42.join(config2.stateDir, "session-memory"),
27799
+ stateDir: path43.join(config2.stateDir, "session-memory"),
27345
27800
  provider,
27346
27801
  model: config2.provider.modelId || config2.model || "deepseek-v4-flash",
27347
27802
  features: config2.profile.features
@@ -27371,9 +27826,9 @@ async function startEngine(config2, opts) {
27371
27826
  if (config2.hooks) {
27372
27827
  loadHooksFromConfig({ hooks: config2.hooks });
27373
27828
  }
27374
- const hooksPath = path42.join(config2.workspace, ".hooks.json");
27829
+ const hooksPath = path43.join(config2.workspace, ".hooks.json");
27375
27830
  loadHooksFromFile(hooksPath);
27376
- const settingsHooksPath = path42.join(config2.stateDir, "settings.json");
27831
+ const settingsHooksPath = path43.join(config2.stateDir, "settings.json");
27377
27832
  loadHooksFromFile(settingsHooksPath);
27378
27833
  console.log(`[hooks] Loaded hooks configuration`);
27379
27834
  registerCallbackHook("PreCompact", {
@@ -27387,15 +27842,15 @@ async function startEngine(config2, opts) {
27387
27842
  const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
27388
27843
  const dateStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")}`;
27389
27844
  const timeStr = `${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
27390
- const dailyDir = path42.join(workspace, "memory", "daily");
27391
- const dailyPath = path42.join(dailyDir, `${dateStr}.md`);
27845
+ const dailyDir = path43.join(workspace, "memory", "daily");
27846
+ const dailyPath = path43.join(dailyDir, `${dateStr}.md`);
27392
27847
  try {
27393
27848
  const fs42 = await import("node:fs");
27394
27849
  if (!fs42.existsSync(dailyDir)) {
27395
27850
  fs42.mkdirSync(dailyDir, { recursive: true });
27396
27851
  }
27397
- const sessionsDir = path42.join(config2.stateDir, "agents", "main", "sessions");
27398
- 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`);
27399
27854
  const recentLines = [];
27400
27855
  if (fs42.existsSync(sessionFile)) {
27401
27856
  const content = fs42.readFileSync(sessionFile, "utf-8");
@@ -27448,7 +27903,7 @@ ${entry}`);
27448
27903
  if (!workspace) return { continue: true };
27449
27904
  try {
27450
27905
  const fs42 = await import("node:fs");
27451
- const bufferPath = path42.join(workspace, "memory", "working-buffer.md");
27906
+ const bufferPath = path43.join(workspace, "memory", "working-buffer.md");
27452
27907
  if (fs42.existsSync(bufferPath)) {
27453
27908
  const stat4 = fs42.statSync(bufferPath);
27454
27909
  const ageMs = Date.now() - stat4.mtimeMs;
@@ -27501,7 +27956,7 @@ ${content}`
27501
27956
  return `${hr}h ${remMin}m`;
27502
27957
  }
27503
27958
  if (config2.skills?.enabled !== false) {
27504
- 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");
27505
27960
  const modelDef2 = config2.provider.models.find((m2) => m2.id === config2.model);
27506
27961
  const contextWindowTokens = modelDef2?.contextWindow;
27507
27962
  const skills = scanSkills(skillsDir);
@@ -27520,7 +27975,7 @@ ${content}`
27520
27975
  workspace: config2.workspace
27521
27976
  });
27522
27977
  const systemPrompt = [systemStable, systemDynamic].join("\n\n");
27523
- const promptDumpPath = path42.join(config2.workspace, ".system-prompt.txt");
27978
+ const promptDumpPath = path43.join(config2.workspace, ".system-prompt.txt");
27524
27979
  fs41.writeFileSync(promptDumpPath, systemPrompt);
27525
27980
  console.log(`System prompt: ${systemStable.length} chars stable + ${systemDynamic.length} chars dynamic \u2192 ${promptDumpPath}`);
27526
27981
  const modelDef = config2.provider.models.find((m2) => m2.id === config2.model);
@@ -27631,20 +28086,23 @@ ${content}`
27631
28086
  enabled: true,
27632
28087
  url: everosCfg.everosUrl || "http://127.0.0.1:8100",
27633
28088
  appId: everosCfg.userId || "default",
27634
- userId: everosCfg.userId || "default"
28089
+ userId: everosCfg.userId || "default",
28090
+ agentName: everosCfg.agentName || everosCfg.userId || "assistant"
27635
28091
  });
27636
28092
  sessions.onWriterCreated = (writer, sessionId) => {
27637
28093
  writer.onMessageWritten = (msg2) => {
28094
+ console.log(`[everos-sync] onMessageWritten fired: role=${msg2.role} len=${msg2.text.length}`);
27638
28095
  everosSync.push({
27639
28096
  sessionId: writer.engineSessionId || sessionId,
27640
28097
  role: msg2.role === "toolResult" ? "tool" : msg2.role,
27641
28098
  text: msg2.text,
27642
28099
  timestamp: new Date(msg2.timestamp).getTime()
27643
- }).catch(() => {
27644
- });
28100
+ }).catch((e) => console.warn(`[everos-sync] push error: ${e}`));
27645
28101
  };
27646
28102
  };
27647
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`);
27648
28106
  }
27649
28107
  const channelManager = new ChannelManager();
27650
28108
  const memoryRecallProvider = createMemorySideProvider(
@@ -27675,6 +28133,7 @@ ${content}`
27675
28133
  recallProvider: memoryRecallProvider || void 0,
27676
28134
  extractProvider: memoryExtractProvider || void 0,
27677
28135
  topics: config2.topics,
28136
+ everosCfg: config2.everos,
27678
28137
  mcpManager
27679
28138
  };
27680
28139
  if (visionEngine && visionConfig) {
@@ -28595,8 +29054,8 @@ Auto-routing disabled \u2014 all messages use this model.
28595
29054
  let writePath = configPath2;
28596
29055
  if (configPath2 && !fs41.existsSync(configPath2)) {
28597
29056
  const __pFile = fileURLToPath(import.meta.url);
28598
- const __pDir = path42.dirname(__pFile);
28599
- 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));
28600
29059
  if (fs41.existsSync(altPath)) {
28601
29060
  console.warn(`[primary] Config not found at ${configPath2}, falling back to ${altPath}`);
28602
29061
  writePath = altPath;
@@ -28876,7 +29335,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
28876
29335
  const ext = detected.split("/")[1] || "png";
28877
29336
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
28878
29337
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
28879
- const savedPath = path42.join(config2.mediaDir, `${imageId}.${ext}`);
29338
+ const savedPath = path43.join(config2.mediaDir, `${imageId}.${ext}`);
28880
29339
  fs41.writeFileSync(savedPath, resized.buffer);
28881
29340
  savedPaths.push(savedPath);
28882
29341
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
@@ -28903,7 +29362,7 @@ ${pathStr}` }];
28903
29362
  }
28904
29363
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
28905
29364
  if (nonImageAttachments && nonImageAttachments.length > 0) {
28906
- const outDir = path42.join(config2.mediaDir, sessionId);
29365
+ const outDir = path43.join(config2.mediaDir, sessionId);
28907
29366
  fs41.mkdirSync(outDir, { recursive: true });
28908
29367
  const resolved = [];
28909
29368
  for (const att of nonImageAttachments) {
@@ -28912,8 +29371,8 @@ ${pathStr}` }];
28912
29371
  const resp = await fetch(att.url);
28913
29372
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
28914
29373
  const buffer = Buffer.from(await resp.arrayBuffer());
28915
- const safeName2 = path42.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
28916
- 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);
28917
29376
  fs41.writeFileSync(savedPath, buffer);
28918
29377
  resolved.push(savedPath);
28919
29378
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
@@ -29261,7 +29720,8 @@ ${pathStr}` }];
29261
29720
  if (config2.cognifold?.intentWatcher?.enabled) {
29262
29721
  try {
29263
29722
  const { registerCognifoldIntentWatcher: registerCognifoldIntentWatcher2 } = await Promise.resolve().then(() => (init_cognifold_intent_watcher(), cognifold_intent_watcher_exports));
29264
- const cfSessionId = config2.cognifold.sessionId;
29723
+ const sm = globalThis.__cognifoldSessions;
29724
+ const cfSessionId = sm?.getSessionId?.("main") || config2.cognifold.sessionId;
29265
29725
  if (!cfSessionId) {
29266
29726
  console.warn("[cognifold] intent-watcher: config.cognifold.sessionId \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 watcher");
29267
29727
  } else {
@@ -29274,7 +29734,7 @@ ${pathStr}` }];
29274
29734
  console.warn("[cognifold] watcher: config.workspace \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 proactive \u5199\u5165");
29275
29735
  return;
29276
29736
  }
29277
- const pFile = path42.join(wsDir, ".cognifold-proactive.json");
29737
+ const pFile = path43.join(wsDir, ".cognifold-proactive.json");
29278
29738
  const cognifoldBaseUrl = config2.cognifold?.baseUrl || "http://127.0.0.1:9001";
29279
29739
  const cognifoldSessionId = cfSessionId;
29280
29740
  const rawSuggestions = data.suggestions || data.actions || (data.intent_id ? [data] : []);
@@ -29328,7 +29788,7 @@ ${pathStr}` }];
29328
29788
  console.error(`[cognifold] failed to save proactive: ${e.message}`);
29329
29789
  }
29330
29790
  if (enriched.length > 0) {
29331
- const promptFile = path42.join(config2.workspace, "prompts", "cognifold-proactive.md");
29791
+ const promptFile = path43.join(config2.workspace, "prompts", "cognifold-proactive.md");
29332
29792
  const promptText = fs41.existsSync(promptFile) ? fs41.readFileSync(promptFile, "utf-8") : "[CogniFold proactive] \u6709 " + enriched.length + " \u4E2A action \u5230\u671F\u4E86";
29333
29793
  const actionsJson = JSON.stringify(enriched, null, 2);
29334
29794
  const sessionId = cfSessionId;
@@ -29434,9 +29894,9 @@ async function doReloadConfig(config2, deps, provider) {
29434
29894
  let reloadConfigPath = savedConfigPath;
29435
29895
  if (!fs41.existsSync(reloadConfigPath)) {
29436
29896
  const __filename = fileURLToPath(import.meta.url);
29437
- const __dirname = path42.dirname(__filename);
29438
- const engineConfigsDir = path42.resolve(__dirname, "../configs");
29439
- 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));
29440
29900
  if (fs41.existsSync(altPath)) {
29441
29901
  console.warn(`[reload] Config not found at ${reloadConfigPath}, falling back to ${altPath} (dev mode)`);
29442
29902
  reloadConfigPath = altPath;
@@ -29489,7 +29949,7 @@ async function doReloadConfig(config2, deps, provider) {
29489
29949
  } catch (err) {
29490
29950
  console.error(`[reload] Failed: ${err.message}`);
29491
29951
  try {
29492
- 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}
29493
29953
  ${err.stack}
29494
29954
  `);
29495
29955
  } catch {
@@ -29501,17 +29961,17 @@ function startConfigWatcher(config2, deps, provider) {
29501
29961
  const raw = config2._configFilePath;
29502
29962
  let configPath2 = raw;
29503
29963
  if (!fs41.existsSync(configPath2)) {
29504
- configPath2 = path42.resolve(raw);
29964
+ configPath2 = path43.resolve(raw);
29505
29965
  }
29506
29966
  if (!fs41.existsSync(configPath2)) {
29507
29967
  const __filename2 = fileURLToPath(import.meta.url);
29508
- const __dirname22 = path42.dirname(__filename2);
29509
- configPath2 = path42.resolve(__dirname22, "..", raw);
29968
+ const __dirname22 = path43.dirname(__filename2);
29969
+ configPath2 = path43.resolve(__dirname22, "..", raw);
29510
29970
  }
29511
29971
  if (!fs41.existsSync(configPath2)) {
29512
29972
  console.warn(`[config-watch] config path invalid: ${configPath2}, watcher disabled`);
29513
29973
  try {
29514
- 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}
29515
29975
  `);
29516
29976
  } catch {
29517
29977
  }
@@ -29523,13 +29983,13 @@ function startConfigWatcher(config2, deps, provider) {
29523
29983
  debounceTimer = setTimeout(async () => {
29524
29984
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
29525
29985
  try {
29526
- 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
29527
29987
  `);
29528
29988
  } catch {
29529
29989
  }
29530
29990
  const result = await doReloadConfig(config2, deps, provider);
29531
29991
  try {
29532
- 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(",")}
29533
29993
  `);
29534
29994
  } catch {
29535
29995
  }
@@ -29538,14 +29998,14 @@ function startConfigWatcher(config2, deps, provider) {
29538
29998
  watcher.on("error", (err) => {
29539
29999
  console.error(`[config-watch] error: ${err.message}`);
29540
30000
  try {
29541
- 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}
29542
30002
  `);
29543
30003
  } catch {
29544
30004
  }
29545
30005
  });
29546
30006
  console.log(`[config-watch] watching ${configPath2}`);
29547
30007
  try {
29548
- 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}
29549
30009
  `);
29550
30010
  } catch {
29551
30011
  }