engine7 7.1.22 → 7.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,22 +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
- const osPlatform = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : process.platform;
18056
- const contextParts = [`\u5F53\u524D\u65F6\u95F4: ${dateStr}`, `\u7CFB\u7EDF: ${osPlatform} (${process.platform})`];
18057
- const meta = options.inboundMeta;
18058
- if (meta) {
18059
- contextParts.push(`\u6765\u6E90: ${meta.channel}`);
18060
- if (meta.channelType) contextParts.push(`\u6D88\u606F\u7C7B\u578B: ${meta.channelType === "dm" ? "\u79C1\u4FE1" : "\u7FA4\u804A"}`);
18061
- if (meta.channel_id) contextParts.push(`\u9891\u9053ID: ${meta.channel_id}`);
18062
- contextParts.push(`\u53D1\u9001\u8005ID: ${meta.from}`);
18063
- if (meta.fromName) contextParts.push(`\u53D1\u9001\u8005\u540D\u79F0: ${meta.fromName}`);
18064
- if (meta.messageId) contextParts.push(`\u6D88\u606FID: ${meta.messageId}`);
18065
- } else if (options.channel) {
18066
- contextParts.push(`\u6765\u6E90: ${options.channel}`);
18067
- }
18068
- if (options.sessionId) contextParts.push(`\u4F1A\u8BDDID: ${options.sessionId}`);
18069
18087
  parts.push(`# \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587
18070
- ${contextParts.join("\n")}`);
18088
+ \u5F53\u524D\u65F6\u95F4: ${dateStr}`);
18071
18089
  console.log(`[dynamic-prompt] Loaded: ${loaded2.length > 0 ? loaded2.join(", ") : "(none)"}`);
18072
18090
  return parts.join("\n\n");
18073
18091
  }
@@ -18369,6 +18387,169 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
18369
18387
  }));
18370
18388
  }
18371
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
+
18372
18553
  // src/handle-query.ts
18373
18554
  init_paths();
18374
18555
  import { readFileSync as readFileSync15, existsSync as existsSync12 } from "node:fs";
@@ -18441,18 +18622,18 @@ function truncate(s2, maxLen) {
18441
18622
  }
18442
18623
  var externalChanRulesCache = null;
18443
18624
  function loadExternalChanRules(workspace) {
18444
- const path43 = join20(workspace, "prompts", "external-chan-rules.md");
18445
- 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;
18446
18627
  let content = "";
18447
- if (existsSync12(path43)) {
18628
+ if (existsSync12(path44)) {
18448
18629
  try {
18449
- content = readFileSync15(path43, "utf-8").trim();
18630
+ content = readFileSync15(path44, "utf-8").trim();
18450
18631
  } catch (e) {
18451
18632
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
18452
18633
  }
18453
18634
  }
18454
- externalChanRulesCache = { path: path43, content };
18455
- 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}`);
18456
18637
  return externalChanRulesCache;
18457
18638
  }
18458
18639
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -18475,10 +18656,10 @@ function getExternalChanWhitelist(workspace, configExternalChannels) {
18475
18656
  if (!externalChanWhitelist) loadContactMap(workspace);
18476
18657
  return externalChanWhitelist;
18477
18658
  }
18478
- async function handleQuery(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18479
- 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);
18480
18661
  }
18481
- async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall) {
18662
+ async function handleQueryInner(text, sessionId, channelName, cb, deps, channelTarget, inboundMeta, skipRecall, source) {
18482
18663
  const { engine, sessions, channelManager, workspace, providerId, providerApi, model } = deps;
18483
18664
  const features = deps.features || {};
18484
18665
  const preQueryAbort = new AbortController();
@@ -18655,6 +18836,8 @@ ${text}` : text });
18655
18836
  const toolContext = {
18656
18837
  sessionId,
18657
18838
  channel: channelName === "cli" ? "console" : channelName,
18839
+ source: source || "",
18840
+ // 消息来源(user/inbox/heartbeat/cron/system/inner-voice),Stop hook 用来区分注入 turn
18658
18841
  workspace,
18659
18842
  stateDir: deps.stateDir || workspace,
18660
18843
  channelManager,
@@ -18829,7 +19012,23 @@ ${text}` : text });
18829
19012
  const recallP = deps.recallProvider;
18830
19013
  const recallMode = deps.topics?.recall?.mode || "llm";
18831
19014
  let relevantMemories;
