engine7 7.1.23 → 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.
@@ -725,8 +725,10 @@ async function executeStopHooks(ctx, signal, lastAssistantMessage) {
725
725
  cwd: ctx.cwd,
726
726
  stop_hook_active: false,
727
727
  last_assistant_message: lastAssistantMessage,
728
- channel: ctx.channel
728
+ channel: ctx.channel,
729
729
  // 让 callback hook 能判断来源
730
+ source: ctx.source || ""
731
+ // 消息来源(heartbeat/cron/system 等注入 turn 可据此跳过)
730
732
  };
731
733
  return executeHooks("Stop", hookInput, ctx, signal);
732
734
  }
@@ -1078,6 +1080,7 @@ function collectSurfacedMemories(messages) {
1078
1080
  return { paths };
1079
1081
  }
1080
1082
  function normalizePath(p2) {
1083
+ if (p2.startsWith("everos://")) return p2;
1081
1084
  return pathResolve(p2);
1082
1085
  }
1083
1086
  function memoryHeader(filePath, mtimeMs) {
@@ -2524,10 +2527,12 @@ function breakdownMessages(messages) {
2524
2527
  if (memCount === 0) console.log(`[context-analyzer] WARNING: relevant_memories attachment with 0 memories! keys=${Object.keys(m2.attachment).join(",")}`);
2525
2528
  for (const mem of m2.attachment.memories || []) {
2526
2529
  const fileName = mem.path.split(/[/\\]/).pop() || mem.path;
2530
+ const scoreMatch = mem.header?.match(/score=([\d.]+)/);
2527
2531
  recalledTopics.push({
2528
2532
  path: fileName,
2529
2533
  tokens: roughTokenCountEstimation(mem.content),
2530
- stable: false
2534
+ stable: false,
2535
+ score: scoreMatch ? scoreMatch[1] : void 0
2531
2536
  });
2532
2537
  }
2533
2538
  }
@@ -2738,13 +2743,25 @@ function formatContextReport(report) {
2738
2743
  output += `### Recalled Memories (${rt.length})
2739
2744
 
2740
2745
  `;
2741
- output += `| File | Tokens |
2746
+ const hasScore = rt.some((t) => t.score);
2747
+ if (hasScore) {
2748
+ output += `| File | Score | Tokens |
2742
2749
  `;
2743
- output += `|------|--------|
2750
+ output += `|------|-------|--------|
2751
+ `;
2752
+ for (const t of rt) {
2753
+ output += `| ${t.path} | ${t.score ?? "-"} | ${formatTokens(t.tokens)} |
2754
+ `;
2755
+ }
2756
+ } else {
2757
+ output += `| File | Tokens |
2758
+ `;
2759
+ output += `|------|--------|
2744
2760
  `;
2745
- for (const t of rt) {
2746
- output += `| ${t.path} | ${formatTokens(t.tokens)} |
2761
+ for (const t of rt) {
2762
+ output += `| ${t.path} | ${formatTokens(t.tokens)} |
2747
2763
  `;
2764
+ }
2748
2765
  }
2749
2766
  output += "\n";
2750
2767
  }
@@ -3082,7 +3099,8 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
3082
3099
  sessionId: context?.sessionId || "default",
3083
3100
  workspace: context?.workspace || "",
3084
3101
  channel: context?.channel || "",
3085
- cwd: context?.workspace || ""
3102
+ cwd: context?.workspace || "",
3103
+ source: typeof context?.source === "string" ? context.source : ""
3086
3104
  };
3087
3105
  const stopResult = await executeStopHooks(stopCtx, ac.signal, textContent);
3088
3106
  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(args, searchPath, signal) {
7220
7238
  return new Promise((resolve10) => {
7221
7239
  const fullArgs = [...args, 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 (args) => {
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 (args.task_id) {
11226
- const file = path43.join(resultsDir, `${args.task_id}.json`);
11244
+ const file = path44.join(resultsDir, `${args.task_id}.json`);
11227
11245
  if (!fs42.existsSync(file)) {
11228
11246
  return { content: `\u4EFB\u52A1 ${args.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 ? "..." : ""}`;
@@ -11401,7 +11419,7 @@ __export(manager_exports, {
11401
11419
  McpManager: () => McpManager
11402
11420
  });
11403
11421
  import * as fs40 from "node:fs";
11404
- import * as path40 from "node:path";
11422
+ import * as path41 from "node:path";
11405
11423
  import { Client as Client3 } from "@modelcontextprotocol/sdk/client/index.js";
11406
11424
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11407
11425
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -11434,9 +11452,9 @@ function convertInputSchema(inputSchema) {
11434
11452
  }
11435
11453
  function persistBinary(base64Data, mimeType, persistId) {
11436
11454
  const ext = mimeType?.split("/")[1] || "bin";
11437
- const dir = path40.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
11455
+ const dir = path41.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
11438
11456
  fs40.mkdirSync(dir, { recursive: true });
11439
- const filepath = path40.join(dir, `${persistId}.${ext}`);
11457
+ const filepath = path41.join(dir, `${persistId}.${ext}`);
11440
11458
  try {
11441
11459
  const buf = Buffer.from(base64Data, "base64");
11442
11460
  fs40.writeFileSync(filepath, buf);
@@ -11773,7 +11791,7 @@ __export(resources_exports, {
11773
11791
  registerMcpResourceTools: () => registerMcpResourceTools,
11774
11792
  unregisterMcpResourceTools: () => unregisterMcpResourceTools
11775
11793
  });
11776
- import * as path41 from "node:path";
11794
+ import * as path42 from "node:path";
11777
11795
  function registerMcpResourceTools(manager) {
11778
11796
  mcpManagerRef = manager;
11779
11797
  registry.register(listResourcesTool);
@@ -11791,7 +11809,7 @@ var init_resources = __esm({
11791
11809
  "use strict";
11792
11810
  init_registry();
11793
11811
  MAX_RESULT_CHARS2 = 1e5;
11794
- MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path41.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
11812
+ MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path42.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
11795
11813
  MCP_LIST_RESOURCES_TOOL = "mcp__list_resources";
11796
11814
  MCP_READ_RESOURCE_TOOL = "mcp__read_resource";
11797
11815
  mcpManagerRef = null;
@@ -11881,11 +11899,21 @@ var everos_sync_exports = {};
11881
11899
  __export(everos_sync_exports, {
11882
11900
  createEverosSync: () => createEverosSync
11883
11901
  });
11902
+ function parseMeta(text) {
11903
+ const m2 = text.match(/^\[meta:\s*(.+?)\s*\((.+?)\)\s*@(\S+)\s*[^\]]*\]/);
11904
+ if (!m2) return null;
11905
+ return { senderName: m2[1].trim(), senderId: m2[2].trim(), platform: m2[3].trim() };
11906
+ }
11884
11907
  function createEverosSync(cfg) {
11885
- const { enabled, url, appId, userId } = cfg;
11908
+ const { enabled, url, appId, userId, agentName } = cfg;
11886
11909
  async function push(event) {
11887
11910
  if (!enabled) return;
11888
11911
  if (!event.text.trim()) return;
11912
+ return;
11913
+ const meta = event.role === "user" ? parseMeta(event.text) : null;
11914
+ const senderId = appId;
11915
+ const senderName = meta?.senderName ?? (event.role === "assistant" ? agentName : void 0) ?? event.senderName ?? event.role;
11916
+ console.log(`[everos-sync] role=${event.role} sender_id=${senderId} sender_name=${senderName} metaParsed=${!!meta} textLen=${event.text.length}`);
11889
11917
  try {
11890
11918
  const resp = await fetch(`${url}/api/v1/memory/add`, {
11891
11919
  method: "POST",
@@ -11895,8 +11923,8 @@ function createEverosSync(cfg) {
11895
11923
  app_id: appId,
11896
11924
  project_id: "default",
11897
11925
  messages: [{
11898
- sender_id: event.role === "user" ? "user" : appId,
11899
- sender_name: event.senderName || event.role,
11926
+ sender_id: senderId,
11927
+ sender_name: senderName,
11900
11928
  role: event.role,
11901
11929
  timestamp: event.timestamp,
11902
11930
  content: event.text
@@ -11920,13 +11948,17 @@ function createEverosSync(cfg) {
11920
11948
  session_id: events[0].sessionId,
11921
11949
  app_id: appId,
11922
11950
  project_id: "default",
11923
- messages: events.map((e) => ({
11924
- sender_id: e.role === "user" ? "user" : appId,
11925
- sender_name: e.senderName || e.role,
11926
- role: e.role,
11927
- timestamp: e.timestamp,
11928
- content: e.text
11929
- }))
11951
+ messages: events.map((e) => {
11952
+ const meta = e.role === "user" ? parseMeta(e.text) : null;
11953
+ const name = meta?.senderName ?? (e.role === "assistant" ? agentName : void 0) ?? e.senderName ?? e.role;
11954
+ return {
11955
+ sender_id: appId,
11956
+ sender_name: name,
11957
+ role: e.role,
11958
+ timestamp: e.timestamp,
11959
+ content: e.text
11960
+ };
11961
+ })
11930
11962
  }),
11931
11963
  signal: AbortSignal.timeout(3e4)
11932
11964
  });
@@ -12028,10 +12060,10 @@ function ensureLoaded(workspace, configIds) {
12028
12060
  if (!state.blockedUserIds.includes(id)) state.blockedUserIds.push(id);
12029
12061
  }
12030
12062
  }
12031
- const path43 = join36(workspace, ".reply-blocklist.json");
12063
+ const path44 = join36(workspace, ".reply-blocklist.json");
12032
12064
  try {
12033
- if (existsSync24(path43)) {
12034
- const raw = readFileSync26(path43, "utf-8");
12065
+ if (existsSync24(path44)) {
12066
+ const raw = readFileSync26(path44, "utf-8");
12035
12067
  const parsed = JSON.parse(raw);
12036
12068
  if (parsed.blockedUserIds) {
12037
12069
  for (const id of parsed.blockedUserIds) {
@@ -12047,9 +12079,9 @@ function ensureLoaded(workspace, configIds) {
12047
12079
  loaded = true;
12048
12080
  }
12049
12081
  function save(workspace) {
12050
- const path43 = join36(workspace, ".reply-blocklist.json");
12082
+ const path44 = join36(workspace, ".reply-blocklist.json");
12051
12083
  try {
12052
- writeFileSync15(path43, JSON.stringify(state, null, 2), "utf-8");
12084
+ writeFileSync15(path44, JSON.stringify(state, null, 2), "utf-8");
12053
12085
  } catch (err) {
12054
12086
  console.warn(`[reply-blocklist] Failed to save: ${err.message}`);
12055
12087
  }
@@ -12646,7 +12678,7 @@ var init_cognifold_intent_watcher = __esm({
12646
12678
  });
12647
12679
 
12648
12680
  // src/engine-startup.ts
12649
- import * as path42 from "node:path";
12681
+ import * as path43 from "node:path";
12650
12682
  import * as fs41 from "node:fs";
12651
12683
  import { fileURLToPath } from "node:url";
12652
12684
 
@@ -13990,11 +14022,11 @@ var DiscordAdapter = class _DiscordAdapter {
13990
14022
  /** 发送媒体附件(图片/文件/音频)— discord.js channel.send({ files }) */
13991
14023
  async sendFile(target, message, attachment) {
13992
14024
  const fs42 = await import("node:fs");
13993
- const path43 = await import("node:path");
14025
+ const path44 = await import("node:path");
13994
14026
  if (!fs42.existsSync(attachment.path)) {
13995
14027
  throw new Error(`File not found: ${attachment.path}`);
13996
14028
  }
13997
- const filename = attachment.filename || path43.basename(attachment.path);
14029
+ const filename = attachment.filename || path44.basename(attachment.path);
13998
14030
  const fileBuffer = fs42.readFileSync(attachment.path);
13999
14031
  const filePayload = {
14000
14032
  attachment: fileBuffer,
@@ -14436,11 +14468,11 @@ var FeishuAdapter = class _FeishuAdapter {
14436
14468
  /** 发送媒体附件(图片/文件) */
14437
14469
  async sendFile(target, message, attachment) {
14438
14470
  const fs42 = await import("node:fs");
14439
- const path43 = await import("node:path");
14471
+ const path44 = await import("node:path");
14440
14472
  if (!fs42.existsSync(attachment.path)) {
14441
14473
  throw new Error(`File not found: ${attachment.path}`);
14442
14474
  }
14443
- const filename = attachment.filename || path43.basename(attachment.path);
14475
+ const filename = attachment.filename || path44.basename(attachment.path);
14444
14476
  const fileBuffer = fs42.readFileSync(attachment.path);
14445
14477
  const receiveIdType = target.startsWith("ou_") ? "open_id" : "chat_id";
14446
14478
  const mimeType = attachment.mimeType || "application/octet-stream";
@@ -18052,7 +18084,8 @@ ${skillsListing}`);
18052
18084
  parts.push(getEnvInfoSection(options.workspace));
18053
18085
  const now = /* @__PURE__ */ new Date();
18054
18086
  const dateStr = now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
18055
- parts.push(`\u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18087
+ parts.push(`# \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587
18088
+ \u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18056
18089
  console.log(`[dynamic-prompt] Loaded: ${loaded2.length > 0 ? loaded2.join(", ") : "(none)"}`);
18057
18090
  return parts.join("\n\n");
18058
18091
  }
@@ -18354,6 +18387,169 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
18354
18387
  }));
18355
18388
  }
18356
18389
 