18832
- 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") {
18833
19032
  relevantMemories = await findRelevantMemoriesVector(
18834
19033
  textForMemory,
18835
19034
  memoryDir,
@@ -18852,8 +19051,8 @@ ${text}` : text });
18852
19051
  const attachmentMemories = [];
18853
19052
  for (const mem of relevantMemories) {
18854
19053
  try {
18855
- const content = readFileSync15(mem.path, "utf-8");
18856
- 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);
18857
19056
  attachmentMemories.push({ path: mem.path, content, mtimeMs: mem.mtimeMs, header });
18858
19057
  } catch {
18859
19058
  }
@@ -19401,7 +19600,9 @@ function registerCognifoldBridge(config) {
19401
19600
  messageId: ctx.inbound.messageId
19402
19601
  }
19403
19602
  };
19404
- void enqueueEvent(cognifoldConfig.sessionId, event);
19603
+ const sm = globalThis.__cognifoldSessions;
19604
+ const dynamicSessionId = sm?.getSessionId?.("main") || cognifoldConfig.sessionId;
19605
+ void enqueueEvent(dynamicSessionId, event);
19405
19606
  return null;
19406
19607
  }, 80);
19407
19608
  }
@@ -19725,7 +19926,8 @@ var MessageDispatcher = class {
19725
19926
  msg2.deps,
19726
19927
  msg2.channelTarget,
19727
19928
  msg2.inboundMeta,
19728
- msg2.skipRecall
19929
+ msg2.skipRecall,
19930
+ msg2.source
19729
19931
  );
19730
19932
  } catch (err) {
19731
19933
  console.error(`[dispatcher] Query error (session=${msg2.sessionId}): ${err.message}`);
@@ -19885,13 +20087,19 @@ function startCliLoop(deps, cliConfig, channelManager, dispatcher) {
19885
20087
 
19886
20088
  // src/session/session-history.ts
19887
20089
  import fs14 from "node:fs";
20090
+ import path14 from "node:path";
19888
20091
  var BEIJING_OFFSET_MS = 8 * 36e5;
19889
20092
  var INJECTED_CONTENT_PATTERNS = [
19890
20093
  /【定时心跳】/,
19891
20094
  /\[内心对话测试\]/,
19892
20095
  /\[inner-voice\]/,
19893
20096
  /\[微信巡检\]/,
19894
- /\[plugin\]/
20097
+ /\[plugin\]/,
20098
+ /<nudge-notification>/,
20099
+ /<task-notification>/,
20100
+ /<calendar-notification>/,
20101
+ /## Actions \(\d+\s*个\)/
20102
+ // CogniFold proactive 注入(block[0] 固定格式,engine-startup 拼的)
19895
20103
  ];
19896
20104
  function parseJsonlEntries(lines) {
19897
20105
  const entries = [];
@@ -19926,6 +20134,20 @@ function resolveScopeMainJsonl(sessions) {
19926
20134
  if (!sessionId) return null;
19927
20135
  return sessions.getSessionFilePath(sessionId);
19928
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
+ }
19929
20151
  function cleanText(rawText) {
19930
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]");
19931
20153
  return clean.trim();
@@ -20016,6 +20238,7 @@ function recentMessages(sessions, hours = 12, limit = 60) {
20016
20238
  time: `${p2(bj.getHours())}:${p2(bj.getMinutes())}`,
20017
20239
  role,
20018
20240
  text: clean.slice(0, 80),
20241
+ timestamp: dtMs,
20019
20242
  _utc: dtMs
20020
20243
  });
20021
20244
  }
@@ -20147,6 +20370,8 @@ ${basePrompt}`;
20147
20370
  channelName: "heartbeat",
20148
20371
  source: "heartbeat",
20149
20372
  priority: "later",
20373
+ skipRecall: true,
20374
+ // 心跳不需要记忆召回,避免重复注入心跳相关记忆
20150
20375
  callbacks: {
20151
20376
  onResult: () => resolveDone()
20152
20377
  },
@@ -20164,7 +20389,7 @@ ${basePrompt}`;
20164
20389
 
20165
20390
  // src/nudge/plugin.ts
20166
20391
  import fs17 from "node:fs";
20167
- import path16 from "node:path";
20392
+ import path17 from "node:path";
20168
20393
 
20169
20394
  // src/nudge/judge.ts
20170
20395
  function shouldNudge(task, taskState, cfg) {
@@ -20333,10 +20558,10 @@ function formatDuration2(ms) {
20333
20558
 
20334
20559
  // src/nudge/session-state-reader.ts
20335
20560
  import fs15 from "node:fs";
20336
- import path14 from "node:path";
20561
+ import path15 from "node:path";
20337
20562
  function parseSessionStateFull(workspace, sessionStateFile) {
20338
20563
  const stateFile = sessionStateFile || "SESSION-STATE.md";
20339
- const statePath = path14.isAbsolute(stateFile) ? stateFile : path14.join(workspace, stateFile);
20564
+ const statePath = path15.isAbsolute(stateFile) ? stateFile : path15.join(workspace, stateFile);
20340
20565
  let content;
20341
20566
  try {
20342
20567
  content = fs15.readFileSync(statePath, "utf-8");
@@ -20391,13 +20616,13 @@ function taskIdFromTitle(title) {
20391
20616
 
20392
20617
  // src/calendar/db.ts
20393
20618
  import { DatabaseSync } from "node:sqlite";
20394
- import * as path15 from "node:path";
20619
+ import * as path16 from "node:path";
20395
20620
  import * as fs16 from "node:fs";
20396
20621
  var TZ_OFFSET_MS = 8 * 60 * 60 * 1e3;
20397
20622
  function openDb(workspace) {
20398
- const dir = path15.join(workspace, ".calendar");
20623
+ const dir = path16.join(workspace, ".calendar");
20399
20624
  fs16.mkdirSync(dir, { recursive: true });
20400
- const dbPath = path15.join(dir, "calendar.db");
20625
+ const dbPath = path16.join(dir, "calendar.db");
20401
20626
  const db = new DatabaseSync(dbPath);
20402
20627
  db.exec("PRAGMA journal_mode=WAL");
20403
20628
  db.exec(`CREATE TABLE IF NOT EXISTS events (
@@ -20486,7 +20711,7 @@ var NudgePlugin = class {
20486
20711
  provider;
20487
20712
  model;
20488
20713
  loadPrompt(workspace, promptFile) {
20489
- 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");
20490
20715
  try {
20491
20716
  const content = fs17.readFileSync(promptPath, "utf-8").trim();
20492
20717
  if (content) {
@@ -20523,11 +20748,20 @@ var NudgePlugin = class {
20523
20748
  const lastMsg = input?.last_assistant_message || "";
20524
20749
  const sessionId = input?.session_id || "";
20525
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
+ }
20526
20755
  const msgChannel = input?.channel || "";
20527
20756
  if (sessionId.includes("voice-chat") || msgChannel === "voice-chat") {
20528
20757
  console.log(`[stop-hook] skipping voice-chat (channel=${msgChannel})`);
20529
20758
  return { outcome: { outcome: "success" } };
20530
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
+ }
20531
20765
  if (!lastMsg) {
20532
20766
  return { outcome: { outcome: "success" } };
20533
20767
  }
@@ -20600,8 +20834,8 @@ var NudgePlugin = class {
20600
20834
  if (pushedDecision && waitDesc) {
20601
20835
  console.log(`[stop-hook] DETECTED pushedDecision! Injecting corrective message to ${sessionId}`);
20602
20836
  try {
20603
- const correctiveMsg = [
20604
- "\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",
20605
20839
  "",
20606
20840
  `\u8BCA\u65AD\uFF1A${waitDesc}`,
20607
20841
  "",
@@ -20614,9 +20848,10 @@ var NudgePlugin = class {
20614
20848
  '3. \u6267\u884C\u5B8C\u6C47\u62A5\u7ED3\u679C\uFF08"\u5DF2\u5904\u7406" / "\u5DF2 commit" / "\u5DF2 archive"\uFF09',
20615
20849
  "",
20616
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"
20617
- ].join("\n");
20618
- if (this.dispatcher) {
20619
- this.dispatcher.submitMessage(correctiveMsg, sessionId);
20851
+ ].join("\n"));
20852
+ const route = this.getRoute(sessions);
20853
+ if (route) {
20854
+ enqueueNotification(correctiveMsg, route);
20620
20855
  }
20621
20856
  } catch (e) {
20622
20857
  console.warn(`[stop-hook] Failed to inject corrective message: ${e.message}`);
@@ -20625,14 +20860,21 @@ var NudgePlugin = class {
20625
20860
  if (!isWaiting) {
20626
20861
  return { outcome: { outcome: "success" } };
20627
20862
  }
20628
- const nudgeDir = path16.join(this.workspace, ".nudge");
20629
- 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");
20630
20865
  try {
20631
20866
  if (!fs17.existsSync(nudgeDir)) fs17.mkdirSync(nudgeDir, { recursive: true });
20632
20867
  let notifs = [];
20633
20868
  if (fs17.existsSync(notifPath)) {
20634
20869
  notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20635
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
+ }
20636
20878
  const recentReg = notifs.find((n) => now - new Date(n.createdAt).getTime() < 3 * 6e4);
20637
20879
  if (recentReg) {
20638
20880
  console.log(`[stop-hook] Skip (recent registration within 3min)`);
@@ -20681,9 +20923,14 @@ var NudgePlugin = class {
20681
20923
  return null;
20682
20924
  }
20683
20925
  }
20684
- /** 检查 stop-hook-notifications:你停止前注册的等待唤起 */
20685
- checkStopHookNotifications() {
20686
- 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");
20687
20934
  try {
20688
20935
  if (!fs17.existsSync(notifPath)) return null;
20689
20936
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
@@ -20691,54 +20938,161 @@ var NudgePlugin = class {
20691
20938
  const now = Date.now();
20692
20939
  const due = notifs.filter((n) => new Date(n.wakeAt).getTime() <= now && !n.notified);
20693
20940
  if (due.length === 0) return null;
20694
- const latest = due[due.length - 1];
20695
- console.log(`[nudge] Stop-hook notification ${latest.id} triggered: ${latest.description.slice(0, 80)}...`);
20696
- const updated = notifs.map((n) => n.id === latest.id ? { ...n, notified: true } : n);
20697
- fs17.writeFileSync(notifPath, JSON.stringify(updated, null, 2));
20698
- 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}
20699
20947
 
20700
- [\u901A\u77E5ID: ${latest.id}]
20701
- \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
20702
20949
 
20703
- \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) };
20704
20954
  } catch (e) {
20705
- console.warn(`[nudge] checkStopHookNotifications error: ${e.message}`);
20955
+ console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
20706
20956
  return null;
20707
20957
  }
20708
20958
  }
20709
- /** 读最近消息,如果发现"<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
+ */
20710
21013
  cleanupStaleNotificationsFromMessages(sessions) {
20711
21014
  try {
20712
- const recent = recentMessages(sessions, 0.5, 6);
20713
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
20714
- const recentTexts = recent.filter((r) => new Date(r.timestamp || r.createdAt || Date.now()).getTime() > fiveMinAgo).map((r) => r.text);
20715
- const notifPath = path16.join(this.workspace, ".nudge", "stop-hook-notifications.json");
21015
+ const notifPath = path17.join(this.workspace, ".nudge", "stop-hook-notifications.json");
20716
21016
  if (!fs17.existsSync(notifPath)) return;
20717
21017
  const notifs = JSON.parse(fs17.readFileSync(notifPath, "utf-8"));
20718
21018
  if (notifs.length === 0) return;
20719
- const expiredIds = /* @__PURE__ */ new Set();
20720
- for (const text of recentTexts) {
20721
- for (const n of notifs) {
20722
- if (text.includes(`${n.id} \u8FC7\u671F\u4E86`) || text.includes(`${n.id}\u8FC7\u671F\u4E86`)) {
20723
- expiredIds.add(n.id);
20724
- }
20725
- }
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);
20726
21032
  }
20727
- if (expiredIds.size === 0) return;
20728
- const remaining = notifs.filter((n) => !expiredIds.has(n.id));
20729
- const cleaned = notifs.length - remaining.length;
20730
- if (cleaned > 0) {
20731
- if (remaining.length > 0) {
20732
- fs17.writeFileSync(notifPath, JSON.stringify(remaining, null, 2));
20733
- } else {
20734
- fs17.unlinkSync(notifPath);
20735
- }
20736
- 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(", ")}`);
20737
21038
  }
20738
21039
  } catch (e) {
20739
21040
  console.warn(`[nudge] cleanupStaleNotificationsFromMessages error: ${e.message}`);
20740
21041
  }
20741
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
+ }
20742
21096
  async tick(sessions, deps) {
20743
21097
  if (this.running) {
20744
21098
  console.log("[nudge] Previous tick still running, skipping");
@@ -20753,7 +21107,7 @@ var NudgePlugin = class {
20753
21107
  const recent = recentMessages(sessions, 0.5, 6);
20754
21108
  const lastUserMsg2 = recent.filter((r) => r.role === "user").slice(-1)[0];
20755
21109
  if (lastUserMsg2) {
20756
- const elapsed = Date.now() - new Date(lastUserMsg2.timestamp || lastUserMsg2.createdAt || Date.now()).getTime();
21110
+ const elapsed = lastUserMsg2.timestamp ? Date.now() - lastUserMsg2.timestamp : 0;
20757
21111
  if (elapsed < activeThresholdMs) {
20758
21112
  console.log(`[nudge] User active ${Math.round(elapsed / 1e3)}s ago (<${activeThresholdMs / 1e3}s), skipping tick`);
20759
21113
  return;
@@ -20765,10 +21119,15 @@ var NudgePlugin = class {
20765
21119
  this.running = true;
20766
21120
  try {
20767
21121
  this.cleanupStaleNotificationsFromMessages(sessions);
20768
- const stopNotifs = this.checkStopHookNotifications();
20769
- if (stopNotifs) {
21122
+ const dueNotifs = this.collectDueStopHookNotifications();
21123
+ if (dueNotifs) {
20770
21124
  const route2 = this.getRoute(sessions);
20771
- 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
+ }
20772
21131
  const state0 = this.loadState();
20773
21132
  state0.lastAnyNudgeAt = (/* @__PURE__ */ new Date()).toISOString();
20774
21133
  this.saveState(state0);
@@ -20954,7 +21313,7 @@ var NudgePlugin = class {
20954
21313
  // === state 持久化 ===
20955
21314
  loadState() {
20956
21315
  const stateFile = this.cfg.stateFile || "nudge-state.json";
20957
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21316
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
20958
21317
  try {
20959
21318
  const content = fs17.readFileSync(statePath, "utf-8");
20960
21319
  return JSON.parse(content);
@@ -20964,7 +21323,7 @@ var NudgePlugin = class {
20964
21323
  }
20965
21324
  saveState(state2) {
20966
21325
  const stateFile = this.cfg.stateFile || "nudge-state.json";
20967
- const statePath = path16.isAbsolute(stateFile) ? stateFile : path16.join(this.workspace, stateFile);
21326
+ const statePath = path17.isAbsolute(stateFile) ? stateFile : path17.join(this.workspace, stateFile);
20968
21327
  fs17.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
20969
21328
  }
20970
21329
  newTaskState() {
@@ -21050,30 +21409,47 @@ var NudgePlugin = class {
21050
21409
  const now = /* @__PURE__ */ new Date();
21051
21410
  const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
21052
21411
  const bj = new Date(now.getTime() + bjOffset);
21053
- const month = bj.getMonth() + 1;
21054
- const day = bj.getDate();
21055
21412
  const bjHour = bj.getHours();
21056
21413
  const bjMinute = bj.getMinutes();
21414
+ const todayStart = new Date(bj.getFullYear(), bj.getMonth(), bj.getDate()).getTime();
21057
21415
  const rows = db.prepare(
21058
- "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND (date_str=? OR date_str LIKE ?)"
21059
- ).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();
21060
21418
  db.close();
21061
- if (rows.length === 0) return null;
21062
- const dueRows = rows.filter((r2) => {
21063
- if (!r2.time_exact) return true;
21064
- const [h, m2] = r2.time_exact.split(":").map(Number);
21065
- if (h < bjHour) return true;
21066
- if (h === bjHour && m2 <= bjMinute) return true;
21067
- 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;
21068
21434
  });
21069
- if (dueRows.length === 0) return null;
21070
- const r = dueRows[0];
21071
- 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();
21072
21437
  } catch (e) {
21073
21438
  console.warn(`[nudge] checkCalendarDue error: ${e.message}`);
21074
21439
  return null;
21075
21440
  }
21076
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
+ }
21077
21453
  /** 检查 carry-over:如果 in_progress task 24h+ 没推进,自动 calendar add-task 排明天 */
21078
21454
  checkCarryOver(task, _sessions) {
21079
21455
  try {
@@ -21127,7 +21503,7 @@ var NudgePlugin = class {
21127
21503
 
21128
21504
  // src/inner-voice/plugin.ts
21129
21505
  import fs21 from "node:fs";
21130
- import path20 from "node:path";
21506
+ import path21 from "node:path";
21131
21507
 
21132
21508
  // src/inner-voice/activity.ts
21133
21509
  function checkActivity(sessions, activeThresholdMs) {
@@ -21167,7 +21543,7 @@ function calcHintProb(min) {
21167
21543
 
21168
21544
  // src/inner-voice/emotional-state.ts
21169
21545
  import fs18 from "node:fs";
21170
- import path17 from "node:path";
21546
+ import path18 from "node:path";
21171
21547
  var NEUTRAL = 0.5;
21172
21548
  var DECAY_RATE = 0.17;
21173
21549
  var MAX_EVENTS = 20;
@@ -21218,7 +21594,7 @@ function initialState() {
21218
21594
  return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowIsoBj(), events: [] };
21219
21595
  }
21220
21596
  async function updateEmotionalState(workspace, sessions) {
21221
- const stateFile = path17.join(workspace, "inner-voice", "emotional-state.json");
21597
+ const stateFile = path18.join(workspace, "inner-voice", "emotional-state.json");
21222
21598
  const messages = readRecentMessages(sessions, RECENT_N);
21223
21599
  if (messages.length === 0) {
21224
21600
  console.log("[emotional-state] no messages");
@@ -21251,7 +21627,7 @@ async function updateEmotionalState(workspace, sessions) {
21251
21627
  function readRecentMessages(sessions, n) {
21252
21628
  const mainId = sessions.getSessionId("scope:main");
21253
21629
  if (!mainId) return [];
21254
- const file = path17.join(sessions.sessionsDir, `${mainId}.jsonl`);
21630
+ const file = path18.join(sessions.sessionsDir, `${mainId}.jsonl`);
21255
21631
  if (!fs18.existsSync(file)) return [];
21256
21632
  const lines = readLastNLines(file, n * 4 + 20);
21257
21633
  const entries = [];
@@ -21369,7 +21745,7 @@ function refreshHoursAgo(events) {
21369
21745
  }
21370
21746
  function appendMoodLog(workspace, state2, summary) {
21371
21747
  try {
21372
- const logPath = path17.join(workspace, "mood-history.log");
21748
+ const logPath = path18.join(workspace, "mood-history.log");
21373
21749
  const ts = formatBj(/* @__PURE__ */ new Date(), false);
21374
21750
  fs18.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
21375
21751
  `);
@@ -21386,7 +21762,7 @@ function loadJson(file) {
21386
21762
  }
21387
21763
  function saveJson(file, data) {
21388
21764
  try {
21389
- fs18.mkdirSync(path17.dirname(file), { recursive: true });
21765
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
21390
21766
  fs18.writeFileSync(file, JSON.stringify(data, null, 2));
21391
21767
  } catch (err) {
21392
21768
  console.warn(`[emotional-state] save failed: ${err.message}`);
@@ -21432,7 +21808,7 @@ function formatBj(d, withSec) {
21432
21808
 
21433
21809
  // src/inner-voice/topics-scorer.ts
21434
21810
  import fs19 from "node:fs";
21435
- import path18 from "node:path";
21811
+ import path19 from "node:path";
21436
21812
  var HALF_LIFE_DAYS = 3;
21437
21813
  var PROJECT_HALF_LIFE_DAYS = 1.5;
21438
21814
  var COOLDOWN_HOURS = 6;
@@ -21440,8 +21816,8 @@ var MAX_CHARS = 8e3;
21440
21816
  var SKIP_NAMES = /* @__PURE__ */ new Set(["MEMORY.md", "archive"]);
21441
21817
  var SKIP_DIRS = /* @__PURE__ */ new Set(["archive"]);
21442
21818
  function pickTopic(workspace, typeFilter, opts) {
21443
- const topicsDir = path18.join(workspace, "topics");
21444
- 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");
21445
21821
  const files = scanTopics(topicsDir, typeFilter);
21446
21822
  if (files.length === 0) {
21447
21823
  console.log(`[topics-scorer] no topics found (type=${typeFilter})`);
@@ -21473,7 +21849,7 @@ function pickTopic(workspace, typeFilter, opts) {
21473
21849
  recency: Math.round(recency * 1e3) / 1e3,
21474
21850
  freq: Math.round(freq * 1e3) / 1e3,
21475
21851
  type: type2,
21476
- name: meta.name || path18.basename(relpath),
21852
+ name: meta.name || path19.basename(relpath),
21477
21853
  description: meta.description || "",
21478
21854
  mtime
21479
21855
  });
@@ -21531,14 +21907,14 @@ function scanTopics(topicsDir, typeFilter) {
21531
21907
  const out = [];
21532
21908
  const walk = (dir) => {
21533
21909
  for (const name of fs19.readdirSync(dir)) {
21534
- const full = path18.join(dir, name);
21910
+ const full = path19.join(dir, name);
21535
21911
  const stat4 = fs19.statSync(full);
21536
21912
  if (stat4.isDirectory()) {
21537
21913
  if (SKIP_DIRS.has(name)) continue;
21538
21914
  walk(full);
21539
21915
  } else {
21540
21916
  if (!name.endsWith(".md") || SKIP_NAMES.has(name)) continue;
21541
- const relpath = path18.relative(topicsDir, full).replace(/\\/g, "/");
21917
+ const relpath = path19.relative(topicsDir, full).replace(/\\/g, "/");
21542
21918
  if (typeFilter && !relpath.startsWith(typeFilter + "/") && !relpath.startsWith(typeFilter + "_")) continue;
21543
21919
  out.push({ relpath, fullpath: full });
21544
21920
  }
@@ -21583,7 +21959,7 @@ function loadJson2(file) {
21583
21959
  }
21584
21960
  function saveJson2(file, data) {
21585
21961
  try {
21586
- fs19.mkdirSync(path18.dirname(file), { recursive: true });
21962
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
21587
21963
  fs19.writeFileSync(file, JSON.stringify(data, null, 2));
21588
21964
  } catch (err) {
21589
21965
  console.warn(`[topics-scorer] usage save failed: ${err.message}`);
@@ -21592,21 +21968,21 @@ function saveJson2(file, data) {
21592
21968
 
21593
21969
  // src/inner-voice/memory-reader.ts
21594
21970
  import fs20 from "node:fs";
21595
- import path19 from "node:path";
21971
+ import path20 from "node:path";
21596
21972
  var US_HALF_LIFE_DAYS = 10;
21597
21973
  var US_MAX_LINES = 60;
21598
21974
  function readRecentMemory(workspace) {
21599
- const dir = path19.join(workspace, "memory");
21975
+ const dir = path20.join(workspace, "memory");
21600
21976
  const now = new Date(Date.now() + 8 * 36e5);
21601
21977
  const today = formatYmd(now);
21602
21978
  const yesterday = formatYmd(new Date(now.getTime() - 864e5));
21603
21979
  return {
21604
- today: readIfExists(path19.join(dir, `${today}.md`)),
21605
- yesterday: readIfExists(path19.join(dir, `${yesterday}.md`))
21980
+ today: readIfExists(path20.join(dir, `${today}.md`)),
21981
+ yesterday: readIfExists(path20.join(dir, `${yesterday}.md`))
21606
21982
  };
21607
21983
  }
21608
21984
  function sampleUs(workspace) {
21609
- const usFile = path19.join(workspace, "memory", "us.md");
21985
+ const usFile = path20.join(workspace, "memory", "us.md");
21610
21986
  let content;
21611
21987
  try {
21612
21988
  content = fs20.readFileSync(usFile, "utf-8");
@@ -21946,7 +22322,7 @@ var InnerVoicePlugin = class {
21946
22322
  }
21947
22323
  /** 读 workspace/prompts/my-inner-voice.md,不存在用 DEFAULT_PROMPT */
21948
22324
  loadPrompt(workspace) {
21949
- const promptPath = path20.join(workspace, "prompts", "my-inner-voice.md");
22325
+ const promptPath = path21.join(workspace, "prompts", "my-inner-voice.md");
21950
22326
  try {
21951
22327
  const content = fs21.readFileSync(promptPath, "utf-8").trim();
21952
22328
  if (content) {
@@ -22020,7 +22396,7 @@ var InnerVoicePlugin = class {
22020
22396
  console.warn(`[inner-voice] emotional-state failed: ${err.message}`);
22021
22397
  }
22022
22398
  try {
22023
- 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");
22024
22400
  lines.push("\n--- SESSION-STATE\uFF08\u5C3E\u90E8\uFF09 ---");
22025
22401
  lines.push(content.slice(-2e3));
22026
22402
  } catch {
@@ -22131,7 +22507,7 @@ var InnerVoicePlugin = class {
22131
22507
  if (Math.random() >= activity.hintProb) {
22132
22508
  return { text: thought, hintTriggered: false, hintText: "" };
22133
22509
  }
22134
- const poolPath = path20.join(this.workspace, "inner-voice", "hints_pool.txt");
22510
+ const poolPath = path21.join(this.workspace, "inner-voice", "hints_pool.txt");
22135
22511
  let hint = "\u60F3\u4ED6\u5C31\u53D1\u6D88\u606F\u5427";
22136
22512
  try {
22137
22513
  const pool = fs21.readFileSync(poolPath, "utf-8").split("\n").map((s2) => s2.trim()).filter(Boolean);
@@ -22159,7 +22535,7 @@ var InnerVoicePlugin = class {
22159
22535
  try {
22160
22536
  const writer = sessions.getWriter(mainSessionId);
22161
22537
  const history = sessions.getHistory(mainSessionId);
22162
- const fullPath = path20.resolve(this.workspace, emoTopic.file);
22538
+ const fullPath = path21.resolve(this.workspace, emoTopic.file);
22163
22539
  const memories = [{
22164
22540
  path: fullPath,
22165
22541
  content: emoTopic.content,
@@ -22187,9 +22563,9 @@ var InnerVoicePlugin = class {
22187
22563
  /** 写 xiaoyi.log(格式对齐旧 memory_whisper.py,便于既有日志分析复用)。 */
22188
22564
  writeLog(status, delivered, activity, hintTriggered, hintText) {
22189
22565
  try {
22190
- const logDir = path20.join(this.workspace, "inner-voice");
22566
+ const logDir = path21.join(this.workspace, "inner-voice");
22191
22567
  fs21.mkdirSync(logDir, { recursive: true });
22192
- const logPath = path20.join(logDir, "xiaoyi.log");
22568
+ const logPath = path21.join(logDir, "xiaoyi.log");
22193
22569
  const ts = formatBeijingTs(/* @__PURE__ */ new Date());
22194
22570
  const hintStatus = hintTriggered ? `YES (${(hintText || "").trim()})` : "no";
22195
22571
  fs21.appendFileSync(
@@ -22728,7 +23104,7 @@ var PluginManager = class {
22728
23104
  // src/voice-chat/plugin.ts
22729
23105
  import { spawn as spawn4, exec } from "node:child_process";
22730
23106
  import net from "node:net";
22731
- import path21 from "node:path";
23107
+ import path22 from "node:path";
22732
23108
  import fs22 from "node:fs";
22733
23109
 
22734
23110
  // src/voice-chat/bridge.ts
@@ -23105,13 +23481,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23105
23481
  }
23106
23482
  getPythonDir() {
23107
23483
  const dir = import.meta.dirname;
23108
- const srcDir = path21.resolve(dir, "..", "src", "voice-chat", "python");
23109
- const localDir = path21.join(dir, "python");
23484
+ const srcDir = path22.resolve(dir, "..", "src", "voice-chat", "python");
23485
+ const localDir = path22.join(dir, "python");
23110
23486
  return fs22.existsSync(srcDir) ? srcDir : localDir;
23111
23487
  }
23112
23488
  startPython() {
23113
23489
  const pythonDir = this.getPythonDir();
23114
- const serverPy = path21.join(pythonDir, "server.py");
23490
+ const serverPy = path22.join(pythonDir, "server.py");
23115
23491
  const pythonBin = this.findPython();
23116
23492
  const args = [serverPy];
23117
23493
  if (this.config.pythonPort) args.push("--port", String(this.config.pythonPort));
@@ -23196,7 +23572,7 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
23196
23572
  init_BashTool();
23197
23573
  import { spawn as spawn5, exec as exec2 } from "node:child_process";
23198
23574
  import net2 from "node:net";
23199
- import path22 from "node:path";
23575
+ import path23 from "node:path";
23200
23576
  import fs23 from "node:fs";
23201
23577
 
23202
23578
  // src/memory/cognifold/config.ts
@@ -23220,7 +23596,8 @@ function parseCognifoldConfig(raw) {
23220
23596
  persistDir: raw.persistDir ?? DEFAULTS3.persistDir,
23221
23597
  scopes: raw.scopes,
23222
23598
  readyTimeoutMs: raw.readyTimeoutMs ?? DEFAULTS3.readyTimeoutMs,
23223
- maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts
23599
+ maxRestarts: raw.maxRestarts ?? DEFAULTS3.maxRestarts,
23600
+ llm: raw.llm
23224
23601
  };
23225
23602
  }
23226
23603
 
@@ -23228,15 +23605,17 @@ function parseCognifoldConfig(raw) {
23228
23605
  var CogniFoldClient = class {
23229
23606
  baseUrl;
23230
23607
  timeoutMs;
23231
- constructor(baseUrl, timeoutMs = 3e4) {
23608
+ modelName;
23609
+ constructor(baseUrl, timeoutMs = 3e4, modelName = "openai:MiniMax-M3") {
23232
23610
  this.baseUrl = baseUrl.replace(/\/$/, "");
23233
23611
  this.timeoutMs = timeoutMs;
23612
+ this.modelName = modelName;
23234
23613
  }
23235
- async req(path43, options = {}) {
23614
+ async req(path44, options = {}) {
23236
23615
  const ctrl = new AbortController();
23237
23616
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23238
23617
  try {
23239
- const resp = await fetch(`${this.baseUrl}${path43}`, {
23618
+ const resp = await fetch(`${this.baseUrl}${path44}`, {
23240
23619
  ...options,
23241
23620
  signal: ctrl.signal,
23242
23621
  headers: {
@@ -23274,7 +23653,7 @@ var CogniFoldClient = class {
23274
23653
  method: "POST",
23275
23654
  body: JSON.stringify({
23276
23655
  user_id: userId,
23277
- config: { model_name: "openai:deepseek-v4-flash" }
23656
+ config: { model_name: this.modelName }
23278
23657
  })
23279
23658
  });
23280
23659
  }
@@ -23326,8 +23705,8 @@ var CogniFoldClient = class {
23326
23705
  });
23327
23706
  }
23328
23707
  /** 兼容老版命名 */
23329
- async recl(path43, options = {}) {
23330
- return this.req(path43, options);
23708
+ async recl(path44, options = {}) {
23709
+ return this.req(path44, options);
23331
23710
  }
23332
23711
  };
23333
23712
 
@@ -23432,7 +23811,8 @@ var CogniFoldPlugin = class {
23432
23811
  baseUrl = baseUrl.replace(/\/$/, "") + "/api/v1";
23433
23812
  }
23434
23813
  this.config.baseUrl = baseUrl;
23435
- 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);
23436
23816
  }
23437
23817
  workspacePath;
23438
23818
  name = "cognifold";
@@ -23607,16 +23987,16 @@ var CogniFoldPlugin = class {
23607
23987
  const dir = import.meta.dirname;
23608
23988
  const candidates = [
23609
23989
  // 从 dist/ 往回找 src
23610
- path22.resolve(dir, "..", "src", "memory", "cognifold", "python"),
23611
- path22.resolve(dir, "..", "..", "src", "memory", "cognifold", "python"),
23612
- 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"),
23613
23993
  // 从 src/memory/cognifold/ 找本地
23614
- path22.join(dir, "python"),
23994
+ path23.join(dir, "python"),
23615
23995
  // 从 dist/memory/cognifold/ 找本地
23616
- path22.resolve(dir, "python")
23996
+ path23.resolve(dir, "python")
23617
23997
  ];
23618
23998
  for (const candidate of candidates) {
23619
- if (fs23.existsSync(path22.join(candidate, "cognifold"))) {
23999
+ if (fs23.existsSync(path23.join(candidate, "cognifold"))) {
23620
24000
  return candidate;
23621
24001
  }
23622
24002
  }
@@ -23642,12 +24022,18 @@ var CogniFoldPlugin = class {
23642
24022
  const pythonBin = this.findPython();
23643
24023
  console.log(`[cognifold] Starting Python: ${pythonBin} ${args.join(" ")}`);
23644
24024
  console.log(`[cognifold] Python dir: ${pythonDir}`);
23645
- if (!fs23.existsSync(path22.join(pythonDir, "cognifold"))) {
24025
+ if (!fs23.existsSync(path23.join(pythonDir, "cognifold"))) {
23646
24026
  console.error(`[cognifold] FATAL: Python module not found at ${pythonDir}/cognifold`);
23647
24027
  throw new Error(`cognifold: python module not found`);
23648
24028
  }
23649
24029
  const childEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
23650
- 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");
23651
24037
  try {
23652
24038
  if (fs23.existsSync(envFile)) {
23653
24039
  const envContent = fs23.readFileSync(envFile, "utf-8");
@@ -23719,7 +24105,7 @@ var CogniFoldPlugin = class {
23719
24105
  init_BashTool();
23720
24106
  import { spawn as spawn6 } from "node:child_process";
23721
24107
  import net3 from "node:net";
23722
- import path23 from "node:path";
24108
+ import path24 from "node:path";
23723
24109
  import fs24 from "node:fs";
23724
24110
 
23725
24111
  // src/memory/everos/config.ts
@@ -23753,7 +24139,8 @@ function parseEverosConfig(raw) {
23753
24139
  llm: raw.llm ?? { model: "glm-5.2", apiKey: "", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
23754
24140
  rerank: raw.rerank ?? { model: "Qwen/Qwen3-Reranker-4B", apiKey: "", baseUrl: "https://api.deepinfra.com/v1/inference" },
23755
24141
  lancedbPath: raw.lancedbPath ?? "",
23756
- sqlitePath: raw.sqlitePath ?? ""
24142
+ sqlitePath: raw.sqlitePath ?? "",
24143
+ minScore: raw.minScore
23757
24144
  };
23758
24145
  }
23759
24146
 
@@ -23791,21 +24178,31 @@ var EverosSearchClient = class {
23791
24178
  clearTimeout(timer);
23792
24179
  }
23793
24180
  }
23794
- /** Search — 3-mode unified endpoint */
24181
+ /** Search — routes to 8101 (agentic) or 8100 (hybrid) based on mode */
23795
24182
  async search(params) {
23796
24183
  const ctrl = new AbortController();
23797
24184
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
23798
24185
  try {
23799
- 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, {
23800
24203
  method: "POST",
23801
24204
  headers: { "Content-Type": "application/json" },
23802
- body: JSON.stringify({
23803
- query: params.query,
23804
- user_id: params.userId || "xiaomei",
23805
- mode: params.mode || "hybrid_agentic",
23806
- top_k: params.topK ?? 5,
23807
- strategy: params.strategy || "multi_query"
23808
- }),
24205
+ body,
23809
24206
  signal: ctrl.signal
23810
24207
  });
23811
24208
  if (!resp.ok) {
@@ -23845,6 +24242,7 @@ var EverosPlugin = class {
23845
24242
  }
23846
24243
  async start(ctx) {
23847
24244
  if (!this.config.enabled) return;
24245
+ await this.ensureVenv();
23848
24246
  try {
23849
24247
  await this.client.healthEveros();
23850
24248
  console.log(`[everos] EverOS already running at ${this.config.everosUrl}`);
@@ -23915,13 +24313,15 @@ var EverosPlugin = class {
23915
24313
  }, 3e5);
23916
24314
  }
23917
24315
  async startEveros() {
23918
- const pythonDir = path23.dirname(this.config.lancedbPath);
23919
- const configPath = path23.join(pythonDir, "config.toml");
24316
+ const pythonDir = path24.dirname(this.config.lancedbPath);
24317
+ const configPath = path24.join(pythonDir, "config.toml");
23920
24318
  await this.ensureFcntlCompat();
23921
24319
  const venvPython = this.findVenvPython();
24320
+ const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
23922
24321
  const args = ["server", "start"];
23923
- const cmd = `${venvPython} ${args.join(" ")}`;
24322
+ const cmd = `${everosBin} ${args.join(" ")}`;
23924
24323
  console.log(`[everos] Starting EverOS: ${cmd}`);
24324
+ console.log(`[everos] LLM config: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
23925
24325
  if (process.platform === "win32") {
23926
24326
  const { shell, args: shellArgs } = findShell();
23927
24327
  spawn6(shell, [...shellArgs, cmd], {
@@ -23936,7 +24336,7 @@ var EverosPlugin = class {
23936
24336
  env: { ...process.env, PYTHONUNBUFFERED: "1" }
23937
24337
  });
23938
24338
  }
23939
- await this.waitForReady(`${this.config.everosUrl}/health`, 3e4);
24339
+ await this.waitForReady(`${this.config.everosUrl}/health`, 6e4);
23940
24340
  }
23941
24341
  startAgenticServer() {
23942
24342
  const pythonDir = this.getPythonDir();
@@ -23946,6 +24346,7 @@ var EverosPlugin = class {
23946
24346
  const cmd = `${venvPython} ${args.join(" ")}`;
23947
24347
  console.log(`[everos] Starting agentic server: ${cmd}`);
23948
24348
  console.log(`[everos] Python dir: ${pythonDir}`);
24349
+ console.log(`[everos] LLM: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
23949
24350
  const childEnv = {
23950
24351
  ...process.env,
23951
24352
  PYTHONUNBUFFERED: "1",
@@ -23954,15 +24355,25 @@ var EverosPlugin = class {
23954
24355
  LLM_API_KEY: this.config.llm.apiKey,
23955
24356
  LLM_BASE_URL: this.config.llm.baseUrl,
23956
24357
  RERANK_API_KEY: this.config.rerank.apiKey,
23957
- RERANK_URL: `${this.config.rerank.baseUrl}/${this.config.rerank.model}`,
24358
+ RERANK_URL: this.config.rerank.baseUrl,
23958
24359
  LANCEDB_PATH: this.config.lancedbPath,
23959
24360
  SQLITE_PATH: this.config.sqlitePath,
23960
24361
  EVEROS_USER_ID: this.config.userId
23961
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}`);
23962
24374
  let child;
23963
24375
  if (process.platform === "win32") {
23964
- const { shell, args: shellArgs } = findShell();
23965
- child = spawn6(shell, [...shellArgs, cmd], {
24376
+ child = spawn6(venvPython, args, {
23966
24377
  cwd: pythonDir,
23967
24378
  stdio: ["ignore", "pipe", "pipe"],
23968
24379
  env: childEnv
@@ -23992,21 +24403,64 @@ var EverosPlugin = class {
23992
24403
  return child;
23993
24404
  }
23994
24405
  findVenvPython() {
23995
- 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");
23996
24407
  if (process.platform === "win32") {
23997
- 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`);
23998
24453
  }
23999
- return path23.join(stateDir, "everos-venv", "bin", "python");
24000
24454
  }
24001
24455
  getPythonDir() {
24002
24456
  const dir = import.meta.dirname;
24003
24457
  const candidates = [
24004
- path23.join(dir, "python"),
24005
- path23.resolve(dir, "..", "src", "memory", "everos", "python"),
24006
- 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")
24007
24461
  ];
24008
24462
  for (const candidate of candidates) {
24009
- if (fs24.existsSync(path23.join(candidate, "agentic_server.py"))) {
24463
+ if (fs24.existsSync(path24.join(candidate, "agentic_server.py"))) {
24010
24464
  return candidate;
24011
24465
  }
24012
24466
  }
@@ -24015,11 +24469,11 @@ var EverosPlugin = class {
24015
24469
  async ensureFcntlCompat() {
24016
24470
  if (process.platform !== "win32") return;
24017
24471
  const venvPython = this.findVenvPython();
24018
- const venvDir = path23.dirname(path23.dirname(venvPython));
24019
- const sitePackages = path23.join(venvDir, "Lib", "site-packages");
24020
- 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");
24021
24475
  if (fs24.existsSync(target)) return;
24022
- const source = path23.join(this.getPythonDir(), "fcntl_compat.py");
24476
+ const source = path24.join(this.getPythonDir(), "fcntl_compat.py");
24023
24477
  if (fs24.existsSync(source)) {
24024
24478
  try {
24025
24479
  fs24.copyFileSync(source, target);
@@ -24069,7 +24523,7 @@ var EverosPlugin = class {
24069
24523
  init_task_manager();
24070
24524
 
24071
24525
  // src/skills/scanner.ts
24072
- import * as path24 from "node:path";
24526
+ import * as path25 from "node:path";
24073
24527
  import * as fs25 from "node:fs";
24074
24528
  function scanSkills(skillsDir) {
24075
24529
  if (!fs25.existsSync(skillsDir)) {
@@ -24080,7 +24534,7 @@ function scanSkills(skillsDir) {
24080
24534
  const skills = [];
24081
24535
  for (const entry of entries) {
24082
24536
  if (!entry.isDirectory()) continue;
24083
- const skillMdPath = path24.join(skillsDir, entry.name, "SKILL.md");
24537
+ const skillMdPath = path25.join(skillsDir, entry.name, "SKILL.md");
24084
24538
  if (!fs25.existsSync(skillMdPath)) continue;
24085
24539
  try {
24086
24540
  const content = fs25.readFileSync(skillMdPath, "utf-8");
@@ -24148,7 +24602,7 @@ function parseFrontmatter2(content) {
24148
24602
  // src/tools/SkillTool/SkillTool.ts
24149
24603
  init_registry();
24150
24604
  import * as fs26 from "node:fs";
24151
- import * as path25 from "node:path";
24605
+ import * as path26 from "node:path";
24152
24606
 
24153
24607
  // src/tools/SkillTool/constants.ts
24154
24608
  var SKILL_TOOL_NAME2 = "Skill";
@@ -24225,12 +24679,12 @@ Important:
24225
24679
  `;
24226
24680
  }
24227
24681
  function loadSkillContent(skillName) {
24228
- const skillMdPath = path25.join(skillsDirPath, skillName, "SKILL.md");
24682
+ const skillMdPath = path26.join(skillsDirPath, skillName, "SKILL.md");
24229
24683
  if (!fs26.existsSync(skillMdPath)) return null;
24230
24684
  const content = fs26.readFileSync(skillMdPath, "utf-8");
24231
24685
  const bodyMatch = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/);
24232
24686
  const body = bodyMatch ? bodyMatch[1] : content;
24233
- const skillDir = path25.dirname(skillMdPath);
24687
+ const skillDir = path26.dirname(skillMdPath);
24234
24688
  const normalizedDir = process.platform === "win32" ? skillDir.replace(/\\/g, "/") : skillDir;
24235
24689
  let finalContent = `Base directory for this skill: ${normalizedDir}
24236
24690
 
@@ -24505,9 +24959,9 @@ Examples:
24505
24959
  init_registry();
24506
24960
  init_live();
24507
24961
  import fs27 from "node:fs";
24508
- import path26 from "node:path";
24962
+ import path27 from "node:path";
24509
24963
  function getHusbandFeishuId(workspace) {
24510
- const contactsPath = path26.join(workspace, "prompts", "contacts.md");
24964
+ const contactsPath = path27.join(workspace, "prompts", "contacts.md");
24511
24965
  try {
24512
24966
  const text = fs27.readFileSync(contactsPath, "utf-8");
24513
24967
  const m2 = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
@@ -24710,7 +25164,7 @@ Examples:
24710
25164
  init_live();
24711
25165
  init_registry();
24712
25166
  import * as fs28 from "node:fs";
24713
- import * as path27 from "node:path";
25167
+ import * as path28 from "node:path";
24714
25168
  var MIME_MAP = {
24715
25169
  ".jpg": "jpeg",
24716
25170
  ".jpeg": "jpeg",
@@ -24722,7 +25176,7 @@ var MIME_MAP = {
24722
25176
  function resolveLatestImage(specifiedPath, mediaDir) {
24723
25177
  if (specifiedPath && fs28.existsSync(specifiedPath)) return specifiedPath;
24724
25178
  if (!fs28.existsSync(mediaDir)) return null;
24725
- 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);
24726
25180
  return files[0]?.p || null;
24727
25181
  }
24728
25182
  registry.register({
@@ -24746,13 +25200,13 @@ registry.register({
24746
25200
  if (!provider?.streamChat) {
24747
25201
  return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
24748
25202
  }
24749
- const mediaDir = path27.join(ctx.stateDir, "media", "inbound");
25203
+ const mediaDir = path28.join(ctx.stateDir, "media", "inbound");
24750
25204
  const imagePath = resolveLatestImage(args.image_path, mediaDir);
24751
25205
  if (!imagePath) {
24752
25206
  return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
24753
25207
  }
24754
25208
  const rawPrompt = args.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
24755
- const ext = path27.extname(imagePath).toLowerCase();
25209
+ const ext = path28.extname(imagePath).toLowerCase();
24756
25210
  const mime = MIME_MAP[ext] || "jpeg";
24757
25211
  const imgB64 = fs28.readFileSync(imagePath).toString("base64");
24758
25212
  const userMsg = {
@@ -24792,13 +25246,13 @@ init_registry();
24792
25246
  import { execFile } from "node:child_process";
24793
25247
  import { promisify } from "node:util";
24794
25248
  import * as fs29 from "node:fs";
24795
- import * as path28 from "node:path";
25249
+ import * as path29 from "node:path";
24796
25250
  import * as os3 from "node:os";
24797
25251
  var execFileAsync = promisify(execFile);
24798
- var VOICE_DIR = path28.join(os3.tmpdir(), "engine-voice");
25252
+ var VOICE_DIR = path29.join(os3.tmpdir(), "engine-voice");
24799
25253
  async function ttsCosyvoice(text, apiKey, model, voice, workspaceId) {
24800
25254
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24801
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25255
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
24802
25256
  const script = `
24803
25257
  import sys, json, wave, time, threading
24804
25258
  import dashscope
@@ -24863,7 +25317,7 @@ var GPTSOVITS_REF_TEXT = "\u6625\u7720\u4E0D\u89C9\u6653\uFF0C\u5904\u5904\u95FB
24863
25317
  var GPTSOVITS_REF_LANG = "zh";
24864
25318
  async function ttsGptsovits(text) {
24865
25319
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24866
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.wav`);
25320
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.wav`);
24867
25321
  const params = new URLSearchParams({
24868
25322
  text,
24869
25323
  text_language: "zh",
@@ -24880,7 +25334,7 @@ async function ttsGptsovits(text) {
24880
25334
  var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
24881
25335
  async function ttsEdge(text) {
24882
25336
  fs29.mkdirSync(VOICE_DIR, { recursive: true });
24883
- const output = path28.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
25337
+ const output = path29.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
24884
25338
  const script = `
24885
25339
  import asyncio, edge_tts, sys
24886
25340
  async def main():
@@ -24973,7 +25427,7 @@ registry.register({
24973
25427
  } catch (e) {
24974
25428
  return { content: `TTS failed: ${e.message}`, isError: true };
24975
25429
  }
24976
- const ext = path28.extname(audioPath).toLowerCase();
25430
+ const ext = path29.extname(audioPath).toLowerCase();
24977
25431
  const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg" };
24978
25432
  const mimeType = mimeMap[ext] || "audio/mpeg";
24979
25433
  const sizeKB = fs29.statSync(audioPath).size / 1024;
@@ -25004,7 +25458,7 @@ registry.register({
25004
25458
  init_live();
25005
25459
  init_registry();
25006
25460
  import * as fs30 from "node:fs";
25007
- import * as path29 from "node:path";
25461
+ import * as path30 from "node:path";
25008
25462
  var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
25009
25463
  var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
25010
25464
  var DEFAULT_RESOLUTION = "1k";
@@ -25120,7 +25574,7 @@ registry.register({
25120
25574
  const REFERENCES = getReferences(ctx);
25121
25575
  const refName = args.reference || "default";
25122
25576
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
25123
- const refPath = path29.join(ctx.workspace, refEntry.p);
25577
+ const refPath = path30.join(ctx.workspace, refEntry.p);
25124
25578
  if (!fs30.existsSync(refPath)) {
25125
25579
  return { content: `Error: reference image not found at ${refPath}`, isError: true };
25126
25580
  }
@@ -25139,10 +25593,10 @@ registry.register({
25139
25593
  } catch (err) {
25140
25594
  return { content: `Selfie generation failed: ${err.message}`, isError: true };
25141
25595
  }
25142
- const imagesDir = path29.join(ctx.workspace, "images");
25596
+ const imagesDir = path30.join(ctx.workspace, "images");
25143
25597
  if (!fs30.existsSync(imagesDir)) fs30.mkdirSync(imagesDir, { recursive: true });
25144
25598
  const filename = `selfie_${Date.now()}.jpg`;
25145
- const outputPath = path29.join(imagesDir, filename);
25599
+ const outputPath = path30.join(imagesDir, filename);
25146
25600
  fs30.writeFileSync(outputPath, imageBuffer);
25147
25601
  const mgr = ctx.channelManager;
25148
25602
  if (mgr) {
@@ -25154,11 +25608,11 @@ registry.register({
25154
25608
  mimeType: "image/jpeg"
25155
25609
  });
25156
25610
  } catch (err) {
25157
- 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 };
25158
25612
  }
25159
25613
  return { content: `Selfie sent! Mode: ${mode}, Provider: ${getProvider(ctx)}, Ref: ${refEntry.name}` };
25160
25614
  }
25161
- 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)}` };
25162
25616
  },
25163
25617
  isConcurrencySafe: () => false,
25164
25618
  interruptBehavior: () => "block",
@@ -25625,14 +26079,14 @@ init_planModeState();
25625
26079
 
25626
26080
  // src/utils/plans.ts
25627
26081
  import * as fs32 from "node:fs";
25628
- import * as path31 from "node:path";
26082
+ import * as path32 from "node:path";
25629
26083
  import * as crypto4 from "node:crypto";
25630
26084
  var MAX_SLUG_RETRIES = 10;
25631
26085
  function generateSlug() {
25632
26086
  return crypto4.randomBytes(4).toString("hex");
25633
26087
  }
25634
26088
  function getPlansDirectory(stateDir) {
25635
- const plansDir = path31.join(stateDir, "plans");
26089
+ const plansDir = path32.join(stateDir, "plans");
25636
26090
  fs32.mkdirSync(plansDir, { recursive: true });
25637
26091
  return plansDir;
25638
26092
  }
@@ -25643,7 +26097,7 @@ function getPlanSlug(sessionId, stateDir) {
25643
26097
  const plansDir = getPlansDirectory(stateDir);
25644
26098
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
25645
26099
  slug = generateSlug();
25646
- const filePath = path31.join(plansDir, `${slug}.md`);
26100
+ const filePath = path32.join(plansDir, `${slug}.md`);
25647
26101
  if (!fs32.existsSync(filePath)) {
25648
26102
  break;
25649
26103
  }
@@ -25655,9 +26109,9 @@ function getPlanSlug(sessionId, stateDir) {
25655
26109
  function getPlanFilePath(sessionId, stateDir, agentId) {
25656
26110
  const slug = getPlanSlug(sessionId, stateDir);
25657
26111
  if (!agentId) {
25658
- return path31.join(getPlansDirectory(stateDir), `${slug}.md`);
26112
+ return path32.join(getPlansDirectory(stateDir), `${slug}.md`);
25659
26113
  }
25660
- return path31.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
26114
+ return path32.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
25661
26115
  }
25662
26116
  function getPlan(sessionId, stateDir, agentId) {
25663
26117
  const filePath = getPlanFilePath(sessionId, stateDir, agentId);
@@ -26508,7 +26962,7 @@ async function setupFeatures(features, licensedFeatures) {
26508
26962
  // src/license/license.ts
26509
26963
  import * as crypto6 from "node:crypto";
26510
26964
  import * as fs39 from "node:fs";
26511
- import * as path39 from "node:path";
26965
+ import * as path40 from "node:path";
26512
26966
  var EMBEDDED_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
26513
26967
  MCowBQYDK2VwAyEAaKBEX+e8+D59qwtidazsu7WYDglApyvsVI3APwFoakA=
26514
26968
  -----END PUBLIC KEY-----`;
@@ -26539,7 +26993,7 @@ function loadLicense(stateDir, devMode) {
26539
26993
  _cachedLicense = allActive;
26540
26994
  return allActive;
26541
26995
  }
26542
- const licensePath = path39.join(stateDir, "license.json");
26996
+ const licensePath = path40.join(stateDir, "license.json");
26543
26997
  if (!fs39.existsSync(licensePath)) {
26544
26998
  console.log("[license] No license.json found, running basic engine only");
26545
26999
  return null;
@@ -26608,14 +27062,14 @@ var EverosSearchSchema = {
26608
27062
  type: "object",
26609
27063
  properties: {
26610
27064
  query: { type: "string", description: "\u641C\u7D22\u67E5\u8BE2" },
26611
- 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)" }
26612
27067
  },
26613
27068
  required: ["query"]
26614
27069
  };
26615
27070
  function createEverosSearchTool(everosCfg) {
26616
27071
  const agenticUrl = (everosCfg?.agenticUrl || "http://127.0.0.1:8101").replace(/\/$/, "");
26617
27072
  const userId = everosCfg?.userId || "xiaomei";
26618
- const defaultMode = everosCfg?.defaultMode || "hybrid_agentic";
26619
27073
  return {
26620
27074
  name: "memory_search",
26621
27075
  description: "Mandatory recall step: semantically search memory before answering questions about prior work, decisions, dates, people, preferences, or todos.",
@@ -26624,6 +27078,7 @@ function createEverosSearchTool(everosCfg) {
26624
27078
  const query = args.query;
26625
27079
  if (!query) return { content: "\u7F3A\u5C11 query \u53C2\u6570" };
26626
27080
  const topK = args.maxResults ?? 10;
27081
+ const mode = args.mode || "hybrid_agentic";
26627
27082
  const ctrl = new AbortController();
26628
27083
  const timer = setTimeout(() => ctrl.abort(), 3e4);
26629
27084
  try {
@@ -26633,7 +27088,7 @@ function createEverosSearchTool(everosCfg) {
26633
27088
  body: JSON.stringify({
26634
27089
  query,
26635
27090
  user_id: userId,
26636
- mode: defaultMode,
27091
+ mode,
26637
27092
  top_k: topK
26638
27093
  }),
26639
27094
  signal: ctrl.signal
@@ -26641,7 +27096,7 @@ function createEverosSearchTool(everosCfg) {
26641
27096
  clearTimeout(timer);
26642
27097
  if (!resp.ok) {
26643
27098
  const text = await resp.text();
26644
- console.warn(`[everos] search failed: ${resp.status} ${text.slice(0, 200)}`);
27099
+ console.warn(`[memory_search] failed: ${resp.status} ${text.slice(0, 200)}`);
26645
27100
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26646
27101
  }
26647
27102
  const data = await resp.json();
@@ -26650,7 +27105,7 @@ function createEverosSearchTool(everosCfg) {
26650
27105
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26651
27106
  }
26652
27107
  const formatted = episodes.map((ep, i) => {
26653
- 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})` : "";
26654
27109
  const subject = ep.subject || "";
26655
27110
  const ts = ep.timestamp ? ` [${ep.timestamp}]` : "";
26656
27111
  return `### ${i + 1}. ${subject}${ts}${score}
@@ -26662,9 +27117,9 @@ ${formatted}` };
26662
27117
  } catch (err) {
26663
27118
  clearTimeout(timer);
26664
27119
  if (err.name === "AbortError") {
26665
- console.warn(`[everos] search timeout: ${query.slice(0, 50)}`);
27120
+ console.warn(`[memory_search] timeout: ${query.slice(0, 50)}`);
26666
27121
  } else {
26667
- console.warn(`[everos] search error: ${err.message}`);
27122
+ console.warn(`[memory_search] error: ${err.message}`);
26668
27123
  }
26669
27124
  return { content: "\u6CA1\u6709\u627E\u5230\u76F8\u5173\u8BB0\u5FC6\u3002" };
26670
27125
  }
@@ -27163,9 +27618,9 @@ async function startEngine(config, opts) {
27163
27618
  process.env.ENGINE_MEDIA_DIR = config.mediaDir;
27164
27619
  process.env.ENGINE7_WORKSPACE = config.workspace;
27165
27620
  process.env.OPENCLAW_WORKSPACE = config.workspace;
27166
- fs41.mkdirSync(path42.join(config.stateDir, "agents", "main", "memory"), { recursive: true });
27167
- fs41.mkdirSync(path42.join(config.stateDir, "agents", "main", "sessions"), { recursive: true });
27168
- 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 });
27169
27624
  fs41.mkdirSync(config.workspace, { recursive: true });
27170
27625
  fs41.mkdirSync(config.mediaDir, { recursive: true });
27171
27626
  try {
@@ -27275,7 +27730,7 @@ async function startEngine(config, opts) {
27275
27730
  const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
27276
27731
  initSessionMemory2({
27277
27732
  workspace: config.workspace,
27278
- stateDir: path42.join(config.stateDir, "session-memory"),
27733
+ stateDir: path43.join(config.stateDir, "session-memory"),
27279
27734
  provider,
27280
27735
  model: config.provider.modelId || config.model || "deepseek-v4-flash",
27281
27736
  features: config.profile.features
@@ -27305,9 +27760,9 @@ async function startEngine(config, opts) {
27305
27760
  if (config.hooks) {
27306
27761
  loadHooksFromConfig({ hooks: config.hooks });
27307
27762
  }
27308
- const hooksPath = path42.join(config.workspace, ".hooks.json");
27763
+ const hooksPath = path43.join(config.workspace, ".hooks.json");
27309
27764
  loadHooksFromFile(hooksPath);
27310
- const settingsHooksPath = path42.join(config.stateDir, "settings.json");
27765
+ const settingsHooksPath = path43.join(config.stateDir, "settings.json");
27311
27766
  loadHooksFromFile(settingsHooksPath);
27312
27767
  console.log(`[hooks] Loaded hooks configuration`);
27313
27768
  registerCallbackHook("PreCompact", {
@@ -27321,15 +27776,15 @@ async function startEngine(config, opts) {
27321
27776
  const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
27322
27777
  const dateStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")}`;
27323
27778
  const timeStr = `${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
27324
- const dailyDir = path42.join(workspace, "memory", "daily");
27325
- const dailyPath = path42.join(dailyDir, `${dateStr}.md`);
27779
+ const dailyDir = path43.join(workspace, "memory", "daily");
27780
+ const dailyPath = path43.join(dailyDir, `${dateStr}.md`);
27326
27781
  try {
27327
27782
  const fs42 = await import("node:fs");
27328
27783
  if (!fs42.existsSync(dailyDir)) {
27329
27784
  fs42.mkdirSync(dailyDir, { recursive: true });
27330
27785
  }
27331
- const sessionsDir = path42.join(config.stateDir, "agents", "main", "sessions");
27332
- 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`);
27333
27788
  const recentLines = [];
27334
27789
  if (fs42.existsSync(sessionFile)) {
27335
27790
  const content = fs42.readFileSync(sessionFile, "utf-8");
@@ -27382,7 +27837,7 @@ ${entry}`);
27382
27837
  if (!workspace) return { continue: true };
27383
27838
  try {
27384
27839
  const fs42 = await import("node:fs");
27385
- const bufferPath = path42.join(workspace, "memory", "working-buffer.md");
27840
+ const bufferPath = path43.join(workspace, "memory", "working-buffer.md");
27386
27841
  if (fs42.existsSync(bufferPath)) {
27387
27842
  const stat4 = fs42.statSync(bufferPath);
27388
27843
  const ageMs = Date.now() - stat4.mtimeMs;
@@ -27435,7 +27890,7 @@ ${content}`
27435
27890
  return `${hr}h ${remMin}m`;
27436
27891
  }
27437
27892
  if (config.skills?.enabled !== false) {
27438
- 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");
27439
27894
  const modelDef2 = config.provider.models.find((m2) => m2.id === config.model);
27440
27895
  const contextWindowTokens = modelDef2?.contextWindow;
27441
27896
  const skills = scanSkills(skillsDir);
@@ -27454,7 +27909,7 @@ ${content}`
27454
27909
  workspace: config.workspace
27455
27910
  });
27456
27911
  const systemPrompt = [systemStable, systemDynamic].join("\n\n");
27457
- const promptDumpPath = path42.join(config.workspace, ".system-prompt.txt");
27912
+ const promptDumpPath = path43.join(config.workspace, ".system-prompt.txt");
27458
27913
  fs41.writeFileSync(promptDumpPath, systemPrompt);
27459
27914
  console.log(`System prompt: ${systemStable.length} chars stable + ${systemDynamic.length} chars dynamic \u2192 ${promptDumpPath}`);
27460
27915
  const modelDef = config.provider.models.find((m2) => m2.id === config.model);
@@ -27565,20 +28020,23 @@ ${content}`
27565
28020
  enabled: true,
27566
28021
  url: everosCfg.everosUrl || "http://127.0.0.1:8100",
27567
28022
  appId: everosCfg.userId || "default",
27568
- userId: everosCfg.userId || "default"
28023
+ userId: everosCfg.userId || "default",
28024
+ agentName: everosCfg.agentName || everosCfg.userId || "assistant"
27569
28025
  });
27570
28026
  sessions.onWriterCreated = (writer, sessionId) => {
27571
28027
  writer.onMessageWritten = (msg2) => {
28028
+ console.log(`[everos-sync] onMessageWritten fired: role=${msg2.role} len=${msg2.text.length}`);
27572
28029
  everosSync.push({
27573
28030
  sessionId: writer.engineSessionId || sessionId,
27574
28031
  role: msg2.role === "toolResult" ? "tool" : msg2.role,
27575
28032
  text: msg2.text,
27576
28033
  timestamp: new Date(msg2.timestamp).getTime()
27577
- }).catch(() => {
27578
- });
28034
+ }).catch((e) => console.warn(`[everos-sync] push error: ${e}`));
27579
28035
  };
27580
28036
  };
27581
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`);
27582
28040
  }
27583
28041
  const channelManager = new ChannelManager();
27584
28042
  const memoryRecallProvider = createMemorySideProvider(
@@ -27609,6 +28067,7 @@ ${content}`
27609
28067
  recallProvider: memoryRecallProvider || void 0,
27610
28068
  extractProvider: memoryExtractProvider || void 0,
27611
28069
  topics: config.topics,
28070
+ everosCfg: config.everos,
27612
28071
  mcpManager
27613
28072
  };
27614
28073
  if (visionEngine && visionConfig) {
@@ -28529,8 +28988,8 @@ Auto-routing disabled \u2014 all messages use this model.
28529
28988
  let writePath = configPath;
28530
28989
  if (configPath && !fs41.existsSync(configPath)) {
28531
28990
  const __pFile = fileURLToPath(import.meta.url);
28532
- const __pDir = path42.dirname(__pFile);
28533
- 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));
28534
28993
  if (fs41.existsSync(altPath)) {
28535
28994
  console.warn(`[primary] Config not found at ${configPath}, falling back to ${altPath}`);
28536
28995
  writePath = altPath;
@@ -28810,7 +29269,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
28810
29269
  const ext = detected.split("/")[1] || "png";
28811
29270
  const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
28812
29271
  const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
28813
- const savedPath = path42.join(config.mediaDir, `${imageId}.${ext}`);
29272
+ const savedPath = path43.join(config.mediaDir, `${imageId}.${ext}`);
28814
29273
  fs41.writeFileSync(savedPath, resized.buffer);
28815
29274
  savedPaths.push(savedPath);
28816
29275
  console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
@@ -28837,7 +29296,7 @@ ${pathStr}` }];
28837
29296
  }
28838
29297
  const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
28839
29298
  if (nonImageAttachments && nonImageAttachments.length > 0) {
28840
- const outDir = path42.join(config.mediaDir, sessionId);
29299
+ const outDir = path43.join(config.mediaDir, sessionId);
28841
29300
  fs41.mkdirSync(outDir, { recursive: true });
28842
29301
  const resolved = [];
28843
29302
  for (const att of nonImageAttachments) {
@@ -28846,8 +29305,8 @@ ${pathStr}` }];
28846
29305
  const resp = await fetch(att.url);
28847
29306
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
28848
29307
  const buffer = Buffer.from(await resp.arrayBuffer());
28849
- const safeName2 = path42.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
28850
- 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);
28851
29310
  fs41.writeFileSync(savedPath, buffer);
28852
29311
  resolved.push(savedPath);
28853
29312
  console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
@@ -29195,7 +29654,8 @@ ${pathStr}` }];
29195
29654
  if (config.cognifold?.intentWatcher?.enabled) {
29196
29655
  try {
29197
29656
  const { registerCognifoldIntentWatcher: registerCognifoldIntentWatcher2 } = await Promise.resolve().then(() => (init_cognifold_intent_watcher(), cognifold_intent_watcher_exports));
29198
- const cfSessionId = config.cognifold.sessionId;
29657
+ const sm = globalThis.__cognifoldSessions;
29658
+ const cfSessionId = sm?.getSessionId?.("main") || config.cognifold.sessionId;
29199
29659
  if (!cfSessionId) {
29200
29660
  console.warn("[cognifold] intent-watcher: config.cognifold.sessionId \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 watcher");
29201
29661
  } else {
@@ -29208,7 +29668,7 @@ ${pathStr}` }];
29208
29668
  console.warn("[cognifold] watcher: config.workspace \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7 proactive \u5199\u5165");
29209
29669
  return;
29210
29670
  }
29211
- const pFile = path42.join(wsDir, ".cognifold-proactive.json");
29671
+ const pFile = path43.join(wsDir, ".cognifold-proactive.json");
29212
29672
  const cognifoldBaseUrl = config.cognifold?.baseUrl || "http://127.0.0.1:9001";
29213
29673
  const cognifoldSessionId = cfSessionId;
29214
29674
  const rawSuggestions = data.suggestions || data.actions || (data.intent_id ? [data] : []);
@@ -29262,7 +29722,7 @@ ${pathStr}` }];
29262
29722
  console.error(`[cognifold] failed to save proactive: ${e.message}`);
29263
29723
  }
29264
29724
  if (enriched.length > 0) {
29265
- const promptFile = path42.join(config.workspace, "prompts", "cognifold-proactive.md");
29725
+ const promptFile = path43.join(config.workspace, "prompts", "cognifold-proactive.md");
29266
29726
  const promptText = fs41.existsSync(promptFile) ? fs41.readFileSync(promptFile, "utf-8") : "[CogniFold proactive] \u6709 " + enriched.length + " \u4E2A action \u5230\u671F\u4E86";
29267
29727
  const actionsJson = JSON.stringify(enriched, null, 2);
29268
29728
  const sessionId = cfSessionId;
@@ -29368,9 +29828,9 @@ async function doReloadConfig(config, deps, provider) {
29368
29828
  let reloadConfigPath = savedConfigPath;
29369
29829
  if (!fs41.existsSync(reloadConfigPath)) {
29370
29830
  const __filename = fileURLToPath(import.meta.url);
29371
- const __dirname = path42.dirname(__filename);
29372
- const engineConfigsDir = path42.resolve(__dirname, "../configs");
29373
- 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));
29374
29834
  if (fs41.existsSync(altPath)) {
29375
29835
  console.warn(`[reload] Config not found at ${reloadConfigPath}, falling back to ${altPath} (dev mode)`);
29376
29836
  reloadConfigPath = altPath;
@@ -29423,7 +29883,7 @@ async function doReloadConfig(config, deps, provider) {
29423
29883
  } catch (err) {
29424
29884
  console.error(`[reload] Failed: ${err.message}`);
29425
29885
  try {
29426
- 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}
29427
29887
  ${err.stack}
29428
29888
  `);
29429
29889
  } catch {
@@ -29435,17 +29895,17 @@ function startConfigWatcher(config, deps, provider) {
29435
29895
  const raw = config._configFilePath;
29436
29896
  let configPath = raw;
29437
29897
  if (!fs41.existsSync(configPath)) {
29438
- configPath = path42.resolve(raw);
29898
+ configPath = path43.resolve(raw);
29439
29899
  }
29440
29900
  if (!fs41.existsSync(configPath)) {
29441
29901
  const __filename2 = fileURLToPath(import.meta.url);
29442
- const __dirname2 = path42.dirname(__filename2);
29443
- configPath = path42.resolve(__dirname2, "..", raw);
29902
+ const __dirname2 = path43.dirname(__filename2);
29903
+ configPath = path43.resolve(__dirname2, "..", raw);
29444
29904
  }
29445
29905
  if (!fs41.existsSync(configPath)) {
29446
29906
  console.warn(`[config-watch] config path invalid: ${configPath}, watcher disabled`);
29447
29907
  try {
29448
- 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}
29449
29909
  `);
29450
29910
  } catch {
29451
29911
  }
@@ -29457,13 +29917,13 @@ function startConfigWatcher(config, deps, provider) {
29457
29917
  debounceTimer = setTimeout(async () => {
29458
29918
  console.log(`[config-watch] file changed (${eventType}), reloading...`);
29459
29919
  try {
29460
- 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
29461
29921
  `);
29462
29922
  } catch {
29463
29923
  }
29464
29924
  const result = await doReloadConfig(config, deps, provider);
29465
29925
  try {
29466
- 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(",")}
29467
29927
  `);
29468
29928
  } catch {
29469
29929
  }
@@ -29472,14 +29932,14 @@ function startConfigWatcher(config, deps, provider) {
29472
29932
  watcher.on("error", (err) => {
29473
29933
  console.error(`[config-watch] error: ${err.message}`);
29474
29934
  try {
29475
- 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}
29476
29936
  `);
29477
29937
  } catch {
29478
29938
  }
29479
29939
  });
29480
29940
  console.log(`[config-watch] watching ${configPath}`);
29481
29941
  try {
29482
- 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}
29483
29943
  `);
29484
29944
  } catch {
29485
29945
  }