18390
+ // src/memory/memdir/findRelevantMemoriesEveros.ts
18391
+ var ROUND1_TOP_N = 30;
18392
+ var RERANK_BATCH_SIZE = 100;
18393
+ function formatEverosHeader(ep) {
18394
+ const score = ep.score.toFixed(3);
18395
+ const ts = ep.timestamp?.slice(0, 10) ?? "";
18396
+ let ageLabel = "";
18397
+ if (ts) {
18398
+ const days = Math.floor((Date.now() - new Date(ts).getTime()) / 864e5);
18399
+ if (days <= 1) ageLabel = "\u4ECA\u5929";
18400
+ else if (days <= 3) ageLabel = `${days}\u5929\u524D`;
18401
+ else if (days <= 14) ageLabel = `${days}\u5929\u524D`;
18402
+ else if (days <= 30) ageLabel = `~${Math.ceil(days / 7)}\u5468\u524D`;
18403
+ else ageLabel = `~${Math.ceil(days / 30)}\u4E2A\u6708\u524D`;
18404
+ }
18405
+ return `[EverOS score=${score} ${ts} (${ageLabel})]`;
18406
+ }
18407
+ async function hybridSearch(query, everosUrl, userId, topK) {
18408
+ const resp = await fetch(`${everosUrl}/api/v1/memory/search`, {
18409
+ method: "POST",
18410
+ headers: { "Content-Type": "application/json" },
18411
+ body: JSON.stringify({
18412
+ query: query.slice(0, 2e3),
18413
+ user_id: userId,
18414
+ app_id: userId,
18415
+ project_id: "default",
18416
+ top_k: topK,
18417
+ method: "hybrid"
18418
+ }),
18419
+ signal: AbortSignal.timeout(15e3)
18420
+ });
18421
+ if (!resp.ok) {
18422
+ console.warn(`[memdir] everos hybrid: search failed ${resp.status}`);
18423
+ return [];
18424
+ }
18425
+ const body = await resp.json();
18426
+ const episodes = body.data?.episodes ?? [];
18427
+ return episodes;
18428
+ }
18429
+ async function deepinfraRerank(query, episodes, rerankUrl, rerankApiKey, rerankModel, provider) {
18430
+ if (episodes.length === 0) return [];
18431
+ const documents = episodes.map(
18432
+ (ep) => ep.episode?.slice(0, 500) || ep.summary?.slice(0, 500) || ep.subject
18433
+ );
18434
+ const allScores = [];
18435
+ const isDashscope = provider === "dashscope";
18436
+ for (let i = 0; i < documents.length; i += RERANK_BATCH_SIZE) {
18437
+ const batch = documents.slice(i, i + RERANK_BATCH_SIZE);
18438
+ const body = isDashscope ? JSON.stringify({
18439
+ model: rerankModel || "qwen3-rerank",
18440
+ input: { query, documents: batch },
18441
+ parameters: { return_documents: false, top_n: batch.length }
18442
+ }) : JSON.stringify({ queries: [query], documents: batch });
18443
+ const makeRequest = () => fetch(rerankUrl, {
18444
+ method: "POST",
18445
+ headers: {
18446
+ "Authorization": `Bearer ${rerankApiKey}`,
18447
+ "Content-Type": "application/json"
18448
+ },
18449
+ body,
18450
+ signal: AbortSignal.timeout(3e4)
18451
+ });
18452
+ let resp = await makeRequest();
18453
+ if (resp.status === 429) {
18454
+ await new Promise((r) => setTimeout(r, 2e3));
18455
+ resp = await makeRequest();
18456
+ }
18457
+ if (!resp.ok) {
18458
+ console.warn(`[memdir] everos rerank: failed ${resp.status}`);
18459
+ return episodes;
18460
+ }
18461
+ if (isDashscope) {
18462
+ const data = await resp.json();
18463
+ const results = data.output?.results ?? [];
18464
+ const scoreMap = new Array(batch.length).fill(0);
18465
+ for (const r of results) {
18466
+ scoreMap[r.index] = r.relevance_score;
18467
+ }
18468
+ allScores.push(...scoreMap);
18469
+ } else {
18470
+ const data = await resp.json();
18471
+ let batchScores = data.scores ?? [];
18472
+ if (Array.isArray(batchScores) && batchScores.length > 0 && Array.isArray(batchScores[0])) {
18473
+ batchScores = batchScores[0];
18474
+ }
18475
+ allScores.push(...batchScores);
18476
+ }
18477
+ }
18478
+ const ranked = episodes.map((ep, idx) => ({ ep, score: allScores[idx] ?? 0 })).sort((a, b2) => b2.score - a.score);
18479
+ for (const { ep, score } of ranked) {
18480
+ ep.score = score;
18481
+ }
18482
+ return ranked.map((r) => r.ep);
18483
+ }
18484
+ var DEFAULT_MIN_SCORE2 = 0.5;
18485
+ async function findRelevantMemoriesEveros(query, _memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
18486
+ const everosUrl = options?.everosUrl ?? "http://127.0.0.1:8100";
18487
+ const userId = options?.userId ?? "xiaomei";
18488
+ const topK = options?.topK ?? 3;
18489
+ const minScore = options?.minScore ?? DEFAULT_MIN_SCORE2;
18490
+ console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} userId=${userId} topK=${topK}`);
18491
+ const t0 = Date.now();
18492
+ try {
18493
+ const tH1 = Date.now();
18494
+ let episodes = await hybridSearch(query, everosUrl, userId, ROUND1_TOP_N);
18495
+ const tH2 = Date.now();
18496
+ console.log(`[memdir] everos recall: hybrid ${episodes.length} candidates in ${tH2 - tH1}ms`);
18497
+ if (episodes.length === 0) return [];
18498
+ const rerankUrl = options?.rerankUrl;
18499
+ const rerankApiKey = options?.rerankApiKey;
18500
+ if (rerankUrl && rerankApiKey) {
18501
+ const tR1 = Date.now();
18502
+ episodes = await deepinfraRerank(
18503
+ query,
18504
+ episodes,
18505
+ rerankUrl,
18506
+ rerankApiKey,
18507
+ options?.rerankModel,
18508
+ options?.rerankProvider
18509
+ );
18510
+ const tR2 = Date.now();
18511
+ console.log(`[memdir] everos recall: rerank done in ${tR2 - tR1}ms (${options?.rerankProvider || "deepinfra"})`);
18512
+ } else {
18513
+ console.log(`[memdir] everos recall: no rerank key, using hybrid scores as-is`);
18514
+ }
18515
+ const ms = Date.now() - t0;
18516
+ console.log(`[memdir] everos recall: ${episodes.length} episodes in ${ms}ms total (hybrid+rerank)`);
18517
+ const surfacedSubjects = /* @__PURE__ */ new Set();
18518
+ for (const p2 of alreadySurfaced) {
18519
+ if (p2.startsWith("everos://")) {
18520
+ surfacedSubjects.add(p2.slice(8));
18521
+ } else {
18522
+ }
18523
+ }
18524
+ const result = [];
18525
+ const seenSubjects = /* @__PURE__ */ new Set();
18526
+ for (const ep of episodes) {
18527
+ if (ep.score < minScore) continue;
18528
+ const subject = (ep.subject || ep.id).slice(0, 80).replace(/[\n\r]/g, " ");
18529
+ const virtualPath = `everos://${subject}`;
18530
+ if (alreadySurfaced.has(virtualPath)) continue;
18531
+ if (surfacedSubjects.has(subject)) continue;
18532
+ if (seenSubjects.has(subject)) continue;
18533
+ seenSubjects.add(subject);
18534
+ result.push({
18535
+ path: virtualPath,
18536
+ mtimeMs: ep.timestamp ? new Date(ep.timestamp).getTime() : Date.now(),
18537
+ content: `### ${ep.subject}
18538
+
18539
+ ${ep.episode || ep.summary}`,
18540
+ header: formatEverosHeader(ep)
18541
+ });
18542
+ if (result.length >= topK) break;
18543
+ }
18544
+ console.log(`[memdir] everos recall: returning ${result.length} memories (after dedup)`);
18545
+ return result;
18546
+ } catch (e) {
18547
+ const ms = Date.now() - t0;
18548
+ console.warn(`[memdir] everos recall: error after ${ms}ms: ${e?.message ?? e}`);
18549
+ return [];
18550
+ }
18551
+ }
18552
+
18357
18553
  // src/handle-query.ts
18358
18554
  init_paths();
18359
18555
  import { readFileSync as readFileSync15, existsSync as existsSync12 } from "node:fs";
@@ -18426,18 +18622,18 @@ function truncate(s2, maxLen) {
18426
18622
  }
18427
18623
  var externalChanRulesCache = null;
18428
18624
  function loadExternalChanRules(workspace) {
18429
- const path43 = join20(workspace, "prompts", "external-chan-rules.md");
18430
- if (externalChanRulesCache && externalChanRulesCache.path === path43) return externalChanRulesCache;
18625
+ const path44 = join20(workspace, "prompts", "external-chan-rules.md");
18626
+ if (externalChanRulesCache && externalChanRulesCache.path === path44) return externalChanRulesCache;
18431
18627
  let content = "";
18432
- if (existsSync12(path43)) {
18628
+ if (existsSync12(path44)) {
18433
18629
  try {
18434
- content = readFileSync15(path43, "utf-8").trim();
18630
+ content = readFileSync15(path44, "utf-8").trim();
18435
18631
  } catch (e) {
18436
18632
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
18437
18633
  }
18438
18634
  }
18439
- externalChanRulesCache = { path: path43, content };
18440
- console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path43}`);
18635
+ externalChanRulesCache = { path: path44, content };
18636
+ console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path44}`);
18441
18637
  return externalChanRulesCache;
18442
18638
  }
18443
18639
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -18460,10 +18656,10 @@ function getExternalChanWhitelist(workspace, configExternalChannels) {
18460
18656
  if (!externalChanWhitelist) loadContactMap(workspace);
18461
18657
  return externalChanWhitelist;
18462
18658
  }
18463
- async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18464
- return handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall);
18659
+ async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
18660
+ return handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source);
18465
18661
  }
18466
- async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18662
+ async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
18467
18663
  const { engine, sessions, channelManager, workspace, providerId, providerApi, model } = deps;
18468
18664
  const features = deps.features || {};
18469
18665
  const preQueryAbort = new AbortController();
@@ -18640,6 +18836,8 @@ ${text}` : text });
18640
18836
  const toolContext = {
18641
18837
  sessionId,
18642
18838
  channel: channelName === "cli" ? "console" : channelName,
18839
+ source: source || "",
18840
+ // 消息来源(user/inbox/heartbeat/cron/system/inner-voice),Stop hook 用来区分注入 turn
18643
18841
  workspace,
18644
18842
  stateDir: deps.stateDir || workspace,
18645
18843
  channelManager,
@@ -18814,7 +19012,23 @@ ${text}` : text });
18814
19012
  const recallP = deps.recallProvider;
18815
19013
  const recallMode = deps.topics?.recall?.mode || "llm";
18816
19014
  let relevantMemories;
18817
- if (recallMode === "vector") {
19015
+ if (recallMode === "everos") {
19016
+ const everosCfg = deps?.everosCfg;
19017
+ relevantMemories = await findRelevantMemoriesEveros(
19018
+ textForMemory,
19019
+ memoryDir,
19020
+ surfaced.paths,
19021
+ everosCfg ? {
19022
+ everosUrl: everosCfg.everosUrl || "http://127.0.0.1:8100",
19023
+ userId: everosCfg.userId || "xiaomei",
19024
+ rerankUrl: everosCfg.rerank?.baseUrl,
19025
+ rerankApiKey: everosCfg.rerank?.apiKey,
19026
+ rerankModel: everosCfg.rerank?.model,
19027
+ rerankProvider: everosCfg.rerank?.provider,
19028
+ minScore: deps.topics?.recall?.minScore
19029
+ } : void 0
19030
+ );
19031
+ } else if (recallMode === "vector") {
18818
19032
  relevantMemories = await findRelevantMemoriesVector(
18819
19033
  textForMemory,
18820
19034
  memoryDir,
@@ -18837,8 +19051,8 @@ ${text}` : text });
18837
19051
  const attachmentMemories = [];
18838
19052
  for (const mem of relevantMemories) {
18839
19053
  try {
18840
- const content = readFileSync15(mem.path, "utf-8");
18841
- const header = memoryHeader(mem.path, mem.mtimeMs);
19054
+ const content = mem.content ?? readFileSync15(mem.path, "utf-8");
19055
+ const header = mem.header ?? memoryHeader(mem.path, mem.mtimeMs);
18842
19056
  attachmentMemories.push({ path: mem.path, content, mtimeMs: mem.mtimeMs, header });
18843
19057
  } catch {
18844
19058
  }
@@ -19386,7 +19600,9 @@ function registerCognifoldBridge(config) {
19386
19600
  messageId: ctx.inbound.messageId
19387
19601
  }
19388
19602
  };
19389
- void enqueueEvent(cognifoldConfig.sessionId, event);
19603
+ const sm = globalThis.__cognifoldSessions;
19604
+ const dynamicSessionId = sm?.getSessionId?.("main") || cognifoldConfig.sessionId;
19605
+ void enqueueEvent(dynamicSessionId, event);
19390
19606
  return null;
19391
19607
  }, 80);
19392
19608
  }
@@ -19710,7 +19926,8 @@ var MessageDispatcher = class {
19710
19926
  msg2.deps,
19711
19927
  msg2.channelTarget,
19712
19928
  msg2.inboundMeta,
19713
- msg2.skipRecall
19929
+ msg2.skipRecall,
19930
+ msg2.source
19714
19931
  );
19715
19932
  } catch (err) {
19716
19933
  console.error(`[dispatcher] Query error (session=${msg2.sessionId}): ${err.message}`);
@@ -19870,13 +20087,19 @@ function startCliLoop(deps, cliConfig, channelManager, dispatcher) {
19870
20087
 
19871
20088
  // src/session/session-history.ts
19872
20089
  import fs14 from "node:fs";
20090
+ import path14 from "node:path";
19873
20091
  var BEIJING_OFFSET_MS = 8 * 36e5;
19874
20092
  var INJECTED_CONTENT_PATTERNS = [
19875
20093
  /【定时心跳】/,
19876
20094
  /\[内心对话测试\]/,
19877
20095
  /\[inner-voice\]/,
19878
20096
  /\[微信巡检\]/,
19879
- /\[plugin\]/
20097
+ /\[plugin\]/,
20098
+ /<nudge-notification>/,
20099
+ /<task-notification>/,
20100
+ /<calendar-notification>/,
20101
+ /## Actions \(\d+\s*个\)/
20102
+ // CogniFold proactive 注入(block[0] 固定格式,engine-startup 拼的)
19880
20103
  ];
19881
20104
  function parseJsonlEntries(lines) {
19882
20105
  const entries = [];
@@ -19911,6 +20134,20 @@ function resolveScopeMainJsonl(sessions) {
19911
20134
  if (!sessionId) return null;
19912
20135
  return sessions.getSessionFilePath(sessionId);
19913
20136
  }
20137
+ function scopeMainJsonlPaths(sessions) {
20138
+ const current = resolveScopeMainJsonl(sessions);
20139
+ let latestArchive = null;
20140
+ if (current) {
20141
+ try {
20142
+ const dir = path14.dirname(current);
20143
+ const base = path14.basename(current);
20144
+ const archives = fs14.readdirSync(dir).filter((f2) => f2.startsWith(base + ".archived.")).sort();
20145
+ if (archives.length > 0) latestArchive = path14.join(dir, archives[archives.length - 1]);
20146
+ } catch {
20147
+ }
20148
+ }
20149
+ return { current, latestArchive };
20150
+ }
19914
20151
  function cleanText(rawText) {
19915
20152
  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]");
19916
20153
  return clean.trim();
@@ -20001,6 +20238,7 @@ function recentMessages(sessions, hours = 12, limit = 60) {
20001
20238
  time: `${p2(bj.getHours())}:${p2(bj.getMinutes())}`,
20002
20239
  role,
20003
20240
  text: clean.slice(0, 80),
20241
+ timestamp: dtMs,
20004
20242
  _utc: dtMs
20005
20243
  });
20006
20244
  }
@@ -20132,6 +20370,8 @@ ${basePrompt}`;
20132
20370
  channelName: "heartbeat",
20133
20371
  source: "heartbeat",
20134
20372
  priority: "later",
20373
+ skipRecall: true,
20374
+ // 心跳不需要记忆召回,避免重复注入心跳相关记忆
20135
20375
  callbacks: {
20136
20376
  onResult: () => resolveDone()
20137
20377
  },
@@ -20149,7 +20389,7 @@ ${basePrompt}`;
20149
20389
 
20150
20390
  // src/nudge/plugin.ts
20151
20391
  import fs17 from "node:fs";
20152
- import path16 from "node:path";
20392
+ import path17 from "node:path";
20153
20393
 
20154
20394
  // src/nudge/judge.ts
20155
20395
  function shouldNudge(task, taskState, cfg) {
@@ -20318,10 +20558,10 @@ function formatDuration2(ms) {
20318
20558
 
20319
20559
  // src/nudge/session-state-reader.ts
20320
20560
  import fs15 from "node:fs";
20321
- import path14 from "node:path";
20561
+ import path15 from "node:path";
20322
20562
  function parseSessionStateFull(workspace, sessionStateFile) {
20323
20563
  const stateFile = sessionStateFile || "SESSION-STATE.md";
20324
- const statePath = path14.isAbsolute(stateFile) ? stateFile : path14.join(workspace, stateFile);
20564
+ const statePath = path15.isAbsolute(stateFile) ? stateFile : path15.join(workspace, stateFile);
20325
20565
  let content;
20326
20566
  try {
20327
20567
  content = fs15.readFileSync(statePath, "utf-8");
@@ -20376,13 +20616,13 @@ function taskIdFromTitle(title) {
20376
20616
 
20377
20617
  // src/calendar/db.ts
20378
20618
  import { DatabaseSync } from "node:sqlite";
20379
- import * as path15 from "node:path";
20619
+ import * as path16 from "node:path";
20380
20620
  import * as fs16 from "node:fs";
20381
20621
  var TZ_OFFSET_MS = 8 * 60 * 60 * 1e3;
20382
20622
  function openDb(workspace) {
20383
- const dir = path15.join(workspace, ".calendar");
20623
+ const dir = path16.join(workspace, ".calendar");
20384
20624
  fs16.mkdirSync(dir, { recursive: true });
20385
- const dbPath = path15.join(dir, "calendar.db");
20625
+ const dbPath = path16.join(dir, "calendar.db");
20386
20626
  const db = new DatabaseSync(dbPath);
20387
20627
  db.exec("PRAGMA journal_mode=WAL");
20388
20628
  db.exec(`CREATE TABLE IF NOT EXISTS events (
@@ -20471,7 +20711,7 @@ var NudgePlugin = class {
20471
20711
  provider;
20472
20712
  model;
20473
20713
  loadPrompt(workspace, promptFile) {
20474
- const promptPath = promptFile ? path16.isAbsolute(promptFile) ? promptFile : path16.join(workspace, promptFile) : path16.join(workspace, "prompts", "nudge-prompt.md");
20714
+ const promptPath = promptFile ? path17.isAbsolute(promptFile) ? promptFile : path17.join(workspace, promptFile) : path17.join(workspace, "prompts", "nudge-prompt.md");
20475
20715
  try {
20476
20716
  const content = fs17.readFileSync(promptPath, "utf-8").trim();
20477
20717
  if (content) {
@@ -20508,11 +20748,20 @@ var NudgePlugin = class {
20508
20748
  const lastMsg = input?.last_assistant_message || "";
20509
20749
  const sessionId = input?.session_id || "";
20510
20750
  console.log(`[stop-hook] lastMsg len=${lastMsg.length}, text="${lastMsg.slice(0, 80)}"`);
20751
+ const repliedIds = this.extractWakeReplyIds(lastMsg);
20752
+ if (repliedIds.length > 0) {
20753
+ this.removeNotificationsById(repliedIds);
20754
+ }
20511
20755
  const msgChannel = input?.channel || "";
20512
20756
  if (sessionId.includes("voice-chat") || msgChannel === "voice-chat") {
20513
20757
  console.log(`[stop-hook] skipping voice-chat (channel=${msgChannel})`);
20514
20758
  return { outcome: { outcome: "success" } };
20515
20759
  }
20760
+ const msgSource = input?.source || "";
20761
+ if (msgSource && msgSource !== "user" && msgSource !== "inbox") {
20762
+ console.log(`[stop-hook] skipping non-conversation turn (source=${msgSource})`);
20763
+ return { outcome: { outcome: "success" } };
20764
+ }
20516
20765
  if (!lastMsg) {
20517
20766
  return { outcome: { outcome: "success" } };
20518
20767
  }
@@ -20585,8 +20834,8 @@ var NudgePlugin = class {
20585
20834
  if (pushedDecision && waitDesc) {
20586
20835
  console.log(`[stop-hook] DETECTED pushedDecision! Injecting corrective message to ${sessionId}`);
20587
20836
  try {
20588
- const correctiveMsg = [
20589
- "\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",
20837
+ const correctiveMsg = buildNudgeNotification("prompt", [
20838
+ "[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",
20590
20839
  "",
20591
20840
  `\u8BCA\u65AD\uFF1A${waitDesc}`,
20592
20841
  "",
@@ -20599,9 +20848,10 @@ var NudgePlugin = class {
20599
20848
  '3. \u6267\u884C\u5B8C\u6C47\u62A5\u7ED3\u679C\uFF08"\u5DF2\u5904\u7406" / "\u5DF2 commit" / "\u5DF2 archive"\uFF09',
20600
20849
  "",
20601
20850
  "\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"
20602
- ].join("\n");
20603
- if (this.dispatcher) {
20604
- this.dispatcher.submitMessage(correctiveMsg, sessionId);
20851
+ ].join("\n"));
20852
+ const route = this.getRoute(sessions);
20853
+ if (route) {
20854
+ enqueueNotification(correctiveMsg, route);
20605
20855
  }
20606
20856
  } catch (e) {
20607
20857
  console.warn(`[stop-hook] Failed to inject corrective message: ${e.message}`);
@@ -20610,14 +20860,21 @@ var NudgePlugin = class {
20610
20860
  if (!isWaiting) {
20611
20861
  return { outcome: { outcome: "success" } };
20612
20862
  }
20613
- const nudgeDir = path16.join(this.workspace, ".nudge");
20614
- const notifPath = path16.join(nudgeDir, "stop-hook-notifications.json");
20863
+ const nudgeDir = path17.join(this.workspace, ".nudge");
20864
+ const notifPath = path17.join(nudgeDir, "stop-hook-notifications.json");
20615
20865
  try {
20616
20866
  if (!fs17.existsSync(nudgeDir)) fs17.mkdirSync(nudgeDir, { recursive: true });
20617
20867
  let notifs = [];
20618
20868
  if (fs17.existsSync(notifPath)) {
20619
20869
  notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20620
20870
  const now = Date.now();
20871
+ const dup = notifs.find((n) => !n.notified && n.description === (waitDesc || lastMsg.slice(0, 200)));
20872
+ if (dup) {
20873
+ dup.wakeAt = new Date(now + 5 * 6e4).toISOString();
20874
+ fs17.writeFileSync(notifPath, JSON.stringify(notifs, null, 2));
20875
+ console.log(`[stop-hook] Duplicate wait (same desc, not fired yet), refreshed wakeAt: ${dup.id}`);
20876
+ return { outcome: { outcome: "success" } };
20877
+ }
20621
20878
  const recentReg = notifs.find((n) => now - new Date(n.createdAt).getTime() < 3 * 6e4);
20622
20879
  if (recentReg) {
20623
20880
  console.log(`[stop-hook] Skip (recent registration within 3min)`);
@@ -20666,9 +20923,14 @@ var NudgePlugin = class {
20666
20923
  return null;
20667
20924
  }
20668
20925
  }
20669
- /** 检查 stop-hook-notifications:你停止前注册的等待唤起 */
20670
- checkStopHookNotifications() {
20671
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20926
+ /**
20927
+ * 收集到期的 stop-hook notifications,批量构建一条 wake 消息。
20928
+ * 不标 notified——投递成功后由 tick markNotified 标(route 拿不到时保留原样下个 tick 重试,
20929
+ * 避免"消息没投出去但已标 notified"的死账)。
20930
+ * 已 notified 的不会再触发,等 agent 回复 "<id> 过期了" 由 cleanup 删。
20931
+ */
20932
+ collectDueStopHookNotifications() {
20933
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20672
20934
  try {
20673
20935
  if (!fs17.existsSync(notifPath)) return null;
20674
20936
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
@@ -20676,54 +20938,161 @@ var NudgePlugin = class {
20676
20938
  const now = Date.now();
20677
20939
  const due = notifs.filter((n) => new Date(n.wakeAt).getTime() <= now && !n.notified);
20678
20940
  if (due.length === 0) return null;
20679
- const latest = due[due.length - 1];
20680
- console.log(`[nudge] Stop-hook notification ${latest.id} triggered: ${latest.description.slice(0, 80)}...`);
20681
- const updated = notifs.map((n) => n.id === latest.id ? { ...n, notified: true } : n);
20682
- fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
20683
- 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
20941
+ console.log(`[nudge] ${due.length} stop-hook notification(s) due: ${due.map((n) => n.id).join(", ")}`);
20942
+ const items = due.map((n) => `[\u901A\u77E5ID: ${n.id}]
20943
+ \u4E0A\u6B21\u8BF4\uFF1A${n.description}`).join("\n\n");
20944
+ 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
20945
+
20946
+ ${items}
20684
20947
 
20685
- [\u901A\u77E5ID: ${latest.id}]
20686
- \u4E0A\u6B21\u8BF4\uFF1A${latest.description}
20948
+ \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
20687
20949
 
20688
- \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`);
20950
+ ${items}
20951
+
20952
+ \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`;
20953
+ return { message: buildNudgeNotification("wake", desc), ids: due.map((n) => n.id) };
20689
20954
  } catch (e) {
20690
- console.warn(`[nudge] checkStopHookNotifications error: ${e.message}`);
20955
+ console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
20691
20956
  return null;
20692
20957
  }
20693
20958
  }
20694
- /** 读最近消息,如果发现"<notifId> 过期了" 精确清理对应的 wake-up notification */
20959
+ /** 从回复文本里提取 "<id> 过期了" wake id(一条回复可能处置多个) */
20960
+ extractWakeReplyIds(text) {
20961
+ if (!text) return [];
20962
+ const ids = [];
20963
+ const re = /(wake-\d+-[a-z0-9]+)\s*过期了/g;
20964
+ let m2;
20965
+ while ((m2 = re.exec(text)) !== null) {
20966
+ if (!ids.includes(m2[1])) ids.push(m2[1]);
20967
+ }
20968
+ return ids;
20969
+ }
20970
+ /** 按 id 删除条目(stop-hook 实时清理用;正常删除路径,agent 回复即删) */
20971
+ removeNotificationsById(ids) {
20972
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20973
+ try {
20974
+ if (!fs17.existsSync(notifPath)) return;
20975
+ const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20976
+ const idSet = new Set(ids);
20977
+ const remaining = notifs.filter((n) => !idSet.has(n.id));
20978
+ const removed = notifs.length - remaining.length;
20979
+ if (removed === 0) return;
20980
+ if (remaining.length > 0) {
20981
+ fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
20982
+ } else {
20983
+ fs17.unlinkSync(notifPath);
20984
+ }
20985
+ console.log(`[stop-hook] Cleaned ${removed} notification(s) from reply: ${ids.join(", ")}`);
20986
+ } catch (e) {
20987
+ console.warn(`[stop-hook] removeNotificationsById error: ${e.message}`);
20988
+ }
20989
+ }
20990
+ /** 投递成功后标记 notified(防重复触发);不删除——删除只走 agent 回复 "<id> 过期了" */
20991
+ markNotified(ids) {
20992
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20993
+ try {
20994
+ if (!fs17.existsSync(notifPath)) return;
20995
+ const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20996
+ const idSet = new Set(ids);
20997
+ const updated = notifs.map((n) => idSet.has(n.id) ? { ...n, notified: true } : n);
20998
+ fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
20999
+ } catch (e) {
21000
+ console.warn(`[nudge] markNotified error: ${e.message}`);
21001
+ }
21002
+ }
21003
+ /**
21004
+ * tick 兜底清理。正常删除走 stop-hook 实时路径(agent 回复 "<id> 过期了" 当 turn 就删,
21005
+ * 见 registerStopHook 第 0 步),这里只接两种漏网:
21006
+ * ① 回复已落盘但 stop-hook 没来得及执行(进程中途崩等边缘情况)→ 扫 jsonl 补删;
21007
+ * ② TTL 清道夫:wakeAt 超过 cleanupTtlHours(默认 24h)仍无回复 → 回复永远来不了,删。
21008
+ *
21009
+ * 扫描不走 recentMessages()——它截断 80 字符、会过滤"对注入消息的回复"(wake 回复恰好
21010
+ * 被过滤掉)、限 20 条窗口。直接读 jsonl 原始条目:倒序扫、扫过最老 pending 的 wakeAt
21011
+ * 即停、全命中提前退、archive 只在 current 被 2MB 轮转切断时才读。
21012
+ */
20695
21013
  cleanupStaleNotificationsFromMessages(sessions) {
20696
21014
  try {
20697
- const recent = recentMessages(sessions, 0.5, 6);
20698
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
20699
- const recentTexts = recent.filter((r) => new Date(r.timestamp || r.createdAt || Date.now()).getTime() > fiveMinAgo).map((r) => r.text);
20700
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21015
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20701
21016
  if (!fs17.existsSync(notifPath)) return;
20702
21017
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20703
21018
  if (notifs.length === 0) return;
20704
- const expiredIds = /* @__PURE__ */ new Set();
20705
- for (const text of recentTexts) {
20706
- for (const n of notifs) {
20707
- if (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`)) {
20708
- expiredIds.add(n.id);
20709
- }
20710
- }
21019
+ const expiredIds = this.findExpiredReplyIds(sessions, notifs);
21020
+ const ttlMs = (this.cfg.cleanupTtlHours || 24) * 36e5;
21021
+ const now = Date.now();
21022
+ const ttlIds = new Set(
21023
+ notifs.filter((n) => now - new Date(n.wakeAt).getTime() > ttlMs && !expiredIds.has(n.id)).map((n) => n.id)
21024
+ );
21025
+ const removeIds = /* @__PURE__ */ new Set([...expiredIds, ...ttlIds]);
21026
+ if (removeIds.size === 0) return;
21027
+ const remaining = notifs.filter((n) => !removeIds.has(n.id));
21028
+ if (remaining.length > 0) {
21029
+ fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
21030
+ } else {
21031
+ fs17.unlinkSync(notifPath);
20711
21032
  }
20712
- if (expiredIds.size === 0) return;
20713
- const remaining = notifs.filter((n) => !expiredIds.has(n.id));
20714
- const cleaned = notifs.length - remaining.length;
20715
- if (cleaned > 0) {
20716
- if (remaining.length > 0) {
20717
- fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
20718
- } else {
20719
- fs17.unlinkSync(notifPath);
20720
- }
20721
- console.log(`[nudge] Cleaned ${cleaned} stale notification(s) by explicit id: ${[...expiredIds].join(", ")}`);
21033
+ if (expiredIds.size > 0) {
21034
+ console.log(`[nudge] Cleaned ${expiredIds.size} notification(s) by reply: ${[...expiredIds].join(", ")}`);
21035
+ }
21036
+ if (ttlIds.size > 0) {
21037
+ console.log(`[nudge] Cleaned ${ttlIds.size} zombie notification(s) by TTL (>${this.cfg.cleanupTtlHours || 24}h no reply): ${[...ttlIds].join(", ")}`);
20722
21038
  }
20723
21039
  } catch (e) {
20724
21040
  console.warn(`[nudge] cleanupStaleNotificationsFromMessages error: ${e.message}`);
20725
21041
  }
20726
21042
  }
21043
+ /**
21044
+ * 扫 "<id> 过期了" 回复,返回匹配到的 id 集合。
21045
+ * 不做全文扫描:倒序扫(回复紧跟在 fire 之后,通常就在尾部几条);
21046
+ * 扫过最老 pending 条目的 wakeAt 就停(回复不可能早于触发时间);
21047
+ * 全部命中提前退出;archive 只在 current 没覆盖时间范围(被 2MB 轮转切断)时才读。
21048
+ * 典型开销:解析几十条而不是上千条。
21049
+ */
21050
+ findExpiredReplyIds(sessions, notifs) {
21051
+ const found = /* @__PURE__ */ new Set();
21052
+ if (notifs.length === 0) return found;
21053
+ const oldestMs = Math.min(...notifs.map((n) => new Date(n.wakeAt).getTime()));
21054
+ const { current, latestArchive } = scopeMainJsonlPaths(sessions);
21055
+ for (const file of [current, latestArchive]) {
21056
+ if (!file || !fs17.existsSync(file)) continue;
21057
+ let lines;
21058
+ try {
21059
+ lines = fs17.readFileSync(file, "utf-8").split("\n");
21060
+ } catch (e) {
21061
+ console.warn(`[nudge] findExpiredReplyIds read error on ${file}: ${e.message}`);
21062
+ continue;
21063
+ }
21064
+ let coveredOldest = false;
21065
+ for (let i = lines.length - 1; i >= 0; i--) {
21066
+ const trimmed = lines[i].trim();
21067
+ if (!trimmed) continue;
21068
+ let entry;
21069
+ try {
21070
+ entry = JSON.parse(trimmed);
21071
+ } catch {
21072
+ continue;
21073
+ }
21074
+ const tsMs = entry?.timestamp ? new Date(entry.timestamp).getTime() : 0;
21075
+ if (tsMs > 0 && tsMs < oldestMs) {
21076
+ coveredOldest = true;
21077
+ break;
21078
+ }
21079
+ if (entry?.type !== "message") continue;
21080
+ const msg2 = entry.message;
21081
+ if (!msg2 || msg2.role !== "assistant") continue;
21082
+ const content = msg2.content;
21083
+ const text = typeof content === "string" ? content : Array.isArray(content) && content[0] && typeof content[0].text === "string" ? content[0].text : "";
21084
+ if (!text) continue;
21085
+ for (const n of notifs) {
21086
+ if (!found.has(n.id) && (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`))) {
21087
+ found.add(n.id);
21088
+ }
21089
+ }
21090
+ if (found.size === notifs.length) return found;
21091
+ }
21092
+ if (coveredOldest) break;
21093
+ }
21094
+ return found;
21095
+ }
20727
21096
  async tick(sessions, deps) {
20728
21097
  if (this.running) {
20729
21098
  console.log("[nudge] Previous tick still running, skipping");
@@ -20738,7 +21107,7 @@ var NudgePlugin = class {
20738
21107
  const recent = recentMessages(sessions, 0.5, 6);
20739
21108
  const lastUserMsg2 = recent.filter((r) => r.role === "user").slice(-1)[0];
20740
21109
  if (lastUserMsg2) {
20741
- const elapsed = Date.now() - new Date(lastUserMsg2.timestamp || lastUserMsg2.createdAt || Date.now()).getTime();
21110
+ const elapsed = lastUserMsg2.timestamp ? Date.now() - lastUserMsg2.timestamp : 0;
20742
21111
  if (elapsed < activeThresholdMs) {
20743
21112
  console.log(`[nudge] User active ${Math.round(elapsed / 1e3)}s ago (<${activeThresholdMs / 1e3}s), skipping tick`);
20744
21113
  return;
@@ -20750,10 +21119,15 @@ var NudgePlugin = class {
20750
21119
  this.running = true;
20751
21120
  try {
20752
21121
  this.cleanupStaleNotificationsFromMessages(sessions);
20753
- const stopNotifs = this.checkStopHookNotifications();
20754
- if (stopNotifs) {
21122
+ const dueNotifs = this.collectDueStopHookNotifications();
21123
+ if (dueNotifs) {
20755
21124
  const route2 = this.getRoute(sessions);
20756
- if (route2) enqueueNotification(stopNotifs, route2);
21125
+ if (route2) {
21126
+ enqueueNotification(dueNotifs.message, route2);
21127
+ this.markNotified(dueNotifs.ids);
21128
+ } else {
21129
+ console.warn(`[nudge] No route for ${dueNotifs.ids.length} stop-hook notification(s), keeping for retry next tick`);
21130
+ }
20757
21131
  const state0 = this.loadState();
20758
21132
  state0.lastAnyNudgeAt = (/* @__PURE__ */ new Date()).toISOString();
20759
21133
  this.saveState(state0);
@@ -20939,7 +21313,7 @@ var NudgePlugin = class {
20939
21313
  // === state 持久化 ===
20940
21314
  loadState() {
20941
21315
  const stateFile = this.cfg.stateFile || "nudge-state.json";
20942
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21316
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
20943
21317
  try {
20944
21318
  const content = fs17.readFileSync(statePath, "utf-8");
20945
21319
  return JSON.parse(content);
@@ -20949,7 +21323,7 @@ var NudgePlugin = class {
20949
21323
  }
20950
21324
  saveState(state2) {
20951
21325
  const stateFile = this.cfg.stateFile || "nudge-state.json";
20952
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21326
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
20953
21327
  fs17.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
20954
21328
  }
20955
21329
  newTaskState() {
@@ -21035,30 +21409,47 @@ var NudgePlugin = class {
21035
21409
  const now = /* @__PURE__ */ new Date();
21036
21410
  const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
21037
21411
  const bj = new Date(now.getTime() + bjOffset);
21038
- const month = bj.getMonth() + 1;
21039
- const day = bj.getDate();
21040
21412
  const bjHour = bj.getHours();
21041
21413
  const bjMinute = bj.getMinutes();
21414
+ const todayStart = new Date(bj.getFullYear(), bj.getMonth(), bj.getDate()).getTime();
21042
21415
  const rows = db.prepare(
21043
- "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND (date_str=? OR date_str LIKE ?)"
21044
- ).all(`${month}/${day}`, `%/` + day + `%`);
21416
+ "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND date_str IS NOT NULL"
21417
+ ).all();
21045
21418
  db.close();
21046
- if (rows.length === 0) return null;
21047
- const dueRows = rows.filter((r2) => {
21048
- if (!r2.time_exact) return true;
21049
- const [h, m2] = r2.time_exact.split(":").map(Number);
21050
- if (h < bjHour) return true;
21051
- if (h === bjHour && m2 <= bjMinute) return true;
21052
- return false;
21419
+ const due = [];
21420
+ for (const r2 of rows) {
21421
+ const dayMs = this.parseCalendarDateStr(r2.date_str, bj);
21422
+ if (dayMs === null || dayMs > todayStart) continue;
21423
+ const isToday2 = dayMs === todayStart;
21424
+ if (isToday2 && r2.time_exact) {
21425
+ const [h, m2] = String(r2.time_exact).split(":").map(Number);
21426
+ if (h > bjHour || h === bjHour && m2 > bjMinute) continue;
21427
+ }
21428
+ due.push({ id: r2.id, event: r2.event, date_str: r2.date_str, time_exact: r2.time_exact, dayMs, isToday: isToday2 });
21429
+ }
21430
+ if (due.length === 0) return null;
21431
+ due.sort((a, b2) => {
21432
+ if (a.isToday !== b2.isToday) return a.isToday ? -1 : 1;
21433
+ return b2.dayMs - a.dayMs;
21053
21434
  });
21054
- if (dueRows.length === 0) return null;
21055
- const r = dueRows[0];
21056
- return `#${r.id} ${r.event} (${r.date_str} ${r.time_exact || ""})`.trim();
21435
+ const r = due[0];
21436
+ return `#${r.id} ${r.event} (${r.date_str} ${r.time_exact || ""})${r.isToday ? "" : "\uFF08\u5DF2\u903E\u671F\uFF09"}`.trim();
21057
21437
  } catch (e) {
21058
21438
  console.warn(`[nudge] checkCalendarDue error: ${e.message}`);
21059
21439
  return null;
21060
21440
  }
21061
21441
  }
21442
+ /** 解析 date_str 为当日 0 点 epoch ms(北京时间);"M/D" 按当前年,"YYYY-M-D" 按字面年 */
21443
+ parseCalendarDateStr(ds, bj) {
21444
+ if (!ds) return null;
21445
+ if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(ds)) {
21446
+ const t = (/* @__PURE__ */ new Date(ds + "T00:00:00+08:00")).getTime();
21447
+ return Number.isNaN(t) ? null : t;
21448
+ }
21449
+ const m2 = String(ds).match(/^(\d{1,2})\/(\d{1,2})$/);
21450
+ if (!m2) return null;
21451
+ return new Date(bj.getFullYear(), Number(m2[1]) - 1, Number(m2[2])).getTime();
21452
+ }
21062
21453
  /** 检查 carry-over:如果 in_progress task 24h+ 没推进,自动 calendar add-task 排明天 */
21063
21454
  checkCarryOver(task, _sessions) {
21064
21455
  try {
@@ -21112,7 +21503,7 @@ var NudgePlugin = class {
21112
21503
 
21113
21504
  // src/inner-voice/plugin.ts
21114
21505
  import fs21 from "node:fs";
21115
- import path20 from "node:path";
21506
+ import path21 from "node:path";
21116
21507
 
21117
21508
  // src/inner-voice/activity.ts
21118
21509
  function checkActivity(sessions, activeThresholdMs) {
@@ -21152,7 +21543,7 @@ function calcHintProb(min) {
21152
21543
 
21153
21544
  // src/inner-voice/emotional-state.ts
21154
21545
  import fs18 from "node:fs";
21155
- import path17 from "node:path";
21546
+ import path18 from "node:path";
21156
21547
  var NEUTRAL = 0.5;
21157
21548
  var DECAY_RATE = 0.17;
21158
21549
  var MAX_EVENTS = 20;
@@ -21203,7 +21594,7 @@ function initialState() {
21203
21594
  return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowIsoBj(), events: [] };
21204
21595
  }
21205
21596
  async function updateEmotionalState(workspace, sessions) {
21206
- const stateFile = path17.join(workspace, "inner-voice", "emotional-state.json");
21597
+ const stateFile = path18.join(workspace, "inner-voice", "emotional-state.json");
21207
21598
  const messages = readRecentMessages(sessions, RECENT_N);
21208
21599
  if (messages.length === 0) {
21209
21600
  console.log("[emotional-state] no messages");
@@ -21236,7 +21627,7 @@ async function updateEmotionalState(workspace, sessions) {
21236
21627
  function readRecentMessages(sessions, n) {
21237
21628
  const mainId = sessions.getSessionId("scope:main");
21238
21629
  if (!mainId) return [];
21239
- const file = path17.join(sessions.sessionsDir, `${mainId}.jsonl`);
21630
+ const file = path18.join(sessions.sessionsDir, `${mainId}.jsonl`);
21240
21631
  if (!fs18.existsSync(file)) return [];
21241
21632
  const lines = readLastNLines(file, n * 4 + 20);
21242
21633
  const entries = [];
@@ -21354,7 +21745,7 @@ function refreshHoursAgo(events) {
21354
21745
  }
21355
21746
  function appendMoodLog(workspace, state2, summary) {
21356
21747
  try {
21357
- const logPath = path17.join(workspace, "mood-history.log");
21748
+ const logPath = path18.join(workspace, "mood-history.log");
21358
21749
  const ts = formatBj(/* @__PURE__ */ new Date(), false);
21359
21750
  fs18.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
21360
21751
  `);
@@ -21371,7 +21762,7 @@ function loadJson(file) {
21371
21762
  }
21372
21763
  function saveJson(file, data) {
21373
21764
  try {
21374
- fs18.mkdirSync(path17.dirname(file), { recursive: true });
21765
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
21375
21766
  fs18.writeFileSync(file, JSON.stringify(data, null, 2));
21376
21767
  } catch (err) {
21377
21768
  console.warn(`[emotional-state] save failed: ${err.message}`);
@@ -21417,7 +21808,7 @@ function formatBj(d, withSec) {
21417
21808
 
21418
21809
  // src/inner-voice/topics-scorer.ts
21419
21810
  import fs19 from "node:fs";
21420
- import path18 from "node:path";
21811
+ import path19 from "node:path";
21421
21812
  var HALF_LIFE_DAYS = 3;
21422
21813
  var PROJECT_HALF_LIFE_DAYS = 1.5;
21423
21814
  var COOLDOWN_HOURS = 6;
@@ -21425,8 +21816,8 @@ var MAX_CHARS = 8e3;
21425
21816
  var SKIP_NAMES = /* @__PURE__ */ new Set(["MEMORY.md", "archive"]);
21426
21817
  var SKIP_DIRS = /* @__PURE__ */ new Set(["archive"]);
21427
21818
  function pickTopic(workspace, typeFilter, opts) {
21428
- const topicsDir = path18.join(workspace, "topics");
21429
- const usageFile = path18.join(workspace, "inner-voice", "topics-usage.json");
21819
+ const topicsDir = path19.join(workspace, "topics");
21820
+ const usageFile = path19.join(workspace, "inner-voice", "topics-usage.json");
21430
21821
  const files = scanTopics(topicsDir, typeFilter);
21431
21822
  if (files.length === 0) {
21432
21823
  console.log(`[topics-scorer] no topics found (type=${typeFilter})`);
@@ -21458,7 +21849,7 @@ function pickTopic(workspace, typeFilter, opts) {
21458
21849
  recency: Math.round(recency * 1e3) / 1e3,
21459
21850
  freq: Math.round(freq * 1e3) / 1e3,
21460
21851
  type: type2,
21461
- name: meta.name || path18.basename(relpath),
21852
+ name: meta.name || path19.basename(relpath),
21462
21853
  description: meta.description || "",
21463
21854
  mtime
21464
21855
  });
@@ -21516,14 +21907,14 @@ function scanTopics(topicsDir, typeFilter) {
21516
21907
  const out = [];
21517
21908
  const walk = (dir) => {
21518
21909
  for (const name of fs19.readdirSync(dir)) {
21519
- const full = path18.join(dir, name);
21910
+ const full = path19.join(dir, name);
21520
21911
  const stat4 = fs19.statSync(full);
21521
21912
  if (stat4.isDirectory()) {
21522
21913
  if (SKIP_DIRS.has(name)) continue;
21523
21914
  walk(full);
21524
21915
  } else {
21525
21916
  if (!name.endsWith(".md") || SKIP_NAMES.has(name)) continue;
21526
- const relpath = path18.relative(topicsDir, full).replace(/\\/g, "/");
21917
+ const relpath = path19.relative(topicsDir, full).replace(/\\/g, "/");
21527
21918
  if (typeFilter && !relpath.startsWith(typeFilter + "/") && !relpath.startsWith(typeFilter + "_")) continue;
21528
21919
  out.push({ relpath, fullpath: full });
21529
21920
  }
@@ -21568,7 +21959,7 @@ function loadJson2(file) {
21568
21959
  }
21569
21960
  function saveJson2(file, data) {
21570
21961
  try {
21571
- fs19.mkdirSync(path18.dirname(file), { recursive: true });
21962
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
21572
21963
  fs19.writeFileSync(file, JSON.stringify(data, null, 2));
21573
21964
  } catch (err) {
21574
21965
  console.warn(`[topics-scorer] usage save failed: ${err.message}`);
@@ -21577,21 +21968,21 @@ function saveJson2(file, data) {
21577
21968
 
21578
21969
  // src/inner-voice/memory-reader.ts
21579
21970
  import fs20 from "node:fs";
21580
- import path19 from "node:path";
21971
+ import path20 from "node:path";
21581
21972
  var US_HALF_LIFE_DAYS = 10;
21582
21973
  var US_MAX_LINES = 60;
21583
21974
  function readRecentMemory(workspace) {
21584
- const dir = path19.join(workspace, "memory");
21975
+ const dir = path20.join(workspace, "memory");
21585
21976
  const now = new Date(Date.now() + 8 * 36e5);
21586
21977
  const today = formatYmd(now);
21587
21978
  const yesterday = formatYmd(new Date(now.getTime() - 864e5));
21588
21979
  return {
21589
- today: readIfExists(path19.join(dir, `${today}.md`)),
21590
- yesterday: readIfExists(path19.join(dir, `${yesterday}.md`))
21980
+ today: readIfExists(path20.join(dir, `${today}.md`)),
21981
+ yesterday: readIfExists(path20.join(dir, `${yesterday}.md`))
21591
21982
  };
21592
21983
  }
21593
21984
  function sampleUs(workspace) {
21594
- const usFile = path19.join(workspace, "memory", "us.md");
21985
+ const usFile = path20.join(workspace, "memory", "us.md");
21595
21986
  let content;
21596
21987
  try {
21597
21988
  content = fs20.readFileSync(usFile, "utf-8");
@@ -21931,7 +22322,7 @@ var InnerVoicePlugin = class {
21931
22322
  }
21932
22323
  /** 读 workspace/prompts/my-inner-voice.md,不存在用 DEFAULT_PROMPT */
21933
22324
  loadPrompt(workspace) {
21934
- const promptPath = path20.join(workspace, "prompts", "my-inner-voice.md");
22325
+ const promptPath = path21.join(workspace, "prompts", "my-inner-voice.md");
21935
22326
  try {
21936
22327
  const content = fs21.readFileSync(promptPath, "utf-8").trim();
21937
22328
  if (content) {
@@ -22005,7 +22396,7 @@ var InnerVoicePlugin = class {
22005
22396
  console.warn(`[inner-voice] emotional-state failed: ${err.message}`);
22006
22397
  }
22007
22398
  try {
22008
- const content = fs21.readFileSync(path20.join(this.workspace, "SESSION-STATE.md"), "utf-8");
22399
+ const content = fs21.readFileSync(path21.join(this.workspace, "SESSION-STATE.md"), "utf-8");
22009
22400
  lines.push("\n--- SESSION-STATE\uFF08\u5C3E\u90E8\uFF09 ---");
22010
22401
  lines.push(content.slice(-2e3));
22011
22402
  } catch {
@@ -22116,7 +22507,7 @@ var InnerVoicePlugin = class {
22116
22507
  if (Math.random() >= activity.hintProb) {
22117
22508
  return { text: thought, hintTriggered: false, hintText: "" };
22118
22509
  }
22119
- const poolPath = path20.join(this.workspace, "inner-voice", "hints_pool.txt");
22510
+ const poolPath = path21.join(this.workspace, "inner-voice", "hints_pool.txt");
22120
22511
  let hint = "\u60F3\u4ED6\u5C31\u53D1\u6D88\u606F\u5427";
22121
22512
  try {
22122
22513
  const pool = fs21.readFileSync(poolPath, "utf-8").split("\n").map((s2) => s2.trim()).filter(Boolean);
@@ -22144,7 +22535,7 @@ var InnerVoicePlugin = class {
22144
22535
  try {
22145
22536
  const writer = sessions.getWriter(mainSessionId);
22146
22537
  const history = sessions.getHistory(mainSessionId);
22147
- const fullPath = path20.resolve(this.workspace, emoTopic.file);
22538
+ const fullPath = path21.resolve(this.workspace, emoTopic.file);
22148
22539
  const memories = [{
22149
22540
  path: fullPath,
22150
22541
  content: emoTopic.content,
@@ -22172,9 +22563,9 @@ var InnerVoicePlugin = class {
22172
22563
  /** 写 xiaoyi.log(格式对齐旧 memory_whisper.py,便于既有日志分析复用)。 */
22173
22564
  writeLog(status, delivered, activity, hintTriggered, hintText) {
22174
22565
  try {
22175
- const logDir = path20.join(this.workspace, "inner-voice");
22566
+ const logDir = path21.join(this.workspace, "inner-voice");
22176
22567
  fs21.mkdirSync(logDir, { recursive: true });
22177
- const logPath = path20.join(logDir, "xiaoyi.log");
22568
+ const logPath = path21.join(logDir, "xiaoyi.log");
22178
22569
  const ts = formatBeijingTs(/* @__PURE__ */ new Date());
22179
22570
  const hintStatus = hintTriggered ? `YES (${(hintText || "").trim()})` : "no";
22180
22571
  fs21.appendFileSync(
@@ -22713,7 +23104,7 @@ var PluginManager = class {
22713
23104
  // src/voice-chat/plugin.ts
22714
23105
  import { spawn as spawn4, exec } from "node:child_process";
22715
23106
  import net from "node:net";
22716
- import path21 from "node:path";
23107
+ import path22 from "node:path";
22717
23108
  import fs22 from "node:fs";
22718
23109
 
22719
23110
  // src/voice-chat/bridge.ts
@@ -23090,13 +23481,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23090
23481
  }
23091
23482
  getPythonDir() {
23092
23483
  const dir = import.meta.dirname;
23093
- const srcDir = path21.resolve(dir, "..", "src", "voice-chat", "python");
23094
- const localDir = path21.join(dir, "python");
23484
+ const srcDir = path22.resolve(dir, "..", "src", "voice-chat", "python");
23485
+ const localDir = path22.join(dir, "python");
23095
23486
  return fs22.existsSync(srcDir) ? srcDir : localDir;
23096
23487
  }
23097
23488
  startPython() {
23098
23489
  const pythonDir = this.getPythonDir();
23099
- const serverPy = path21.join(pythonDir, "server.py");
23490
+ const serverPy = path22.join(pythonDir, "server.py");
23100
23491
  const pythonBin = this.findPython();
23101
23492
  const args = [serverPy];
23102
23493
  if (this.config.pythonPort) args.push("--port", String(this.config.pythonPort));
@@ -23181,7 +23572,7 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23181
23572
  init_BashTool();
23182
23573
  import { spawn as spawn5, exec as exec2 } from "node:child_process";
23183
23574
  import net2 from "node:net";
23184
- import path22 from "node:path";
23575
+ import path23 from "node:path";
23185
23576
  import fs23 from "node:fs";
23186
23577
 
23187
23578
  // src/memory/cognifold/config.ts
@@ -23205,7 +23596,8 @@ function parseCognifoldConfig(raw) {
23205
23596
  persistDir: raw.persistDir ?? DEFAULTS3.persistDir,
23206
23597
  scopes: raw.scopes,
23207
23598
  readyTimeoutMs: raw.readyTimeoutMs ?? DEFAULTS3.readyTimeoutMs,
23208
- maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts
23599
+ maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts,
23600
+ llm: raw.llm
23209
23601
  };
23210
23602
  }
23211
23603
 
@@ -23213,15 +23605,17 @@ function parseCognifoldConfig(raw) {
23213
23605
  var CogniFoldClient = class {
23214
23606
  baseUrl;
23215
23607
  timeoutMs;
23216
- constructor(baseUrl, timeoutMs = 3e4) {
23608
+ modelName;
23609
+ constructor(baseUrl, timeoutMs = 3e4, modelName = "openai:MiniMax-M3") {
23217
23610
  this.baseUrl = baseUrl.replace(/\/$/, "");
23218
23611
  this.timeoutMs = timeoutMs;
23612
+ this.modelName = modelName;
23219
23613
  }
23220
- async req(path43, options = {}) {
23614
+ async req(path44, options = {}) {
23221
23615
  const ctrl = new AbortController();
23222
23616
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23223
23617
  try {
23224
- const resp = await fetch(`${this.baseUrl}${path43}`, {
23618
+ const resp = await fetch(`${this.baseUrl}${path44}`, {
23225
23619
  ...options,
23226
23620
  signal: ctrl.signal,
23227
23621
  headers: {
@@ -23259,7 +23653,7 @@ var CogniFoldClient = class {
23259
23653
  method: "POST",
23260
23654
  body: JSON.stringify({
23261
23655
  user_id: userId,
23262
- config: { model_name: "openai:deepseek-v4-flash" }
23656
+ config: { model_name: this.modelName }
23263
23657
  })
23264
23658
  });
23265
23659
  }
@@ -23311,8 +23705,8 @@ var CogniFoldClient = class {
23311
23705
  });
23312
23706
  }
23313
23707
  /** 兼容老版命名 */
23314
- async recl(path43, options = {}) {
23315
- return this.req(path43, options);
23708
+ async recl(path44, options = {}) {
23709
+ return this.req(path44, options);
23316
23710
  }
23317
23711
  };
23318
23712
 
@@ -23417,7 +23811,8 @@ var CogniFoldPlugin = class {
23417
23811
  baseUrl = baseUrl.replace(/\/$/, "") + "/api/v1";
23418
23812
  }
23419
23813
  this.config.baseUrl = baseUrl;
23420
- this.client = new CogniFoldClient(baseUrl);
23814
+ const modelName = this.config.llm?.model ? this.config.llm.model.startsWith("openai:") ? this.config.llm.model : `openai:${this.config.llm.model}` : "openai:MiniMax-M3";
23815
+ this.client = new CogniFoldClient(baseUrl, 3e4, modelName);
23421
23816
  }
23422
23817
  workspacePath;
23423
23818
  name = "cognifold";
@@ -23592,16 +23987,16 @@ var CogniFoldPlugin = class {
23592
23987
  const dir = import.meta.dirname;
23593
23988
  const candidates = [
23594
23989
  // 从 dist/ 往回找 src
23595
- path22.resolve(dir, "..", "src", "memory", "cognifold", "python"),
23596
- path22.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
23597
- path22.resolve(dir, "..", "..", "..", "src", "memory", "cognifold", "python"),
23990
+ path23.resolve(dir, "..", "src", "memory", "cognifold", "python"),
23991
+ path23.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
23992
+ path23.resolve(dir, "..", "..", "..", "src", "memory", "cognifold", "python"),
23598
23993
  // 从 src/memory/cognifold/ 找本地
23599
- path22.join(dir, "python"),
23994
+ path23.join(dir, "python"),
23600
23995
  // 从 dist/memory/cognifold/ 找本地
23601
- path22.resolve(dir, "python")
23996
+ path23.resolve(dir, "python")
23602
23997
  ];
23603
23998
  for (const candidate of candidates) {
23604
- if (fs23.existsSync(path22.join(candidate, "cognifold"))) {
23999
+ if (fs23.existsSync(path23.join(candidate, "cognifold"))) {
23605
24000
  return candidate;
23606
24001
  }
23607
24002
  }
@@ -23627,12 +24022,18 @@ var CogniFoldPlugin = class {
23627
24022
  const pythonBin = this.findPython();
23628
24023
  console.log(`[cognifold] Starting Python: ${pythonBin} ${args.join(" ")}`);
23629
24024
  console.log(`[cognifold] Python dir: ${pythonDir}`);
23630
- if (!fs23.existsSync(path22.join(pythonDir, "cognifold"))) {
24025
+ if (!fs23.existsSync(path23.join(pythonDir, "cognifold"))) {
23631
24026
  console.error(`[cognifold] FATAL: Python module not found at ${pythonDir}/cognifold`);
23632
24027
  throw new Error(`cognifold: python module not found`);
23633
24028
  }
23634
24029
  const childEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
23635
- const envFile = path22.join(pythonDir, ".env");
24030
+ if (this.config.llm?.apiKey) {
24031
+ childEnv["OPENAI_API_KEY"] = this.config.llm.apiKey;
24032
+ }
24033
+ if (this.config.llm?.baseUrl) {
24034
+ childEnv["OPENAI_BASE_URL"] = this.config.llm.baseUrl;
24035
+ }
24036
+ const envFile = path23.join(pythonDir, ".env");
23636
24037
  try {
23637
24038
  if (fs23.existsSync(envFile)) {
23638
24039
  const envContent = fs23.readFileSync(envFile, "utf-8");
@@ -23704,7 +24105,7 @@ var CogniFoldPlugin = class {
23704
24105
  init_BashTool();
23705
24106
  import { spawn as spawn6 } from "node:child_process";
23706
24107
  import net3 from "node:net";
23707
- import path23 from "node:path";
24108
+ import path24 from "node:path";
23708
24109
  import fs24 from "node:fs";
23709
24110
 
23710
24111
  // src/memory/everos/config.ts
@@ -23738,7 +24139,8 @@ function parseEverosConfig(raw) {
23738
24139
  llm: raw.llm ?? { model: "glm-5.2", apiKey: "", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
23739
24140
  rerank: raw.rerank ?? { model: "Qwen/Qwen3-Reranker-4B", apiKey: "", baseUrl: "https://api.deepinfra.com/v1/inference" },
23740
24141
  lancedbPath: raw.lancedbPath ?? "",
23741
- sqlitePath: raw.sqlitePath ?? ""
24142
+ sqlitePath: raw.sqlitePath ?? "",
24143
+ minScore: raw.minScore
23742
24144
  };
23743
24145
  }
23744
24146
 
@@ -23776,21 +24178,31 @@ var EverosSearchClient = class {
23776
24178
  clearTimeout(timer);
23777
24179
  }
23778
24180
  }
23779
- /** Search — 3-mode unified endpoint */
24181
+ /** Search — routes to 8101 (agentic) or 8100 (hybrid) based on mode */
23780
24182
  async search(params) {
23781
24183
  const ctrl = new AbortController();
23782
24184
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23783
24185
  try {
23784
- const resp = await fetch(`${this.agenticUrl}/api/v1/search`, {
24186
+ const mode = params.mode || "hybrid";
24187
+ const useAgentic = mode === "hybrid_agentic" || mode === "agentic";
24188
+ const url = useAgentic ? `${this.agenticUrl}/api/v1/search` : `${this.everosUrl}/api/v1/memory/search`;
24189
+ const body = useAgentic ? JSON.stringify({
24190
+ query: params.query,
24191
+ user_id: params.userId || "xiaomei",
24192
+ mode,
24193
+ top_k: params.topK ?? 5,
24194
+ strategy: params.strategy || "multi_query"
24195
+ }) : JSON.stringify({
24196
+ query: params.query,
24197
+ user_id: params.userId || "user",
24198
+ app_id: "xiaomei",
24199
+ project_id: "default",
24200
+ top_k: params.topK ?? 5
24201
+ });
24202
+ const resp = await fetch(url, {
23785
24203
  method: "POST",
23786
24204
  headers: { "Content-Type": "application/json" },
23787
- body: JSON.stringify({
23788
- query: params.query,
23789
- user_id: params.userId || "xiaomei",
23790
- mode: params.mode || "hybrid_agentic",
23791
- top_k: params.topK ?? 5,
23792
- strategy: params.strategy || "multi_query"
23793
- }),
24205
+ body,
23794
24206
  signal: ctrl.signal
23795
24207
  });
23796
24208
  if (!resp.ok) {
@@ -23830,6 +24242,7 @@ var EverosPlugin = class {
23830
24242
  }
23831
24243
  async start(ctx) {
23832
24244
  if (!this.config.enabled) return;
24245
+ await this.ensureVenv();
23833
24246
  try {
23834
24247
  await this.client.healthEveros();
23835
24248
  console.log(`[everos] EverOS already running at ${this.config.everosUrl}`);
@@ -23900,13 +24313,15 @@ var EverosPlugin = class {
23900
24313
  }, 3e5);
23901
24314
  }
23902
24315
  async startEveros() {
23903
- const pythonDir = path23.dirname(this.config.lancedbPath);
23904
- const configPath = path23.join(pythonDir, "config.toml");
24316
+ const pythonDir = path24.dirname(this.config.lancedbPath);
24317
+ const configPath = path24.join(pythonDir, "config.toml");
23905
24318
  await this.ensureFcntlCompat();
23906
24319
  const venvPython = this.findVenvPython();
24320
+ const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
23907
24321
  const args = ["server", "start"];
23908
- const cmd = `${venvPython} ${args.join(" ")}`;
24322
+ const cmd = `${everosBin} ${args.join(" ")}`;
23909
24323
  console.log(`[everos] Starting EverOS: ${cmd}`);
24324
+ console.log(`[everos] LLM config: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
23910
24325
  if (process.platform === "win32") {
23911
24326
  const { shell, args: shellArgs } = findShell();
23912
24327
  spawn6(shell, [...shellArgs, cmd], {
@@ -23921,7 +24336,7 @@ var EverosPlugin = class {
23921
24336
  env: { ...process.env, PYTHONUNBUFFERED: "1" }
23922
24337
  });
23923
24338
  }
23924
- await this.waitForReady(`${this.config.everosUrl}/health`, 3e4);
24339
+ await this.waitForReady(`${this.config.everosUrl}/health`, 6e4);
23925
24340
  }
23926
24341
  startAgenticServer() {
23927
24342
  const pythonDir = this.getPythonDir();
@@ -23931,6 +24346,7 @@ var EverosPlugin = class {
23931
24346
  const cmd = `${venvPython} ${args.join(" ")}`;
23932
24347
  console.log(`[everos] Starting agentic server: ${cmd}`);
23933
24348
  console.log(`[everos] Python dir: ${pythonDir}`);
24349
+ console.log(`[everos] LLM: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
23934
24350
  const childEnv = {
23935
24351
  ...process.env,
23936
24352
  PYTHONUNBUFFERED: "1",
@@ -23939,15 +24355,25 @@ var EverosPlugin = class {
23939
24355
  LLM_API_KEY: this.config.llm.apiKey,
23940
24356
  LLM_BASE_URL: this.config.llm.baseUrl,
23941
24357
  RERANK_API_KEY: this.config.rerank.apiKey,
23942
- RERANK_URL: `${this.config.rerank.baseUrl}/${this.config.rerank.model}`,
24358
+ RERANK_URL: this.config.rerank.baseUrl,
23943
24359
  LANCEDB_PATH: this.config.lancedbPath,
23944
24360
  SQLITE_PATH: this.config.sqlitePath,
23945
24361
  EVEROS_USER_ID: this.config.userId
23946
24362
  };
24363
+ const maskKey = (k2) => k2 ? `${k2.slice(0, 4)}\u2026${k2.slice(-4)}` : "(empty!)";
24364
+ console.log(`[everos] agentic env:`);
24365
+ console.log(`[everos] EVEROS_URL=${childEnv.EVEROS_URL}`);
24366
+ console.log(`[everos] LLM_MODEL=${childEnv.LLM_MODEL}`);
24367
+ console.log(`[everos] LLM_API_KEY=${maskKey(childEnv.LLM_API_KEY)}`);
24368
+ console.log(`[everos] LLM_BASE_URL=${childEnv.LLM_BASE_URL}`);
24369
+ console.log(`[everos] RERANK_API_KEY=${maskKey(childEnv.RERANK_API_KEY)}`);
24370
+ console.log(`[everos] RERANK_URL=${childEnv.RERANK_URL}`);
24371
+ console.log(`[everos] LANCEDB_PATH=${childEnv.LANCEDB_PATH}`);
24372
+ console.log(`[everos] SQLITE_PATH=${childEnv.SQLITE_PATH}`);
24373
+ console.log(`[everos] EVEROS_USER_ID=${childEnv.EVEROS_USER_ID}`);
23947
24374
  let child;
23948
24375
  if (process.platform === "win32") {
23949
- const { shell, args: shellArgs } = findShell();
23950
- child = spawn6(shell, [...shellArgs, cmd], {
24376
+ child = spawn6(venvPython, args, {
23951
24377
  cwd: pythonDir,
23952
24378
  stdio: ["ignore", "pipe", "pipe"],
23953
24379
  env: childEnv
@@ -23977,21 +24403,64 @@ var EverosPlugin = class {
23977
24403
  return child;
23978
24404
  }
23979
24405
  findVenvPython() {
23980
- const stateDir = process.env.OPENCLAW_STATE_DIR || path23.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
24406
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path24.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
23981
24407
  if (process.platform === "win32") {
23982
- return path23.join(stateDir, "everos-venv", "Scripts", "python.exe");
24408
+ return path24.join(stateDir, "everos-venv", "Scripts", "python.exe");
24409
+ }
24410
+ return path24.join(stateDir, "everos-venv", "bin", "python");
24411
+ }
24412
+ /** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
24413
+ async ensureVenv() {
24414
+ const venvPython = this.findVenvPython();
24415
+ if (fs24.existsSync(venvPython)) return;
24416
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path24.join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw");
24417
+ const venvDir = path24.join(stateDir, "everos-venv");
24418
+ const everosSrc = path24.join(stateDir, "workspace", "research", "EverOS");
24419
+ console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
24420
+ console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
24421
+ const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
24422
+ let sysPython = "";
24423
+ for (const cmd of pyCandidates) {
24424
+ try {
24425
+ const { execSync: execSync3 } = await import("node:child_process");
24426
+ execSync3(`"${cmd}" --version`, { stdio: "pipe", shell: true });
24427
+ sysPython = cmd;
24428
+ break;
24429
+ } catch {
24430
+ }
24431
+ }
24432
+ if (!sysPython) {
24433
+ console.error(`[everos] \u2717 Python not found. Install Python 3.10+ first.`);
24434
+ return;
24435
+ }
24436
+ try {
24437
+ console.log(`[everos] Creating venv with ${sysPython}...`);
24438
+ const { execSync: execSync3 } = await import("node:child_process");
24439
+ execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
24440
+ const pip = process.platform === "win32" ? path24.join(venvDir, "Scripts", "pip.exe") : path24.join(venvDir, "bin", "pip");
24441
+ const everosReq = path24.join(this.getPythonDir(), "requirements.txt");
24442
+ if (fs24.existsSync(everosReq)) {
24443
+ console.log(`[everos] Installing from requirements.txt...`);
24444
+ execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
24445
+ } else {
24446
+ console.log(`[everos] No requirements.txt found, installing everos from PyPI...`);
24447
+ execSync3(`"${pip}" install everos -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
24448
+ }
24449
+ console.log(`[everos] \u2705 venv created successfully`);
24450
+ } catch (err) {
24451
+ console.error(`[everos] \u2717 Failed to create venv: ${err.message}`);
24452
+ console.error(`[everos] Manual setup: see workspace/scripts/everos-setup.sh`);
23983
24453
  }
23984
- return path23.join(stateDir, "everos-venv", "bin", "python");
23985
24454
  }
23986
24455
  getPythonDir() {
23987
24456
  const dir = import.meta.dirname;
23988
24457
  const candidates = [
23989
- path23.join(dir, "python"),
23990
- path23.resolve(dir, "..", "src", "memory", "everos", "python"),
23991
- path23.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
24458
+ path24.join(dir, "python"),
24459
+ path24.resolve(dir, "..", "src", "memory", "everos", "python"),
24460
+ path24.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
23992
24461
  ];
23993
24462
  for (const candidate of candidates) {
23994
- if (fs24.existsSync(path23.join(candidate, "agentic_server.py"))) {
24463
+ if (fs24.existsSync(path24.join(candidate, "agentic_server.py"))) {
23995
24464
  return candidate;
23996
24465
  }
23997
24466
  }
@@ -24000,11 +24469,11 @@ var EverosPlugin = class {
24000
24469
  async ensureFcntlCompat() {
24001
24470
  if (process.platform !== "win32") return;
24002
24471
  const venvPython = this.findVenvPython();
24003
- const venvDir = path23.dirname(path23.dirname(venvPython));
24004
- const sitePackages = path23.join(venvDir, "Lib", "site-packages");
24005
- const target = path23.join(sitePackages, "fcntl.py");
24472
+ const venvDir = path24.dirname(path24.dirname(venvPython));
24473
+ const sitePackages = path24.join(venvDir, "Lib", "site-packages");
24474
+ const target = path24.join(sitePackages, "fcntl.py");
24006
24475
  if (fs24.existsSync(target)) return;
24007
- const source = path23.join(this.getPythonDir(), "fcntl_compat.py");
24476
+ const source = path24.join(this.getPythonDir(), "fcntl_compat.py");
24008
24477
  if (fs24.existsSync(source)) {
24009
24478
  try {
24010
24479
  fs24.copyFileSync(source, target);
@@ -24054,7 +24523,7 @@ var EverosPlugin = class {
24054
24523
  init_task_manager();
24055
24524
 
24056
24525
  // src/skills/scanner.ts
24057
- import * as path24 from "node:path";
24526
+ import * as path25 from "node:path";
24058
24527
  import * as fs25 from "node:fs";
24059
24528
  function scanSkills(skillsDir) {
24060
24529
  if (!fs25.existsSync(skillsDir)) {
@@ -24065,7 +24534,7 @@ function scanSkills(skillsDir) {
24065
24534
  const skills = [];
24066
24535
  for (const entry of entries) {
24067
24536
  if (!entry.isDirectory()) continue;
24068
- const skillMdPath = path24.join(skillsDir, entry.name, "SKILL.md");
24537
+ const skillMdPath = path25.join(skillsDir, entry.name, "SKILL.md");
24069
24538
  if (!fs25.existsSync(skillMdPath)) continue;
24070
24539
  try {
24071
24540
  const content = fs25.readFileSync(skillMdPath, "utf-8");
@@ -24133,7 +24602,7 @@ function parseFrontmatter2(content) {
24133
24602
  // src/tools/SkillTool/SkillTool.ts
24134
24603
  init_registry();
24135
24604
  import * as fs26 from "node:fs";
24136
- import * as path25 from "node:path";
24605
+ import * as path26 from "node:path";
24137
24606
 
24138
24607
  // src/tools/SkillTool/constants.ts
24139
24608
  var SKILL_TOOL_NAME2 = "Skill";
@@ -24210,12 +24679,12 @@ Important:
24210
24679
  `;
24211
24680
  }
24212
24681
  function loadSkillContent(skillName) {
24213
- const skillMdPath = path25.join(skillsDirPath, skillName, "SKILL.md");
24682
+ const skillMdPath = path26.join(skillsDirPath, skillName, "SKILL.md");
24214
24683
  if (!fs26.existsSync(skillMdPath)) return null;
24215
24684
  const content = fs26.readFileSync(skillMdPath, "utf-8");
24216
24685
  const bodyMatch = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/);
24217
24686
  const body = bodyMatch ? bodyMatch[1] : content;
24218
- const skillDir = path25.dirname(skillMdPath);
24687
+ const skillDir = path26.dirname(skillMdPath);
24219
24688
  const normalizedDir = process.platform === "win32" ? skillDir.replace(/\\/g, "/") : skillDir;
24220
24689
  let finalContent = `Base directory for this skill: ${normalizedDir}
24221
24690
 
@@ -24490,9 +24959,9 @@ Examples:
24490
24959
  init_registry();
24491
24960
  init_live();
24492
24961
  import fs27 from "node:fs";
24493
- import path26 from "node:path";
24962
+ import path27 from "node:path";
24494
24963
  function getHusbandFeishuId(workspace) {
24495
- const contactsPath = path26.join(workspace, "prompts", "contacts.md");
24964
+ const contactsPath = path27.join(workspace, "prompts", "contacts.md");
24496
24965
  try {
24497
24966
  const text = fs27.readFileSync(contactsPath, "utf-8");
24498
24967
  const m2 = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
@@ -24695,7 +25164,7 @@ Examples:
24695
25164
  init_live();
24696
25165
  init_registry();
24697
25166
  import * as fs28 from "node:fs";
24698
- import * as path27 from "node:path";
25167
+ import * as path28 from "node:path";
24699
25168
  var MIME_MAP = {
24700
25169
  ".jpg": "jpeg",
24701
25170
  ".jpeg": "jpeg",
@@ -24707,7 +25176,7 @@ var MIME_MAP = {
24707
25176
  function resolveLatestImage(specifiedPath, mediaDir) {
24708
25177
  if (specifiedPath && fs28.existsSync(specifiedPath)) return specifiedPath;
24709
25178
  if (!fs28.existsSync(mediaDir)) return null;
24710
- 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);
25179
+ 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);
24711
25180
  return files[0]?.p || null;
24712
25181
  }
24713
25182
  registry.register({
@@ -24731,13 +25200,13 @@ registry.register({
24731
25200
  if (!provider?.streamChat) {
24732
25201
  return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
24733
25202
  }
24734
- const mediaDir = path27.join(ctx.stateDir, "media", "inbound");
25203
+ const mediaDir = path28.join(ctx.stateDir, "media", "inbound");
24735
25204
  const imagePath = resolveLatestImage(args.image_path, mediaDir);
24736
25205
  if (!imagePath) {
24737
25206
  return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
24738
25207
  }
24739
25208
  const rawPrompt = args.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
24740
- const ext = path27.extname(imagePath).toLowerCase();
25209
+ const ext = path28.extname(imagePath).toLowerCase();
24741
25210
  const mime = MIME_MAP[ext] || "jpeg";
24742
25211
  const imgB64 = fs28.readFileSync(imagePath).toString("base64");
24743
25212
  const userMsg = {
@@ -24777,13 +25246,13 @@ init_registry();
24777
25246
  import { execFile } from "node:child_process";
24778
25247
  import { promisify } from "node:util";
24779
25248
  import * as fs29 from "node:fs";
24780
- import * as path28 from "node:path";
25249
+ import * as path29 from "node:path";
24781
25250
  import * as os3 from "node:os";
24782
25251
  var execFileAsync = promisify(execFile);
24783
- var VOICE_DIR = path28.join(os3.tmpdir(), "engine-voice");
25252
+ var VOICE_DIR = path29.join(os3.tmpdir(), "engine-voice");
24784
25253
  async function ttsCosyvoice(text, apiKey, model, voice, workspaceId) {
24785
25254
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24786
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25255
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
24787
25256
  const script = `
24788
25257
  import sys, json, wave, time, threading
24789
25258
  import dashscope
@@ -24848,7 +25317,7 @@ var GPTSOVITS_REF_TEXT = "\u6625\u7720\u4E0D\u89C9\u6653\uFF0C\u5904\u5904\u95FB
24848
25317
  var GPTSOVITS_REF_LANG = "zh";
24849
25318
  async function ttsGptsovits(text) {
24850
25319
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24851
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25320
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
24852
25321
  const params = new URLSearchParams({
24853
25322
  text,
24854
25323
  text_language: "zh",
@@ -24865,7 +25334,7 @@ async function ttsGptsovits(text) {
24865
25334
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
24866
25335
  async function ttsEdge(text) {
24867
25336
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24868
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25337
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
24869
25338
  const script = `
24870
25339
  import asyncio, edge_tts, sys
24871
25340
  async def main():
@@ -24958,7 +25427,7 @@ registry.register({
24958
25427
  } catch (e) {
24959
25428
  return { content: `TTS failed: ${e.message}`, isError: true };
24960
25429
  }
24961
- const ext = path28.extname(audioPath).toLowerCase();
25430
+ const ext = path29.extname(audioPath).toLowerCase();
24962
25431
  const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg" };
24963
25432
  const mimeType = mimeMap[ext] || "audio/mpeg";
24964
25433
  const sizeKB = fs29.statSync(audioPath).size / 1024;
@@ -24989,7 +25458,7 @@ registry.register({
24989
25458
  init_live();
24990
25459
  init_registry();
24991
25460
  import * as fs30 from "node:fs";
24992
- import * as path29 from "node:path";
25461
+ import * as path30 from "node:path";
24993
25462
  var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
24994
25463
  var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
24995
25464
  var DEFAULT_RESOLUTION = "1k";
@@ -25105,7 +25574,7 @@ registry.register({
25105
25574
  const REFERENCES = getReferences(ctx);
25106
25575
  const refName = args.reference || "default";
25107
25576
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
25108
- const refPath = path29.join(ctx.workspace, refEntry.p);
25577
+ const refPath = path30.join(ctx.workspace, refEntry.p);
25109
25578
  if (!fs30.existsSync(refPath)) {
25110
25579
  return { content: `Error: reference image not found at ${refPath}`, isError: true };
25111
25580
  }
@@ -25124,10 +25593,10 @@ registry.register({
25124
25593
  } catch (err) {
25125
25594
  return { content: `Selfie generation failed: ${err.message}`, isError: true };
25126
25595
  }
25127
- const imagesDir = path29.join(ctx.workspace, "images");
25596
+ const imagesDir = path30.join(ctx.workspace, "images");
25128
25597
  if (!fs30.existsSync(imagesDir)) fs30.mkdirSync(imagesDir, { recursive: true });
25129
25598
  const filename = `selfie_${Date.now()}.jpg`;
25130
- const outputPath = path29.join(imagesDir, filename);
25599
+ const outputPath = path30.join(imagesDir, filename);
25131
25600
  fs30.writeFileSync(outputPath, imageBuffer);
25132
25601
  const mgr = ctx.channelManager;
25133
25602
  if (mgr) {
@@ -25139,11 +25608,11 @@ registry.register({
25139
25608
  mimeType: "image/jpeg"
25140
25609
  });
25141
25610
  } catch (err) {
25142
- return { content: `Selfie generated but send failed: ${err.message}. Image: ${path29.resolve(outputPath)}`, isError: false };
25611
+ return { content: `Selfie generated but send failed: ${err.message}. Image: ${path30.resolve(outputPath)}`, isError: false };
25143
25612
  }
25144
25613
  return { content: `Selfie sent! Mode: ${mode}, Provider: ${getProvider(ctx)}, Ref: ${refEntry.name}` };
25145
25614
  }
25146
- return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path29.resolve(outputPath)}` };
25615
+ return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path30.resolve(outputPath)}` };
25147
25616
  },
25148
25617
  isConcurrencySafe: () => false,
25149
25618
  interruptBehavior: () => "block",
@@ -25610,14 +26079,14 @@ init_planModeState();
25610
26079
 
25611
26080
  // src/utils/plans.ts
25612
26081
  import * as fs32 from "node:fs";
25613
- import * as path31 from "node:path";
26082
+ import * as path32 from "node:path";
25614
26083
  import * as crypto4 from "node:crypto";
25615
26084
  var MAX_SLUG_RETRIES = 10;
25616
26085
  function generateSlug() {
25617
26086
  return crypto4.randomBytes(4).toString("hex");
25618
26087
  }
25619
26088
  function getPlansDirectory(stateDir) {
25620
- const plansDir = path31.join(stateDir, "plans");
26089
+ const plansDir = path32.join(stateDir, "plans");
25621
26090
  fs32.mkdirSync(plansDir, { recursive: true });
25622
26091
  return plansDir;
25623
26092
  }
@@ -25628,7 +26097,7 @@ function getPlanSlug(sessionId, stateDir) {
25628
26097
  const plansDir = getPlansDirectory(stateDir);
25629
26098
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
25630
26099
  slug = generateSlug();
25631
- const filePath = path31.join(plansDir, `${slug}.md`);
26100
+ const filePath = path32.join(plansDir, `${slug}.md`);
25632
26101
  if (!fs32.existsSync(filePath)) {
25633
26102
  break;
25634
26103
  }
@@ -25640,9 +26109,9 @@ function getPlanSlug(sessionId, stateDir) {
25640
26109
  function getPlanFilePath(sessionId, stateDir, agentId) {
25641
26110
  const slug = getPlanSlug(sessionId, stateDir);
25642
26111
  if (!agentId) {
25643
- return path31.join(getPlansDirectory(stateDir), `${slug}.md`);
26112
+ return path32.join(getPlansDirectory(stateDir), `${slug}.md`);
25644
26113
  }
25645
- return path31.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26114
+ return path32.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
25646
26115
  }
25647
26116
  function getPlan(sessionId, stateDir, agentId) {
25648
26117
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
@@ -26493,7 +26962,7 @@ async function setupFeatures(features, licensedFeatures) {
26493
26962
  // src/license/license.ts
26494
26963
  import * as crypto6 from "node:crypto";
26495
26964
  import * as fs39 from "node:fs";
26496
- import * as path39 from "node:path";
26965
+ import * as path40 from "node:path";
26497
26966
  var EMBEDDED_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
26498
26967
  MCowBQYDK2VwAyEAaKBEX+e8+D59qwtidazsu7WYDglApyvsVI3APwFoakA=
26499
26968
  -----END PUBLIC KEY-----`;
@@ -26524,7 +26993,7 @@ function loadLicense(stateDir, devMode) {
26524
26993
  _cachedLicense = allActive;
26525
26994
  return allActive;
26526
26995
  }
26527
- const licensePath = path39.join(stateDir, "license.json");
26996
+ const licensePath = path40.join(stateDir, "license.json");
26528
26997
  if (!fs39.existsSync(licensePath)) {
26529
26998
  console.log("[license] No license.json found, running basic engine only");
26530
26999
  return null;
@@ -26593,14 +27062,14 @@ var EverosSearchSchema = {
26593
27062
  type: "object",
26594
27063
  properties: {
26595
27064
  query: { type: "string", description: "\u641C\u7D22\u67E5\u8BE2" },
26596
- maxResults: { type: "number", description: "\u6700\u5927\u8FD4\u56DE\u6570 (\u9ED8\u8BA45)" }
27065
+ maxResults: { type: "number", description: "\u6700\u5927\u8FD4\u56DE\u6570 (\u9ED8\u8BA410)" },
27066
+ 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)" }
26597
27067
  },
26598
27068
  required: ["query"]
26599
27069
  };
26600
27070
  function createEverosSearchTool(everosCfg) {
26601
27071
  const agenticUrl = (everosCfg?.agenticUrl || "http://127.0.0.1:8101").replace(/\/$/, "");
26602
27072
  const userId = everosCfg?.userId || "xiaomei";
26603
- const defaultMode = everosCfg?.defaultMode || "hybrid_agentic";
26604
27073
  return {
26605
27074
  name: "memory_search",
26606
27075
  description: "Mandatory recall step: semantically search memory before answering questions about prior work, decisions, dates, people, preferences, or todos.",
@@ -26609,6 +27078,7 @@ function createEverosSearchTool(everosCfg) {
26609
27078
  const query = args.query;
26610
27079
  if (!query) return { content: "\u7F3A\u5C11 query \u53C2\u6570" };
26611
27080
  const topK = args.maxResults ?? 10;
27081
+ const mode = args.mode || "hybrid_agentic";
26612
27082
  const ctrl = new AbortController();
26613
27083
  const timer = setTimeout(() => ctrl.abort(), 3e4);
26614
27084
  try {
@@ -26618,7 +27088,7 @@ function createEverosSearchTool(everosCfg) {
26618
27088
  body: JSON.stringify({
26619
27089
  query,
26620
27090
  user_id: userId,
26621
- mode: defaultMode,
27091
+ mode,
26622
27092
  top_k: topK
26623
27093
  }),
26624
27094
  signal: ctrl.signal
@@ -26626,7 +27096,7 @@ function createEverosSearchTool(everosCfg) {
26626
27096
  clearTimeout(timer);
26627
27097
  if (!resp.ok) {
26628
27098
  const text = await resp.text();
26629
- console.warn(`[everos] search failed: ${resp.status} ${text.slice(0, 200)}`);
27099
+ console.warn(`[memory_search] failed: ${resp.status} ${text.slice(0, 200)}`);
26630
27100
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26631
27101
  }
26632
27102
  const data = await resp.json();
@@ -26635,7 +27105,7 @@ function createEverosSearchTool(everosCfg) {
26635
27105
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26636
27106
  }
26637
27107
  const formatted = episodes.map((ep, i) => {
26638
- const score = ep.score != null ? ` (score: ${ep.score.toFixed(3)})` : "";
27108
+ const score = ep.score != null ? ` (score: ${typeof ep.score === "number" ? ep.score.toFixed(3) : ep.score})` : "";
26639
27109
  const subject = ep.subject || "";
26640
27110
  const ts = ep.timestamp ? ` [${ep.timestamp}]` : "";
26641
27111
  return `### ${i + 1}. ${subject}${ts}${score}
@@ -26647,9 +27117,9 @@ ${formatted}` };
26647
27117
  } catch (err) {
26648
27118
  clearTimeout(timer);
26649
27119
  if (err.name === "AbortError") {
26650
- console.warn(`[everos] search timeout: ${query.slice(0, 50)}`);
27120
+ console.warn(`[memory_search] timeout: ${query.slice(0, 50)}`);
26651
27121
  } else {
26652
- console.warn(`[everos] search error: ${err.message}`);
27122
+ console.warn(`[memory_search] error: ${err.message}`);
26653
27123
  }
26654
27124
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26655
27125
  }
@@ -27148,9 +27618,9 @@ async function startEngine(config, opts) {
27148
27618
  process.env.ENGINE_MEDIA_DIR = config.mediaDir;
27149
27619
  process.env.ENGINE7_WORKSPACE = config.workspace;
27150
27620
  process.env.OPENCLAW_WORKSPACE = config.workspace;
27151
- fs41.mkdirSync(path42.join(config.stateDir, "agents", "main", "memory"), { recursive: true });
27152
- fs41.mkdirSync(path42.join(config.stateDir, "agents", "main", "sessions"), { recursive: true });
27153
- fs41.mkdirSync(path42.join(config.stateDir, "logs"), { recursive: true });
27621
+ fs41.mkdirSync(path43.join(config.stateDir, "agents", "main", "memory"), { recursive: true });
27622
+ fs41.mkdirSync(path43.join(config.stateDir, "agents", "main", "sessions"), { recursive: true });
27623
+ fs41.mkdirSync(path43.join(config.stateDir, "logs"), { recursive: true });
27154
27624
  fs41.mkdirSync(config.workspace, { recursive: true });
27155
27625
  fs41.mkdirSync(config.mediaDir, { recursive: true });
27156
27626
  try {
@@ -27260,7 +27730,7 @@ async function startEngine(config, opts) {
27260
27730
  const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
27261
27731
  initSessionMemory2({
27262
27732
  workspace: config.workspace,
27263
- stateDir: path42.join(config.stateDir, "session-memory"),
27733
+ stateDir: path43.join(config.stateDir, "session-memory"),
27264
27734
  provider,
27265
27735
  model: config.provider.modelId || config.model || "deepseek-v4-flash",
27266
27736
  features: config.profile.features
@@ -27290,9 +27760,9 @@ async function startEngine(config, opts) {
27290
27760
  if (config.hooks) {
27291
27761
  loadHooksFromConfig({ hooks: config.hooks });
27292
27762
  }
27293
- const hooksPath = path42.join(config.workspace, ".hooks.json");
27763
+ const hooksPath = path43.join(config.workspace, ".hooks.json");
27294
27764
  loadHooksFromFile(hooksPath);
27295
- const settingsHooksPath = path42.join(config.stateDir, "settings.json");
27765
+ const settingsHooksPath = path43.join(config.stateDir, "settings.json");
27296
27766
  loadHooksFromFile(settingsHooksPath);
27297
27767
  console.log(`[hooks] Loaded hooks configuration`);
27298
27768
  registerCallbackHook("PreCompact", {
@@ -27306,15 +27776,15 @@ async function startEngine(config, opts) {
27306
27776
  const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
27307
27777
  const dateStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")}`;
27308
27778
  const timeStr = `${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
27309
- const dailyDir = path42.join(workspace, "memory", "daily");
27310
- const dailyPath = path42.join(dailyDir, `${dateStr}.md`);
27779
+ const dailyDir = path43.join(workspace, "memory", "daily");
27780
+ const dailyPath = path43.join(dailyDir, `${dateStr}.md`);
27311
27781
  try {
27312
27782
  const fs42 = await import("node:fs");
27313
27783
  if (!fs42.existsSync(dailyDir)) {
27314
27784
  fs42.mkdirSync(dailyDir, { recursive: true });
27315
27785
  }
27316
- const sessionsDir = path42.join(config.stateDir, "agents", "main", "sessions");
27317
- const sessionFile = path42.join(sessionsDir, `${sessionId}.jsonl`);
27786
+ const sessionsDir = path43.join(config.stateDir, "agents", "main", "sessions");
27787
+ const sessionFile = path43.join(sessionsDir, `${sessionId}.jsonl`);
27318
27788
  const recentLines = [];
27319
27789
  if (fs42.existsSync(sessionFile)) {
27320
27790
  const content = fs42.readFileSync(sessionFile, "utf-8");
@@ -27367,7 +27837,7 @@ ${entry}`);
27367
27837
  if (!workspace) return { continue: true };
27368
27838
  try {
27369
27839
  const fs42 = await import("node:fs");
27370
- const bufferPath = path42.join(workspace, "memory", "working-buffer.md");
27840
+ const bufferPath = path43.join(workspace, "memory", "working-buffer.md");
27371
27841
  if (fs42.existsSync(bufferPath)) {
27372
27842
  const stat4 = fs42.statSync(bufferPath);
27373
27843
  const ageMs = Date.now() - stat4.mtimeMs;
@@ -27420,7 +27890,7 @@ ${content}`
27420
27890
  return `${hr}h ${remMin}m`;
27421
27891
  }
27422
27892
  if (config.skills?.enabled !== false) {
27423
- const skillsDir = config.skills?.path ? path42.isAbsolute(config.skills.path) ? config.skills.path : path42.resolve(config.workspace, config.skills.path) : path42.resolve(config.workspace, "skills");
27893
+ const skillsDir = config.skills?.path ? path43.isAbsolute(config.skills.path) ? config.skills.path : path43.resolve(config.workspace, config.skills.path) : path43.resolve(config.workspace, "skills");
27424
27894
  const modelDef2 = config.provider.models.find((m2) => m2.id === config.model);
27425
27895
  const contextWindowTokens = modelDef2?.contextWindow;
27426
27896
  const skills = scanSkills(skillsDir);
@@ -27439,7 +27909,7 @@ ${content}`
27439
27909
  workspace: config.workspace
27440
27910
  });
27441
27911
  const systemPrompt = [systemStable, systemDynamic].join("\n\n");
27442
- const promptDumpPath = path42.join(config.workspace, ".system-prompt.txt");
27912
+ const promptDumpPath = path43.join(config.workspace, ".system-prompt.txt");
27443
27913
  fs41.writeFileSync(promptDumpPath, systemPrompt);
27444
27914
  console.log(`System prompt: ${systemStable.length} chars stable + ${systemDynamic.length} chars dynamic \u2192 ${promptDumpPath}`);
27445
27915
  const modelDef = config.provider.models.find((m2) => m2.id === config.model);
@@ -27550,20 +28020,23 @@ ${content}`
27550
28020
  enabled: true,
27551
28021
  url: everosCfg.everosUrl || "http://127.0.0.1:8100",
27552
28022
  appId: everosCfg.userId || "default",
27553
- userId: everosCfg.userId || "default"
28023
+ userId: everosCfg.userId || "default",
28024
+ agentName: everosCfg.agentName || everosCfg.userId || "assistant"
27554
28025
  });
27555
28026
  sessions.onWriterCreated = (writer, sessionId) => {
27556
28027
  writer.onMessageWritten = (msg2) => {
28028
+ console.log(`[everos-sync] onMessageWritten fired: role=${msg2.role} len=${msg2.text.length}`);
27557
28029
  everosSync.push({
27558
28030
  sessionId: writer.engineSessionId || sessionId,
27559
28031
  role: msg2.role === "toolResult" ? "tool" : msg2.role,
27560
28032
  text: msg2.text,
27561
28033
  timestamp: new Date(msg2.timestamp).getTime()
27562
- }).catch(() => {
27563
- });
28034
+ }).catch((e) => console.warn(`[everos-sync] push error: ${e}`));
27564
28035
  };
27565
28036
  };
27566
28037
  console.log(`[everos-sync] hook registered (appId=${everosCfg.userId})`);
28038
+ } else {
28039
+ console.log(`[everos-sync] SKIPPED \u2014 config.everos not enabled or missing`);
27567
28040
  }
27568
28041
  const channelManager = new ChannelManager();
27569
28042
  const memoryRecallProvider = createMemorySideProvider(
@@ -27594,6 +28067,7 @@ ${content}`
27594
28067
  recallProvider: memoryRecallProvider || void 0,
27595
28068
  extractProvider: memoryExtractProvider || void 0,
27596
28069
  topics: config.topics,
28070
+ everosCfg: config.everos,
27597
28071
  mcpManager
27598
28072
  };
27599
28073
  if (visionEngine && visionConfig) {
@@ -28514,8 +28988,8 @@ Auto-routing disabled \u2014 all messages use this model.
28514
28988
  let writePath = configPath;
28515
28989
  if (configPath && !fs41.existsSync(configPath)) {
28516
28990
  const __pFile = fileURLToPath(import.meta.url);
28517
- const __pDir = path42.dirname(__pFile);
28518
- const altPath = path42.join(path42.resolve(__pDir, "../configs"), path42.basename(configPath));
28991
+ const __pDir = path43.dirname(__pFile);
28992
+ const altPath = path43.join(path43.resolve(__pDir, "../configs"), path43.basename(configPath));
28519
28993
  if (fs41.existsSync(altPath)) {
28520
28994
  console.warn(`[primary] Config not found at ${configPath}, falling back to ${altPath}`);
28521
28995
  writePath = altPath;
@@ -28795,7 +29269,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
28795
29269
  const ext = detected.split("/")[1] || "png";
28796
29270
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
28797
29271
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
28798
- const savedPath = path42.join(config.mediaDir, `${imageId}.${ext}`);
29272
+ const savedPath = path43.join(config.mediaDir, `${imageId}.${ext}`);
28799
29273
  fs41.writeFileSync(savedPath, resized.buffer);
28800
29274
  savedPaths.push(savedPath);
28801
29275
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
@@ -28822,7 +29296,7 @@ ${pathStr}` }];
28822
29296
  }
28823
29297
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
28824
29298
  if (nonImageAttachments && nonImageAttachments.length > 0) {
28825
- const outDir = path42.join(config.mediaDir, sessionId);
29299
+ const outDir = path43.join(config.mediaDir, sessionId);
28826
29300
  fs41.mkdirSync(outDir, { recursive: true });
28827
29301
  const resolved = [];
28828
29302
  for (const att of nonImageAttachments) {
@@ -28831,8 +29305,8 @@ ${pathStr}` }];
28831
29305
  const resp = await fetch(att.url);
28832
29306
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
28833
29307
  const buffer = Buffer.from(await resp.arrayBuffer());
28834
- const safeName2 = path42.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
28835
- const savedPath = path42.join(outDir, safeName2);
29308
+ const safeName2 = path43.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
29309
+ const savedPath = path43.join(outDir, safeName2);
28836
29310
  fs41.writeFileSync(savedPath, buffer);
28837
29311
  resolved.push(savedPath);
28838
29312
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
@@ -29180,7 +29654,8 @@ ${pathStr}` }];
29180
29654
  if (config.cognifold?.intentWatcher?.enabled) {
29181
29655
  try {
29182
29656
  const { registerCognifoldIntentWatcher: registerCognifoldIntentWatcher2 } = await Promise.resolve().then(() => (init_cognifold_intent_watcher(), cognifold_intent_watcher_exports));
29183
- const cfSessionId = config.cognifold.sessionId;
29657
+ const sm = globalThis.__cognifoldSessions;
29658
+ const cfSessionId = sm?.getSessionId?.("main") || config.cognifold.sessionId;
29184
29659
  if (!cfSessionId) {
29185
29660
  console.warn("[cognifold] intent-watcher: config.cognifold.sessionId \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 watcher");
29186
29661
  } else {
@@ -29193,7 +29668,7 @@ ${pathStr}` }];
29193
29668
  console.warn("[cognifold] watcher: config.workspace \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 proactive \u5199\u5165");
29194
29669
  return;
29195
29670
  }
29196
- const pFile = path42.join(wsDir, ".cognifold-proactive.json");
29671
+ const pFile = path43.join(wsDir, ".cognifold-proactive.json");
29197
29672
  const cognifoldBaseUrl = config.cognifold?.baseUrl || "http://127.0.0.1:9001";
29198
29673
  const cognifoldSessionId = cfSessionId;
29199
29674
  const rawSuggestions = data.suggestions || data.actions || (data.intent_id ? [data] : []);
@@ -29247,7 +29722,7 @@ ${pathStr}` }];
29247
29722
  console.error(`[cognifold] failed to save proactive: ${e.message}`);
29248
29723
  }
29249
29724
  if (enriched.length > 0) {
29250
- const promptFile = path42.join(config.workspace, "prompts", "cognifold-proactive.md");
29725
+ const promptFile = path43.join(config.workspace, "prompts", "cognifold-proactive.md");
29251
29726
  const promptText = fs41.existsSync(promptFile) ? fs41.readFileSync(promptFile, "utf-8") : "[CogniFold proactive] \u6709 " + enriched.length + " \u4E2A action \u5230\u671F\u4E86";
29252
29727
  const actionsJson = JSON.stringify(enriched, null, 2);
29253
29728
  const sessionId = cfSessionId;
@@ -29353,9 +29828,9 @@ async function doReloadConfig(config, deps, provider) {
29353
29828
  let reloadConfigPath = savedConfigPath;
29354
29829
  if (!fs41.existsSync(reloadConfigPath)) {
29355
29830
  const __filename = fileURLToPath(import.meta.url);
29356
- const __dirname = path42.dirname(__filename);
29357
- const engineConfigsDir = path42.resolve(__dirname, "../configs");
29358
- const altPath = path42.join(engineConfigsDir, path42.basename(savedConfigPath));
29831
+ const __dirname = path43.dirname(__filename);
29832
+ const engineConfigsDir = path43.resolve(__dirname, "../configs");
29833
+ const altPath = path43.join(engineConfigsDir, path43.basename(savedConfigPath));
29359
29834
  if (fs41.existsSync(altPath)) {
29360
29835
  console.warn(`[reload] Config not found at ${reloadConfigPath}, falling back to ${altPath} (dev mode)`);
29361
29836
  reloadConfigPath = altPath;
@@ -29408,7 +29883,7 @@ async function doReloadConfig(config, deps, provider) {
29408
29883
  } catch (err) {
29409
29884
  console.error(`[reload] Failed: ${err.message}`);
29410
29885
  try {
29411
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD FAILED: ${err.message}
29886
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD FAILED: ${err.message}
29412
29887
  ${err.stack}
29413
29888
  `);
29414
29889
  } catch {
@@ -29420,17 +29895,17 @@ function startConfigWatcher(config, deps, provider) {
29420
29895
  const raw = config._configFilePath;
29421
29896
  let configPath = raw;
29422
29897
  if (!fs41.existsSync(configPath)) {
29423
- configPath = path42.resolve(raw);
29898
+ configPath = path43.resolve(raw);
29424
29899
  }
29425
29900
  if (!fs41.existsSync(configPath)) {
29426
29901
  const __filename2 = fileURLToPath(import.meta.url);
29427
- const __dirname2 = path42.dirname(__filename2);
29428
- configPath = path42.resolve(__dirname2, "..", raw);
29902
+ const __dirname2 = path43.dirname(__filename2);
29903
+ configPath = path43.resolve(__dirname2, "..", raw);
29429
29904
  }
29430
29905
  if (!fs41.existsSync(configPath)) {
29431
29906
  console.warn(`[config-watch] config path invalid: ${configPath}, watcher disabled`);
29432
29907
  try {
29433
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] DISABLED: configPath=${configPath}
29908
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] DISABLED: configPath=${configPath}
29434
29909
  `);
29435
29910
  } catch {
29436
29911
  }
@@ -29442,13 +29917,13 @@ function startConfigWatcher(config, deps, provider) {
29442
29917
  debounceTimer = setTimeout(async () => {
29443
29918
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
29444
29919
  try {
29445
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
29920
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] CHANGE eventType=${eventType}, calling doReloadConfig
29446
29921
  `);
29447
29922
  } catch {
29448
29923
  }
29449
29924
  const result = await doReloadConfig(config, deps, provider);
29450
29925
  try {
29451
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD DONE: ok=${result.ok} changes=${result.changes.join(",")}
29926
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] RELOAD DONE: ok=${result.ok} changes=${result.changes.join(",")}
29452
29927
  `);
29453
29928
  } catch {
29454
29929
  }
@@ -29457,14 +29932,14 @@ function startConfigWatcher(config, deps, provider) {
29457
29932
  watcher.on("error", (err) => {
29458
29933
  console.error(`[config-watch] error: ${err.message}`);
29459
29934
  try {
29460
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${err.message}
29935
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${err.message}
29461
29936
  `);
29462
29937
  } catch {
29463
29938
  }
29464
29939
  });
29465
29940
  console.log(`[config-watch] watching ${configPath}`);
29466
29941
  try {
29467
- fs41.appendFileSync(path42.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] STARTED watching=${configPath}
29942
+ fs41.appendFileSync(path43.join(config.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] STARTED watching=${configPath}
29468
29943
  `);
29469
29944
  } catch {
29470
29945
  }