engine7 7.1.84 → 7.1.94

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.
@@ -275,12 +275,15 @@ function normalizeHook(h) {
275
275
  }
276
276
  return {
277
277
  type: "command",
278
- command: h.command || "",
278
+ command: expandCommand(h.command || ""),
279
279
  timeout: h.timeout,
280
280
  async: h.async,
281
281
  shell: h.shell
282
282
  };
283
283
  }
284
+ function expandCommand(command) {
285
+ return command.replace(/\$\{E7_NODE\}/g, process.execPath);
286
+ }
284
287
  function registerCallbackHook(event2, hook) {
285
288
  const list2 = callbackHooks.get(event2) || [];
286
289
  if (hook.id) {
@@ -1771,6 +1774,29 @@ var init_microCompact = __esm({
1771
1774
  }
1772
1775
  });
1773
1776
 
1777
+ // src/utils/well-formed.ts
1778
+ function truncateByCodePoint(text, max) {
1779
+ if (text.length <= max) return text;
1780
+ return Array.from(text).slice(0, max).join("");
1781
+ }
1782
+ function toWellFormed(s) {
1783
+ if (HAS_NATIVE) return s.toWellFormed();
1784
+ return s.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
1785
+ }
1786
+ function wellFormedReplacer(_key, value) {
1787
+ return typeof value === "string" ? toWellFormed(value) : value;
1788
+ }
1789
+ function safeStringify(body) {
1790
+ return JSON.stringify(body, wellFormedReplacer);
1791
+ }
1792
+ var HAS_NATIVE;
1793
+ var init_well_formed = __esm({
1794
+ "src/utils/well-formed.ts"() {
1795
+ "use strict";
1796
+ HAS_NATIVE = typeof String.prototype.toWellFormed === "function";
1797
+ }
1798
+ });
1799
+
1774
1800
  // src/compact/ruleCompact.ts
1775
1801
  function smartCompressToolResult(content, essentialFields, sizeLimit) {
1776
1802
  if (content.length <= (sizeLimit ?? TOOL_SIZE_LIMIT)) return content;
@@ -1812,7 +1838,7 @@ ${result}`;
1812
1838
  }
1813
1839
  } catch {
1814
1840
  }
1815
- return content.slice(0, sizeLimit ?? TRUNCATE_TO) + "\n...[TRUNCATED]";
1841
+ return truncateByCodePoint(content, sizeLimit ?? TRUNCATE_TO) + "\n...[TRUNCATED]";
1816
1842
  }
1817
1843
  function foldTurnInline(turn) {
1818
1844
  const toolMsgs = turn.filter((m) => m.role === "tool");
@@ -1942,7 +1968,8 @@ function step3_truncateLarge(messages) {
1942
1968
  details.push(`tool_call_id=${m.tool_call_id?.slice(0, 8)}... ${originalLen} \u2192 ${TRUNCATE_TO} chars`);
1943
1969
  return {
1944
1970
  ...m,
1945
- content: m.content.slice(0, TRUNCATE_TO) + "\n...[TRUNCATED]"
1971
+ // 0918:按 code point 切,避免切开 emoji 代理对(见 utils/well-formed.ts)
1972
+ content: truncateByCodePoint(m.content, TRUNCATE_TO) + "\n...[TRUNCATED]"
1946
1973
  };
1947
1974
  }
1948
1975
  return m;
@@ -2032,6 +2059,7 @@ var init_ruleCompact = __esm({
2032
2059
  init_tokenEstimate();
2033
2060
  init_autoCompact();
2034
2061
  init_compactLog();
2062
+ init_well_formed();
2035
2063
  TAG3 = "ruleCompact";
2036
2064
  TOOL_SIZE_LIMIT = 8e3;
2037
2065
  TRUNCATE_TO = 5e3;
@@ -2355,6 +2383,9 @@ function openDb(workspace) {
2355
2383
  day TEXT,
2356
2384
  time TEXT,
2357
2385
  date_str TEXT,
2386
+ -- \u26A0\uFE0F 0920 \u4E4B\u524D\u7684\u503C\u7531\u5E26\u7B26\u53F7 bug \u7684 nowIso \u5199\u5165\uFF1A\u503C = \u771F\u5B9E\u65F6\u523B + **\u5199\u673A\u5F53\u65F6\u7684
2387
+ -- UTC \u504F\u79FB**\uFF08\u4E1C\u4E3A\u6B63\uFF09\u3002\u9010\u884C\u4E0D\u540C\u3001\u65E0\u5904\u53EF\u8003\uFF0C\u6CA1\u6709\u53EF\u9760\u7684\u8FD8\u539F\u529E\u6CD5\u3002
2388
+ -- 0920 \u8D77\u5199\u5165\u6B63\u786E UTC Z\u3002\u8BFB\u8FD9\u4E09\u5217\u524D\u5148\u786E\u8BA4\u4F60\u8981\u7684\u662F\u4EC0\u4E48\u3002
2358
2389
  created_at TEXT NOT NULL,
2359
2390
  done_at TEXT,
2360
2391
  archived_at TEXT
@@ -2397,7 +2428,7 @@ function migrate(db2) {
2397
2428
  db2.exec("ALTER TABLE events ADD COLUMN doc_path TEXT");
2398
2429
  }
2399
2430
  }
2400
- var _sqlite, DatabaseSync, TZ_OFFSET_MS;
2431
+ var _sqlite, DatabaseSync;
2401
2432
  var init_db = __esm({
2402
2433
  "src/calendar/db.ts"() {
2403
2434
  "use strict";
@@ -2411,7 +2442,6 @@ var init_db = __esm({
2411
2442
  return getSqlite().DatabaseSync[prop];
2412
2443
  }
2413
2444
  });
2414
- TZ_OFFSET_MS = 8 * 60 * 60 * 1e3;
2415
2445
  }
2416
2446
  });
2417
2447
 
@@ -2540,15 +2570,6 @@ function bindAim(stateDir, goalId, stepId, aimId) {
2540
2570
  step.note = `AIM assignment bound: ${aimId}`;
2541
2571
  });
2542
2572
  }
2543
- function bindCalendar(stateDir, goalId, stepId, calendarId, deadlineAt) {
2544
- return updateGoal(stateDir, goalId, (g) => {
2545
- const step = g.steps.find((s) => s.id === stepId);
2546
- if (!step) throw new Error(`Step not found: ${stepId}`);
2547
- if (step.status === "completed") throw new Error("Cannot bind calendar to completed step");
2548
- step.calendarId = calendarId;
2549
- if (deadlineAt) step.deadlineAt = deadlineAt;
2550
- });
2551
- }
2552
2573
  function applyAimEvent(stateDir, aimId, type2, note) {
2553
2574
  const goal = listGoals(stateDir).find((g) => g.steps.some((s) => s.aimId === aimId));
2554
2575
  if (!goal) return void 0;
@@ -2612,7 +2633,7 @@ function addGoalStep(stateDir, goalId, title, kind = "self") {
2612
2633
  g.steps.push({ id, title, status: g.currentStep >= g.steps.length ? "active" : "pending", kind });
2613
2634
  });
2614
2635
  }
2615
- function bindGoalStep(stateDir, goalId, stepId, kind, ref) {
2636
+ function bindGoalStep(stateDir, goalId, stepId, kind, ref, deadlineAt) {
2616
2637
  return updateGoal(stateDir, goalId, (g) => {
2617
2638
  const step = g.steps.find((s) => s.id === stepId);
2618
2639
  if (!step) throw new Error(`Step not found: ${stepId}`);
@@ -2620,7 +2641,10 @@ function bindGoalStep(stateDir, goalId, stepId, kind, ref) {
2620
2641
  step.kind = kind;
2621
2642
  if (kind === "aim") step.aimId = String(ref);
2622
2643
  else if (kind === "remote_task") step.taskId = String(ref);
2623
- else if (kind === "calendar") step.calendarId = Number(ref);
2644
+ else if (kind === "calendar") {
2645
+ step.calendarId = Number(ref);
2646
+ if (deadlineAt) step.deadlineAt = deadlineAt;
2647
+ }
2624
2648
  step.note = `${kind} \u59D4\u6D3E: ${ref}\uFF08\u5B8C\u6210\u4E8B\u4EF6\u81EA\u52A8\u56DE\u6D41\u89E3\u9501\uFF09`;
2625
2649
  });
2626
2650
  }
@@ -3752,7 +3776,7 @@ async function classifyViaSidecar(url, context, text, opts = {}) {
3752
3776
  const res = await doFetch(`${url.replace(/\/$/, "")}/classify`, {
3753
3777
  method: "POST",
3754
3778
  headers: { "Content-Type": "application/json" },
3755
- body: JSON.stringify({ context: context.map((t) => ({ text: t })), text }),
3779
+ body: safeStringify({ context: context.map((t) => ({ text: t })), text }),
3756
3780
  signal: ac.signal
3757
3781
  });
3758
3782
  if (!res.ok) return null;
@@ -3769,6 +3793,7 @@ var init_skill_gate_classifier = __esm({
3769
3793
  "src/skills/skill-gate-classifier.ts"() {
3770
3794
  "use strict";
3771
3795
  init_skill_gate_window();
3796
+ init_well_formed();
3772
3797
  }
3773
3798
  });
3774
3799
 
@@ -5315,16 +5340,18 @@ function shouldRetry(status, errorBody) {
5315
5340
  if (status === 529) return true;
5316
5341
  if (status === 429) {
5317
5342
  const body = errorBody?.toLowerCase() || "";
5318
- if (body.includes("insufficient_quota") || body.includes("allocationquota") || body.includes("quota_exceeded") || body.includes("quotaexceeded") || body.includes("quota") && body.includes("exceeded") || body.includes('"code":"1310"') || body.includes('"code":1310') || body.includes("\u4F7F\u7528\u4E0A\u9650") && body.includes("\u91CD\u7F6E")) {
5343
+ if (body.includes("insufficient_quota") || body.includes("allocationquota") || body.includes("quota_exceeded") || body.includes("quotaexceeded") || body.includes("quota") && body.includes("exceeded") || body.includes('"code":"1310"') || body.includes('"code":1310') || body.includes("\u4F7F\u7528\u4E0A\u9650") && body.includes("\u91CD\u7F6E") || body.includes("\u7528\u91CF\u4E0A\u9650")) {
5319
5344
  return false;
5320
5345
  }
5321
5346
  return true;
5322
5347
  }
5323
5348
  if (status === 408) return true;
5324
5349
  if (status === 409) return true;
5325
- if (status === 401) return true;
5326
- if (status === 403) return true;
5327
- if (status >= 500) return true;
5350
+ if (status >= 500) {
5351
+ const body = errorBody?.toLowerCase() || "";
5352
+ if (body.includes("\u65E0\u53EF\u7528\u6E20\u9053") || body.includes("new_sensitive")) return false;
5353
+ return true;
5354
+ }
5328
5355
  if (errorBody?.includes('"type":"overloaded_error"')) return true;
5329
5356
  return false;
5330
5357
  }
@@ -5594,6 +5621,7 @@ var init_openai_provider = __esm({
5594
5621
  init_withRetry();
5595
5622
  init_provider();
5596
5623
  init_think_stripper();
5624
+ init_well_formed();
5597
5625
  OpenAIProvider = class {
5598
5626
  // 0831 <think> 剥离改用 ThinkTagStripper 的**局部**实例(见 streamChat)——
5599
5627
  // 原来是实例字段,跨请求共享状态,流中途 abort 会把 active=true 带到下一个请求
@@ -5682,13 +5710,17 @@ var init_openai_provider = __esm({
5682
5710
  }
5683
5711
  const actualThinking = body.chat_template_kwargs;
5684
5712
  console.log(`[openai] \u2192 model=${params.model} thinking=${actualThinking ? JSON.stringify(actualThinking) : "none"} maxTokens=${body.max_tokens ?? "unset"} msgs=${formatted.length} tools=${params.tools?.length ?? 0}`);
5713
+ let bodyStr = safeStringify(body);
5714
+ if (this.config.wafSingleQuoteWorkaround) {
5715
+ bodyStr = bodyStr.replace(/'/g, "\u2019");
5716
+ }
5685
5717
  const retryGen = fetchWithRetry(url, {
5686
5718
  method: "POST",
5687
5719
  headers: {
5688
5720
  "Content-Type": "application/json",
5689
5721
  "Authorization": `Bearer ${this.config.apiKey}`
5690
5722
  },
5691
- body: JSON.stringify(body),
5723
+ body: bodyStr,
5692
5724
  signal: params.signal
5693
5725
  }, "openai", this.config.proxy);
5694
5726
  let response;
@@ -5886,7 +5918,7 @@ function adaptUsage(u) {
5886
5918
  });
5887
5919
  }
5888
5920
  function serializeBody(body, wafSingleQuoteWorkaround) {
5889
- let bodyStr = JSON.stringify(body);
5921
+ let bodyStr = safeStringify(body);
5890
5922
  if (wafSingleQuoteWorkaround) {
5891
5923
  bodyStr = bodyStr.replace(/'/g, "\u2019");
5892
5924
  }
@@ -5913,6 +5945,7 @@ var init_openai_responses_provider = __esm({
5913
5945
  "use strict";
5914
5946
  init_withRetry();
5915
5947
  init_provider();
5948
+ init_well_formed();
5916
5949
  CODEX_CLI_VERSION = "0.154.0";
5917
5950
  CODEX_TERMINAL_TOKEN = "xterm-256color";
5918
5951
  OpenAIResponsesProvider = class {
@@ -6233,12 +6266,14 @@ var init_anthropic_provider = __esm({
6233
6266
  init_withRetry();
6234
6267
  init_provider();
6235
6268
  init_think_stripper();
6269
+ init_well_formed();
6236
6270
  AnthropicProvider = class {
6237
6271
  // 模拟 CC 的 X-Claude-Code-Session-Id
6238
6272
  constructor(config) {
6239
6273
  this.config = config;
6240
6274
  let u = (config.baseUrl || "https://api.anthropic.com").trim();
6241
6275
  while (u.endsWith("/")) u = u.slice(0, -1);
6276
+ if (u.endsWith("/v1")) u = u.slice(0, -3);
6242
6277
  this.baseUrl = u;
6243
6278
  this.sessionId = randomUUID3();
6244
6279
  }
@@ -6437,7 +6472,7 @@ var init_anthropic_provider = __esm({
6437
6472
  } catch {
6438
6473
  }
6439
6474
  }
6440
- let bodyStr = JSON.stringify(body);
6475
+ let bodyStr = safeStringify(body);
6441
6476
  if (this.config.wafSingleQuoteWorkaround) {
6442
6477
  const before = bodyStr.length;
6443
6478
  bodyStr = bodyStr.replace(/'/g, "\u2019");
@@ -6657,6 +6692,7 @@ var init_gemini_provider = __esm({
6657
6692
  "src/models/gemini-provider.ts"() {
6658
6693
  "use strict";
6659
6694
  init_withRetry();
6695
+ init_well_formed();
6660
6696
  GeminiProvider = class {
6661
6697
  constructor(config) {
6662
6698
  this.config = config;
@@ -6816,7 +6852,7 @@ var init_gemini_provider = __esm({
6816
6852
  "Content-Type": "application/json",
6817
6853
  "x-goog-api-key": this.config.apiKey
6818
6854
  },
6819
- body: JSON.stringify(body),
6855
+ body: safeStringify(body),
6820
6856
  signal: params.signal
6821
6857
  }, "gemini", this.config.proxy);
6822
6858
  let response;
@@ -6955,7 +6991,9 @@ function createProvider(config) {
6955
6991
  baseUrl: config.baseUrl,
6956
6992
  apiKey: config.apiKey,
6957
6993
  thinking: config.thinking,
6958
- proxy
6994
+ proxy,
6995
+ wafSingleQuoteWorkaround: config.wafSingleQuoteWorkaround
6996
+ // 0919 中转站→agentrouter 同样有 WAF
6959
6997
  });
6960
6998
  case "openai-responses":
6961
6999
  return new OpenAIResponsesProvider({
@@ -7072,12 +7110,205 @@ var init_features = __esm({
7072
7110
  }
7073
7111
  });
7074
7112
 
7113
+ // src/time/clock.ts
7114
+ function detectHostTimezone() {
7115
+ try {
7116
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
7117
+ } catch {
7118
+ return "UTC";
7119
+ }
7120
+ }
7121
+ function isValidTimezone(name) {
7122
+ if (!name) return false;
7123
+ try {
7124
+ new Intl.DateTimeFormat("en-US", { timeZone: name });
7125
+ return true;
7126
+ } catch {
7127
+ return false;
7128
+ }
7129
+ }
7130
+ function tz() {
7131
+ if (testTimezone) return testTimezone;
7132
+ const v = liveConfig.get("timezone");
7133
+ const name = typeof v === "string" && v ? v : "UTC";
7134
+ if (validatedZones.has(name)) return name;
7135
+ if (isValidTimezone(name)) {
7136
+ validatedZones.add(name);
7137
+ return name;
7138
+ }
7139
+ warnOnce("tz", "invalid-zone", name);
7140
+ return "UTC";
7141
+ }
7142
+ function partsFmt(zone) {
7143
+ let f = fmtCache.get(zone);
7144
+ if (!f) {
7145
+ f = new Intl.DateTimeFormat("en-US", {
7146
+ timeZone: zone,
7147
+ year: "numeric",
7148
+ month: "2-digit",
7149
+ day: "2-digit",
7150
+ hour: "2-digit",
7151
+ minute: "2-digit",
7152
+ second: "2-digit",
7153
+ hourCycle: "h23",
7154
+ weekday: "short"
7155
+ });
7156
+ fmtCache.set(zone, f);
7157
+ }
7158
+ return f;
7159
+ }
7160
+ function asDate(i) {
7161
+ return i instanceof Date ? i : new Date(i);
7162
+ }
7163
+ function wallParts(i = /* @__PURE__ */ new Date()) {
7164
+ const d = asDate(i);
7165
+ const parts = partsFmt(tz()).formatToParts(d);
7166
+ const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
7167
+ return {
7168
+ y: Number(get("year")),
7169
+ mo: Number(get("month")),
7170
+ d: Number(get("day")),
7171
+ hh: Number(get("hour")),
7172
+ mm: Number(get("minute")),
7173
+ ss: Number(get("second")),
7174
+ weekday: EN_WEEKDAY[get("weekday")] ?? 0
7175
+ };
7176
+ }
7177
+ function weekdayZh(i) {
7178
+ return WEEKDAY_ZH[wallParts(i).weekday];
7179
+ }
7180
+ function nowUtc() {
7181
+ return (/* @__PURE__ */ new Date()).toISOString();
7182
+ }
7183
+ function toUtc(d) {
7184
+ return d.toISOString();
7185
+ }
7186
+ function toWall(i = /* @__PURE__ */ new Date()) {
7187
+ const p = wallParts(i);
7188
+ return `${p.y}-${p2(p.mo)}-${p2(p.d)} ${p2(p.hh)}:${p2(p.mm)}:${p2(p.ss)}`;
7189
+ }
7190
+ function formatForLlm(i) {
7191
+ const d = i ?? /* @__PURE__ */ new Date();
7192
+ const p = wallParts(d);
7193
+ return `${p.y}-${p2(p.mo)}-${p2(p.d)} ${WEEKDAY_ZH[p.weekday]} ${p2(p.hh)}:${p2(p.mm)}\uFF08${tz()}\uFF09`;
7194
+ }
7195
+ function formatLog(i) {
7196
+ return toWall(i ?? /* @__PURE__ */ new Date());
7197
+ }
7198
+ function formatWallLabel(i) {
7199
+ return formatForLlm(i).replace(/\s*(.*)$/, "");
7200
+ }
7201
+ function warnOnce(callSite, shape, sample) {
7202
+ const key = `${callSite}:${shape}`;
7203
+ if (warned.has(key)) return;
7204
+ warned.add(key);
7205
+ console.warn(`[clock] ${callSite} \u5951\u7EA6\u8FDD\u89C4(${shape})\uFF0C\u6837\u672C: ${String(sample).slice(0, 80)}`);
7206
+ }
7207
+ function zoneOffsetMs(d) {
7208
+ const p = wallParts(d);
7209
+ const asUtc = Date.UTC(p.y, p.mo - 1, p.d, p.hh, p.mm, p.ss);
7210
+ return asUtc - Math.floor(d.getTime() / 1e3) * 1e3;
7211
+ }
7212
+ function wallToUtc(y, mo, d, hh, mm, ss) {
7213
+ const guess = Date.UTC(y, mo - 1, d, hh, mm, ss);
7214
+ const off1 = zoneOffsetMs(new Date(guess));
7215
+ const ms1 = guess - off1;
7216
+ const off2 = zoneOffsetMs(new Date(ms1));
7217
+ const ms = off2 === off1 ? ms1 : guess - off2;
7218
+ return new Date(ms).toISOString();
7219
+ }
7220
+ function parseWall(s) {
7221
+ const raw = String(s ?? "").trim();
7222
+ if (!raw) return "";
7223
+ if (/Z$/i.test(raw) || /[+-]\d{2}:?\d{2}$/.test(raw)) {
7224
+ const ms2 = Date.parse(raw);
7225
+ if (Number.isNaN(ms2)) {
7226
+ warnOnce("parseWall", "unparseable", raw);
7227
+ return raw;
7228
+ }
7229
+ warnOnce("parseWall", "has-offset", raw);
7230
+ return new Date(ms2).toISOString();
7231
+ }
7232
+ const m = WALL_RE.exec(raw);
7233
+ if (!m) {
7234
+ warnOnce("parseWall", "unparseable", raw);
7235
+ return raw;
7236
+ }
7237
+ const tail = raw.slice(m[0].length);
7238
+ if (tail && !/^\.\d+$/.test(tail)) {
7239
+ warnOnce("parseWall", "unparseable-tail", raw);
7240
+ return raw;
7241
+ }
7242
+ const [Y, Mo, D, H, Mi, S] = [+m[1], +m[2], +m[3], +m[4], +m[5], m[6] ? +m[6] : 0];
7243
+ if (Y < 1e3 || Y > 9999 || Mo < 1 || Mo > 12 || D < 1 || D > daysInMonth(Y, Mo) || H > 23 || Mi > 59 || S > 59) {
7244
+ warnOnce("parseWall", "out-of-range", raw);
7245
+ return raw;
7246
+ }
7247
+ const ms = tail ? Math.round(Number(`0${tail}`) * 1e3) : 0;
7248
+ return withMs(wallToUtc(Y, Mo, D, H, Mi, S), ms);
7249
+ }
7250
+ function daysInMonth(y, mo) {
7251
+ return new Date(Date.UTC(y, mo, 0)).getUTCDate();
7252
+ }
7253
+ function withMs(base, ms) {
7254
+ if (!ms) return base;
7255
+ const t = Date.parse(base);
7256
+ return Number.isNaN(t) ? base : new Date(t + ms).toISOString();
7257
+ }
7258
+ function parseDateStrTime(dateStr, timeExact) {
7259
+ if (typeof dateStr !== "string" || typeof timeExact !== "string") return void 0;
7260
+ if (!/^\d{1,2}\/\d{1,2}$/.test(dateStr)) return void 0;
7261
+ if (!/^\d{2}:\d{2}$/.test(timeExact)) return void 0;
7262
+ const [mo, d] = dateStr.split("/").map(Number);
7263
+ if (mo < 1 || mo > 12 || d < 1 || d > 31) return void 0;
7264
+ const y = wallParts().y;
7265
+ return parseWall(`${y}-${p2(mo)}-${p2(d)}T${timeExact}:00`);
7266
+ }
7267
+ function localOffsetSuffix(at) {
7268
+ const total = Math.round(zoneOffsetMs(at ? asDate(at) : /* @__PURE__ */ new Date()) / 6e4);
7269
+ const sign = total >= 0 ? "+" : "-";
7270
+ const abs = Math.abs(total);
7271
+ return `${sign}${p2(Math.floor(abs / 60))}:${p2(abs % 60)}`;
7272
+ }
7273
+ function startOfLocalDay(i) {
7274
+ const p = wallParts(i ?? /* @__PURE__ */ new Date());
7275
+ return wallToUtc(p.y, p.mo, p.d, 0, 0, 0);
7276
+ }
7277
+ function todayKey(i) {
7278
+ const p = wallParts(i ?? /* @__PURE__ */ new Date());
7279
+ return `${p.y}-${p2(p.mo)}-${p2(p.d)}`;
7280
+ }
7281
+ var WEEKDAY_ZH, EN_WEEKDAY, testTimezone, validatedZones, fmtCache, p2, warned, WALL_RE;
7282
+ var init_clock = __esm({
7283
+ "src/time/clock.ts"() {
7284
+ "use strict";
7285
+ init_live();
7286
+ WEEKDAY_ZH = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
7287
+ EN_WEEKDAY = {
7288
+ Sun: 0,
7289
+ Mon: 1,
7290
+ Tue: 2,
7291
+ Wed: 3,
7292
+ Thu: 4,
7293
+ Fri: 5,
7294
+ Sat: 6
7295
+ };
7296
+ testTimezone = null;
7297
+ validatedZones = /* @__PURE__ */ new Set();
7298
+ fmtCache = /* @__PURE__ */ new Map();
7299
+ p2 = (n) => String(n).padStart(2, "0");
7300
+ warned = /* @__PURE__ */ new Set();
7301
+ WALL_RE = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?/;
7302
+ }
7303
+ });
7304
+
7075
7305
  // src/config/loader.ts
7076
7306
  var loader_exports = {};
7077
7307
  __export(loader_exports, {
7078
7308
  configSummary: () => configSummary,
7079
7309
  loadConfig: () => loadConfig,
7080
7310
  parseModelRef: () => parseModelRef,
7311
+ resolveTimezone: () => resolveTimezone,
7081
7312
  takeLoadWarnings: () => takeLoadWarnings
7082
7313
  });
7083
7314
  import * as fs9 from "node:fs";
@@ -7099,6 +7330,24 @@ function warnAtLoad(msg2) {
7099
7330
  if (!loadWarningsFlushed) loadWarnings.push(msg2);
7100
7331
  console.warn(msg2);
7101
7332
  }
7333
+ function resolveTimezone(raw) {
7334
+ const fallback = () => {
7335
+ try {
7336
+ return detectHostTimezone();
7337
+ } catch {
7338
+ return "UTC";
7339
+ }
7340
+ };
7341
+ const v = raw?.timezone;
7342
+ if (typeof v !== "string" || !v) return fallback();
7343
+ try {
7344
+ new Intl.DateTimeFormat("en-US", { timeZone: v });
7345
+ return v;
7346
+ } catch {
7347
+ warnAtLoad(`[config] timezone "${v}" \u4E0D\u662F\u5408\u6CD5 IANA \u65F6\u533A\u540D\uFF0C\u9000\u56DE\u672C\u673A\u63A2\u6D4B`);
7348
+ return fallback();
7349
+ }
7350
+ }
7102
7351
  function loadConfig(configPath) {
7103
7352
  const resolvedPath = path9.resolve(
7104
7353
  configPath || process.env.ENGINE_CONFIG || "engine-config.json"
@@ -7110,7 +7359,7 @@ function loadConfig(configPath) {
7110
7359
  const missingEnvKeys = /* @__PURE__ */ new Set();
7111
7360
  const resolveEnv = (node) => {
7112
7361
  if (typeof node === "string") {
7113
- const m = node.match(/^(?:env:|{{\s*)([A-Z][A-Z0-9_]*)(?:\s*}})?$/);
7362
+ const m = node.match(/^(?:env:|{{\s*)(?=[A-Z0-9_]*[A-Z])([A-Z0-9_]+)(?:\s*}})?$/);
7114
7363
  if (m) {
7115
7364
  if (process.env[m[1]] !== void 0) return process.env[m[1]];
7116
7365
  if (node.startsWith("env:")) missingEnvKeys.add(m[1]);
@@ -7235,11 +7484,18 @@ function loadConfig(configPath) {
7235
7484
  ...rawInnerVoice,
7236
7485
  enabled: true
7237
7486
  } : void 0;
7487
+ const resolvedTimezone = resolveTimezone(raw);
7238
7488
  const rawCron = raw.cron || {};
7239
7489
  const cron = rawCron.enabled ? {
7240
7490
  ...rawCron,
7241
7491
  enabled: true,
7242
- defaultTimezone: rawCron.defaultTimezone || "Asia/Shanghai",
7492
+ // ★ 这里原来写死 'Asia/Shanghai'。而 cron/tasks.ts 建任务时是
7493
+ // `params.timezone || params.defaultTimezone || 'Asia/Shanghai'` ——
7494
+ // defaultTimezone 恒有值 ⇒ 兜底永远轮不到 ⇒ **工具建的每个任务都被
7495
+ // 显式写死北京时区存进 tasks.json**,顶层 timezone 配置形同虚设
7496
+ // (马耳他用户说"每天 9 点",会在当地凌晨 3 点触发)。
7497
+ // 改为跟随引擎时区;per-task 的显式 timezone 仍然优先。
7498
+ defaultTimezone: rawCron.defaultTimezone || resolvedTimezone,
7243
7499
  tickIntervalMs: rawCron.tickIntervalMs || 1e4,
7244
7500
  maxConsecutiveFailures: rawCron.maxConsecutiveFailures || 5,
7245
7501
  retry: {
@@ -7252,6 +7508,7 @@ function loadConfig(configPath) {
7252
7508
  return {
7253
7509
  ...raw,
7254
7510
  // 先展开 raw:未显式处理的字段(如 webhook)自动进来,避免每次加 config 都要手动挑;下面显式字段覆盖 raw(解析值优先)
7511
+ timezone: resolvedTimezone,
7255
7512
  provider,
7256
7513
  providers,
7257
7514
  // 所有 provider 配置字典(供 memory/provider 动态切换用)
@@ -7320,6 +7577,7 @@ var init_loader = __esm({
7320
7577
  "use strict";
7321
7578
  init_display();
7322
7579
  init_features();
7580
+ init_clock();
7323
7581
  loadWarnings = [];
7324
7582
  loadWarningsFlushed = false;
7325
7583
  }
@@ -8509,23 +8767,42 @@ var init_extractMemories = __esm({
8509
8767
  }
8510
8768
  });
8511
8769
 
8770
+ // src/mem7/core/system-filter.ts
8771
+ function parseMeta(text) {
8772
+ const m = text.match(/^\[meta:\s*(.+?)\s*\((.+?)\)\s*@(\S+?)\s*(?:[#\d]|\])/);
8773
+ if (!m) return null;
8774
+ const platform2 = m[3].replace(/[#\d]+$/, "").trim() || void 0;
8775
+ return { senderName: m[1].trim(), senderId: m[2].trim(), platform: platform2 };
8776
+ }
8777
+ function isSystemUserMessage(meta) {
8778
+ if (!meta) return true;
8779
+ return SYSTEM_PLATFORMS.has(meta.platform ?? "");
8780
+ }
8781
+ var SYSTEM_PLATFORMS;
8782
+ var init_system_filter = __esm({
8783
+ "src/mem7/core/system-filter.ts"() {
8784
+ "use strict";
8785
+ SYSTEM_PLATFORMS = /* @__PURE__ */ new Set([
8786
+ "wx_monitor"
8787
+ // 微信巡检推送(翀哥 0916 点名:"cron、wx-monitor 这种问答都不记")
8788
+ ]);
8789
+ }
8790
+ });
8791
+
8512
8792
  // src/memory/everos/ingest.ts
8513
8793
  var ingest_exports = {};
8514
8794
  __export(ingest_exports, {
8795
+ buildPayload: () => buildPayload,
8515
8796
  pushConversation: () => pushConversation
8516
8797
  });
8517
8798
  import fs20 from "node:fs";
8518
8799
  import path19 from "node:path";
8519
- function parseMeta(text) {
8520
- const m = text.match(/^\[meta:\s*(.+?)\s*\((.+?)\)\s*@(\S+)\s*[^\]]*\]/);
8521
- if (!m) return null;
8522
- return { senderName: m[1].trim() };
8523
- }
8524
8800
  async function readConfig2() {
8525
8801
  const defaults = {
8526
8802
  url: "http://127.0.0.1:8100",
8527
8803
  appId: "default",
8528
8804
  agentName: "assistant",
8805
+ ownerMode: "agent",
8529
8806
  enabled: false,
8530
8807
  ingestTimeoutMs: 3e4,
8531
8808
  syncIntervalMs: 9e5
@@ -8538,6 +8815,7 @@ async function readConfig2() {
8538
8815
  url: cfg.everosUrl || defaults.url,
8539
8816
  appId: cfg.userId || defaults.appId,
8540
8817
  agentName: cfg.agentName || cfg.userId || defaults.agentName,
8818
+ ownerMode: cfg.ownerMode === "sender" ? "sender" : "agent",
8541
8819
  enabled: cfg.enabled === true,
8542
8820
  ingestTimeoutMs: typeof cfg.ingestTimeoutMs === "number" ? cfg.ingestTimeoutMs : defaults.ingestTimeoutMs,
8543
8821
  syncIntervalMs: typeof cfg.syncIntervalMs === "number" ? cfg.syncIntervalMs : defaults.syncIntervalMs
@@ -8573,6 +8851,7 @@ function savePushTs(workspace, sessionId, ts) {
8573
8851
  }
8574
8852
  }
8575
8853
  function buildPayload(messages, cfg, sessionId) {
8854
+ let lastUserWasSystem = false;
8576
8855
  return {
8577
8856
  session_id: `extract-${sessionId}`,
8578
8857
  app_id: cfg.appId,
@@ -8584,21 +8863,28 @@ function buildPayload(messages, cfg, sessionId) {
8584
8863
  const raw = typeof m.content === "string" ? m.content : "[content blocks]";
8585
8864
  const meta = m.role === "user" ? parseMeta(raw) : null;
8586
8865
  let text = raw;
8866
+ let isSystemTurn = false;
8587
8867
  if (m.role === "user") {
8588
- if (/^\[meta:[^\]]*\(webhook\)/.test(raw.trim())) text = "";
8589
- else text = stripInjectedContext(raw.replace(/^\[meta:[^\]]*\]\s*/, ""));
8868
+ if (isSystemUserMessage(parseMeta(raw))) {
8869
+ text = "";
8870
+ isSystemTurn = true;
8871
+ } else text = stripInjectedContext(raw.replace(/^\[meta:[^\]]*\]\s*/, ""));
8872
+ lastUserWasSystem = isSystemTurn;
8590
8873
  }
8591
8874
  const senderName = meta?.senderName ?? (m.role === "assistant" ? cfg.agentName : void 0) ?? m.role;
8592
8875
  const rawRole = m.role === "toolResult" ? "tool" : m.role;
8593
8876
  const role = rawRole === "user" ? "user" : "assistant";
8877
+ const tsMs = typeof m.timestamp === "number" ? m.timestamp : Date.parse(String(m.timestamp ?? "")) || Date.now();
8594
8878
  return {
8595
- sender_id: cfg.appId,
8879
+ // owner 口径见 IngestConfig.ownerMode:'sender' 模式下 user 消息用真人 ID
8880
+ // (everos 的 owner fan-out 取的就是 user 的 sender_id → 每人一份记忆)
8881
+ sender_id: m.role === "user" && cfg.ownerMode === "sender" ? meta?.senderId || cfg.appId : cfg.appId,
8596
8882
  sender_name: senderName,
8597
8883
  role,
8598
- timestamp: Date.now(),
8884
+ timestamp: tsMs,
8599
8885
  content: text,
8600
- _skip: m.role === "user" && !text
8601
- // 内部消息(剥完为空)标记丢弃
8886
+ // 丢:系统指令本体 + 系统轮里的回应(与 mem7 同款,见上面状态机)
8887
+ _skip: m.role === "user" && !text || m.role !== "user" && lastUserWasSystem
8602
8888
  };
8603
8889
  }).filter((x) => !x._skip).map(({ _skip, ...rest }) => rest)
8604
8890
  };
@@ -8708,6 +8994,7 @@ var init_ingest = __esm({
8708
8994
  "src/memory/everos/ingest.ts"() {
8709
8995
  "use strict";
8710
8996
  init_skill_gate_window();
8997
+ init_system_filter();
8711
8998
  PUSH_CHUNK = 10;
8712
8999
  PUSH_STATE_FILE = ".everos-push-state.json";
8713
9000
  pushInProgress = false;
@@ -8754,7 +9041,8 @@ async function embedChunk(cfg, baseUrl, texts, timeoutMs) {
8754
9041
  const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
8755
9042
  method: "POST",
8756
9043
  headers: { "content-type": "application/json", authorization: `Bearer ${cfg.apiKey}` },
8757
- body: JSON.stringify({ model: cfg.model, input: texts }),
9044
+ // 0918:safeStringify 清洗孤立代理(embedding 传对话原文,emoji 风险高)
9045
+ body: safeStringify({ model: cfg.model, input: texts }),
8758
9046
  signal: ctrl.signal
8759
9047
  });
8760
9048
  if (!resp.ok) throw new Error(`embeddings HTTP ${resp.status}`);
@@ -8803,6 +9091,7 @@ function cosine(a, b) {
8803
9091
  var init_embedding = __esm({
8804
9092
  "src/mem7/core/embedding.ts"() {
8805
9093
  "use strict";
9094
+ init_well_formed();
8806
9095
  }
8807
9096
  });
8808
9097
 
@@ -8943,8 +9232,7 @@ function compileFilter(expr) {
8943
9232
  return parts.join(" AND ");
8944
9233
  }
8945
9234
  function openWindowFilter(ownerId) {
8946
- const cstToday = new Date(Date.now() + 8 * 36e5).toISOString().slice(0, 10);
8947
- const floor = (/* @__PURE__ */ new Date(`${cstToday}T00:00:00+08:00`)).toISOString();
9235
+ const floor = startOfLocalDay();
8948
9236
  const out = [{ end_time: { gte: floor } }];
8949
9237
  const owners = (Array.isArray(ownerId) ? ownerId : ownerId ? [ownerId] : []).filter(Boolean);
8950
9238
  if (Array.isArray(ownerId) && owners.length === 0) {
@@ -8958,6 +9246,7 @@ var REGISTERED_FIELDS, OPS, TIME_FIELDS;
8958
9246
  var init_filters = __esm({
8959
9247
  "src/mem7/core/filters.ts"() {
8960
9248
  "use strict";
9249
+ init_clock();
8961
9250
  REGISTERED_FIELDS = /* @__PURE__ */ new Set([
8962
9251
  "owner_id",
8963
9252
  "owner_type",
@@ -9275,13 +9564,6 @@ function renderPrompt(template, vars) {
9275
9564
  }
9276
9565
  return t.split(LB).join("{").split(RB).join("}");
9277
9566
  }
9278
- function renderLiteral(template, vars) {
9279
- let out = template;
9280
- for (const [key, value] of Object.entries(vars)) {
9281
- out = out.split(`{{${key}}}`).join(value);
9282
- }
9283
- return out;
9284
- }
9285
9567
  function formatAtomicFactTime(tsMs) {
9286
9568
  const d = new Date(tsMs);
9287
9569
  const month = MONTHS[d.getUTCMonth()];
@@ -9313,10 +9595,11 @@ function formatMessageTimestamp(tsMs) {
9313
9595
  const iso = new Date(tsMs).toISOString();
9314
9596
  return `${iso.slice(0, 10)} ${iso.slice(11, 19)}`;
9315
9597
  }
9598
+ function renderConversationText(messages) {
9599
+ return messages.map((m) => `[${formatMessageTimestamp(m.tsMs)}] ${m.senderName ?? m.senderId}: ${m.text}`).join("\n");
9600
+ }
9316
9601
  function formatMdTimestamp(d = /* @__PURE__ */ new Date()) {
9317
- const cst = new Date(d.getTime() + 8 * 36e5);
9318
- const iso = cst.toISOString();
9319
- return `${iso.slice(0, 19)}+08:00`;
9602
+ return `${toWall(d).replace(" ", "T")}${localOffsetSuffix(d)}`;
9320
9603
  }
9321
9604
  function pyListRepr(items) {
9322
9605
  return `[${items.join(", ")}]`;
@@ -9325,6 +9608,7 @@ var WEEKDAYS, MONTHS, LB, RB, DEFAULT_CUSTOM_INSTRUCTIONS;
9325
9608
  var init_prompt_render = __esm({
9326
9609
  "src/mem7/core/prompt-render.ts"() {
9327
9610
  "use strict";
9611
+ init_clock();
9328
9612
  WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
9329
9613
  MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
9330
9614
  LB = "\0M7LB\0";
@@ -9356,6 +9640,7 @@ var init_store2 = __esm({
9356
9640
  "use strict";
9357
9641
  init_vector_store();
9358
9642
  init_prompt_render();
9643
+ init_clock();
9359
9644
  SCHEMA = `
9360
9645
  CREATE TABLE IF NOT EXISTS memcells (
9361
9646
  id TEXT PRIMARY KEY,
@@ -9642,7 +9927,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
9642
9927
  }
9643
9928
  // ── md 读(proactive 消费):开窗预测(全 owner 扫描,end>=今天)──
9644
9929
  getOpenPredictions(now, limit = 50) {
9645
- const today = new Date(now.getTime() + 8 * 36e5).toISOString().slice(0, 10);
9930
+ const today = todayKey(now);
9646
9931
  const out = [];
9647
9932
  const usersDir = join22(this.root, "users");
9648
9933
  if (!existsSync12(usersDir)) return out;
@@ -9677,28 +9962,6 @@ CREATE TABLE IF NOT EXISTS watermarks (
9677
9962
  }
9678
9963
  });
9679
9964
 
9680
- // src/mem7/core/system-filter.ts
9681
- function parseMeta2(text) {
9682
- const m = text.match(/^\[meta:\s*(.+?)\s*\((.+?)\)\s*@(\S+?)\s*(?:[#\d]|\])/);
9683
- if (!m) return null;
9684
- const platform2 = m[3].replace(/[#\d]+$/, "").trim() || void 0;
9685
- return { senderName: m[1].trim(), senderId: m[2].trim(), platform: platform2 };
9686
- }
9687
- function isSystemUserMessage(meta) {
9688
- if (!meta) return true;
9689
- return SYSTEM_PLATFORMS.has(meta.platform ?? "");
9690
- }
9691
- var SYSTEM_PLATFORMS;
9692
- var init_system_filter = __esm({
9693
- "src/mem7/core/system-filter.ts"() {
9694
- "use strict";
9695
- SYSTEM_PLATFORMS = /* @__PURE__ */ new Set([
9696
- "wx_monitor"
9697
- // 微信巡检推送(翀哥 0916 点名:"cron、wx-monitor 这种问答都不记")
9698
- ]);
9699
- }
9700
- });
9701
-
9702
9965
  // src/mem7/core/ingest.ts
9703
9966
  function contentText(content) {
9704
9967
  if (typeof content === "string") return content;
@@ -9707,7 +9970,8 @@ function contentText(content) {
9707
9970
  }
9708
9971
  return "";
9709
9972
  }
9710
- function normalizeMessages(rawMessages, agentName) {
9973
+ function normalizeMessages(rawMessages, agentName, identity, ownerMode = "agent") {
9974
+ const ownerId = identity || agentName;
9711
9975
  const out = [];
9712
9976
  let lastUserWasSystem = false;
9713
9977
  for (let rawIdx = 0; rawIdx < rawMessages.length; rawIdx++) {
@@ -9722,19 +9986,19 @@ function normalizeMessages(rawMessages, agentName) {
9722
9986
  let meta = null;
9723
9987
  if (role === "assistant" && lastUserWasSystem) continue;
9724
9988
  if (role === "user") {
9725
- meta = parseMeta2(text);
9989
+ meta = parseMeta(text);
9726
9990
  if (isSystemUserMessage(meta)) {
9727
9991
  lastUserWasSystem = true;
9728
9992
  continue;
9729
9993
  }
9730
9994
  lastUserWasSystem = false;
9731
9995
  senderName = meta.senderName;
9732
- senderId = meta.senderId;
9996
+ senderId = ownerMode === "sender" ? meta.senderId : ownerId;
9733
9997
  text = stripInjectedContext(text.replace(/^\[meta:[^\]]*\]\s*/, ""));
9734
9998
  if (!text) continue;
9735
9999
  } else {
9736
10000
  senderName = agentName;
9737
- senderId = agentName;
10001
+ senderId = ownerId;
9738
10002
  }
9739
10003
  const tsMs = typeof m.timestamp === "number" ? m.timestamp : Date.parse(String(m.timestamp ?? "")) || Date.now();
9740
10004
  out.push({ id: `m_${tsMs}_${String(rawIdx).padStart(3, "0")}`, role, senderId, senderName, platform: meta?.platform, text, tsMs });
@@ -9891,7 +10155,8 @@ async function extractEpisode(cell, provider, model) {
9891
10155
  for await (const chunk of provider.streamChat({
9892
10156
  model,
9893
10157
  messages: [{ role: "user", content: userPrompt }],
9894
- temperature: 0.3
10158
+ // 0(everalgo episode callsite 不传 temperature → LLM 默认 0.0);0.3 是 foresight 的参数,别混
10159
+ temperature: 0
9895
10160
  })) {
9896
10161
  if (chunk.type === "text" && chunk.text) text += chunk.text;
9897
10162
  if (chunk.type === "error") throw new Error(chunk.error ?? "LLM stream error");
@@ -9933,13 +10198,10 @@ var init_episode = __esm({
9933
10198
  import { readFileSync as readFileSync17 } from "node:fs";
9934
10199
  import { join as join25, dirname as dirname4 } from "node:path";
9935
10200
  import { fileURLToPath as fileURLToPath4 } from "node:url";
9936
- async function extractAtomicFacts(episode, cellTsMs, provider, model) {
9937
- const episodeText = `## ${episode.title}
9938
-
9939
- ${episode.content}`;
9940
- const userPrompt = renderLiteral(PROMPT4, {
9941
- EPISODE_TEXT: episodeText,
9942
- TIME: formatAtomicFactTime(cellTsMs)
10201
+ async function extractAtomicFacts(cell, provider, model) {
10202
+ const userPrompt = renderPrompt(PROMPT4, {
10203
+ INPUT_TEXT: renderConversationText(cell.messages),
10204
+ TIME: formatAtomicFactTime(cell.startTs)
9943
10205
  });
9944
10206
  for (let attempt = 0; attempt < RETRIES3; attempt++) {
9945
10207
  try {
@@ -9947,15 +10209,16 @@ ${episode.content}`;
9947
10209
  for await (const chunk of provider.streamChat({
9948
10210
  model,
9949
10211
  messages: [{ role: "user", content: userPrompt }],
9950
- temperature: 0.2
10212
+ temperature: 0
9951
10213
  })) {
9952
10214
  if (chunk.type === "text" && chunk.text) text += chunk.text;
9953
10215
  if (chunk.type === "error") throw new Error(chunk.error ?? "LLM stream error");
9954
10216
  }
9955
10217
  const obj = extractJson(text);
9956
10218
  const block = obj?.event_log ?? obj?.atomic_facts;
9957
- const list2 = block?.atomic_fact;
9958
- if (!Array.isArray(list2)) throw new Error("event_log/atomic_facts.atomic_fact not a list");
10219
+ if (!block || typeof block.time !== "string" || !block.time.trim()) throw new Error("atomic_facts.time missing");
10220
+ const list2 = block.atomic_fact;
10221
+ if (!Array.isArray(list2)) throw new Error("atomic_facts.atomic_fact not a list");
9959
10222
  return list2.filter((f) => typeof f === "string" && f.trim().length > 0).map((f) => f.trim());
9960
10223
  } catch {
9961
10224
  }
@@ -9968,8 +10231,8 @@ var init_atomic_facts = __esm({
9968
10231
  "use strict";
9969
10232
  init_episode();
9970
10233
  init_prompt_render();
9971
- RETRIES3 = 2;
9972
- PROMPT4 = readFileSync17(join25(dirname4(fileURLToPath4(import.meta.url)), "prompts", "atomic-facts.md"), "utf8");
10234
+ RETRIES3 = 5;
10235
+ PROMPT4 = readFileSync17(join25(dirname4(fileURLToPath4(import.meta.url)), "prompts", "atomic-facts-memcell.md"), "utf8");
9973
10236
  }
9974
10237
  });
9975
10238
 
@@ -9988,20 +10251,20 @@ function cleanDateString(dateStr) {
9988
10251
  }
9989
10252
  function calcEndFromDuration(startDate, durationDays) {
9990
10253
  const d = new Date(Date.parse(startDate + "T00:00:00Z") + durationDays * 864e5);
9991
- return Number.isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
10254
+ return Number.isNaN(d.getTime()) ? null : todayKey(d);
9992
10255
  }
9993
10256
  function calcDurationDays(startDate, endDate) {
9994
10257
  const ms = Date.parse(endDate + "T00:00:00Z") - Date.parse(startDate + "T00:00:00Z");
9995
10258
  return Number.isNaN(ms) ? null : Math.round(ms / 864e5);
9996
10259
  }
9997
10260
  function cellDateCst(cell) {
9998
- return new Date(cell.startTs + 8 * 36e5).toISOString().slice(0, 10);
10261
+ return todayKey(cell.startTs);
9999
10262
  }
10000
10263
  async function extractForesights(cell, ownerId, provider, model) {
10001
10264
  const userSenders = cell.messages.filter((m) => m.role === "user");
10002
10265
  const owner = userSenders.find((m) => m.senderId === ownerId) ?? userSenders[0];
10003
10266
  const userName = owner?.senderName ?? ownerId;
10004
- const conversationText = cell.messages.map((m) => `[${formatMessageTimestamp(m.tsMs)}] ${m.senderName ?? m.senderId}: ${m.text}`).join("\n");
10267
+ const conversationText = renderConversationText(cell.messages);
10005
10268
  const userPrompt = renderPrompt(PROMPT5, { USER_ID: ownerId, USER_NAME: userName, CONVERSATION_TEXT: conversationText });
10006
10269
  let raw = null;
10007
10270
  for (let attempt = 0; attempt < RETRIES4; attempt++) {
@@ -10054,6 +10317,7 @@ var init_foresight = __esm({
10054
10317
  "use strict";
10055
10318
  init_episode();
10056
10319
  init_prompt_render();
10320
+ init_clock();
10057
10321
  RETRIES4 = 5;
10058
10322
  MAX_COUNT = 10;
10059
10323
  MIN_COUNT = 4;
@@ -10064,11 +10328,11 @@ var init_foresight = __esm({
10064
10328
 
10065
10329
  // src/mem7/core/pipeline.ts
10066
10330
  function dateCst(tsMs) {
10067
- return new Date(tsMs + 8 * 36e5).toISOString().slice(0, 10);
10331
+ return todayKey(tsMs);
10068
10332
  }
10069
10333
  async function runMem7Pipeline(args) {
10070
- const { sessionId, store, provider, model, agentName } = args;
10071
- const all = normalizeMessages(args.rawMessages, agentName);
10334
+ const { sessionId, store, provider, model, agentName, identity, ownerMode } = args;
10335
+ const all = normalizeMessages(args.rawMessages, agentName, identity, ownerMode);
10072
10336
  const fresh = readFresh(store, sessionId, all);
10073
10337
  if (fresh.length === 0) return;
10074
10338
  const byId = /* @__PURE__ */ new Map();
@@ -10140,7 +10404,7 @@ async function runMem7Pipeline(args) {
10140
10404
  }
10141
10405
  }
10142
10406
  })(),
10143
- // 支 2:episode → atomic-facts
10407
+ // 支 2:episode(memcell → episode,一次调用 + per-owner fan-out,everos 同款)
10144
10408
  (async () => {
10145
10409
  try {
10146
10410
  const ep = await extractEpisode(cell, provider, model);
@@ -10162,26 +10426,34 @@ async function runMem7Pipeline(args) {
10162
10426
  }]);
10163
10427
  embedItems.push({ contentHash: contentHash(ep.content), kind: "episode", refDate: dateStr, ownerId, content: ep.content });
10164
10428
  }
10165
- const facts = await extractAtomicFacts(ep, cell.startTs, provider, model);
10429
+ store.updateMemcellStatus(cell.id, "extracted");
10430
+ } catch (err) {
10431
+ console.warn(`[mem7] episode failed (cell=${cell.id}): ${err.message}`);
10432
+ store.updateMemcellStatus(cell.id, "skipped");
10433
+ }
10434
+ })(),
10435
+ // 支 3:atomic-facts(memcell → 事实,**与 episode 平级**)
10436
+ // 0918 对齐 everos:`extract_atomic_facts.py:79` 直接吃 memcell,不经 episode。
10437
+ // 此前挂在 episode 下游(二次压缩)+ 用 from_text 版 prompt(无语言规则 → 英文事实)
10438
+ (async () => {
10439
+ try {
10440
+ const facts = await extractAtomicFacts(cell, provider, model);
10166
10441
  factsOut = facts;
10167
- if (facts.length > 0) {
10168
- for (const ownerId of userSenders) {
10169
- store.appendAtomicFacts(ownerId, dateStr, [{
10170
- ownerId,
10171
- sessionId,
10172
- time: new Date(cell.startTs).toISOString(),
10173
- parentId: cell.id,
10174
- facts
10175
- }]);
10176
- for (const f of facts) {
10177
- embedItems.push({ contentHash: contentHash(f), kind: "atomic_fact", refDate: dateStr, ownerId, content: f });
10178
- }
10442
+ if (facts.length === 0) return;
10443
+ for (const ownerId of userSenders) {
10444
+ store.appendAtomicFacts(ownerId, dateStr, [{
10445
+ ownerId,
10446
+ sessionId,
10447
+ time: new Date(cell.startTs).toISOString(),
10448
+ parentId: cell.id,
10449
+ facts
10450
+ }]);
10451
+ for (const f of facts) {
10452
+ embedItems.push({ contentHash: contentHash(f), kind: "atomic_fact", refDate: dateStr, ownerId, content: f });
10179
10453
  }
10180
10454
  }
10181
- store.updateMemcellStatus(cell.id, "extracted");
10182
10455
  } catch (err) {
10183
- console.warn(`[mem7] episode cascade failed (cell=${cell.id}): ${err.message}`);
10184
- store.updateMemcellStatus(cell.id, "skipped");
10456
+ console.warn(`[mem7] atomic-facts failed (cell=${cell.id}): ${err.message}`);
10185
10457
  }
10186
10458
  })()
10187
10459
  ]);
@@ -10217,6 +10489,7 @@ var init_pipeline = __esm({
10217
10489
  init_episode();
10218
10490
  init_atomic_facts();
10219
10491
  init_foresight();
10492
+ init_clock();
10220
10493
  }
10221
10494
  });
10222
10495
 
@@ -10259,28 +10532,23 @@ function parseProactiveConfig(raw) {
10259
10532
  try {
10260
10533
  parsed = parseModelRef(modelRef);
10261
10534
  } catch (err) {
10262
- console.warn(`[proactive] invalid model ref "${modelRef}" (${err.message}) \u2014 falling back to ${DEFAULT_MODEL_REF}`);
10535
+ console.warn(`[proactive] model "${modelRef}" \u975E\u6CD5\uFF08${err.message}\uFF09\u2014\u2014 \u6539\u7528\u4E3B\u6A21\u578B`);
10263
10536
  }
10264
10537
  }
10265
- if (!parsed) {
10266
- parsed = parseModelRef(DEFAULT_MODEL_REF);
10267
- }
10268
10538
  return {
10269
10539
  enabled: obj.enabled === true || obj.enabled === "yes" || obj.enabled === 1,
10270
- modelRef: parsed.providerId + "/" + parsed.modelId,
10271
- providerId: parsed.providerId,
10272
- modelId: parsed.modelId
10540
+ modelRef: parsed ? `${parsed.providerId}/${parsed.modelId}` : "",
10541
+ providerId: parsed?.providerId ?? "",
10542
+ modelId: parsed?.modelId ?? ""
10273
10543
  };
10274
10544
  }
10275
10545
  function proactiveDbPath(workspace) {
10276
10546
  return join27(workspace, ".proactive", "proactive.db");
10277
10547
  }
10278
- var DEFAULT_MODEL_REF;
10279
10548
  var init_config3 = __esm({
10280
10549
  "src/memory/proactive/config.ts"() {
10281
10550
  "use strict";
10282
10551
  init_loader();
10283
- DEFAULT_MODEL_REF = "minimax/MiniMax-M3";
10284
10552
  }
10285
10553
  });
10286
10554
 
@@ -10640,18 +10908,13 @@ function rowToAction(r) {
10640
10908
  }
10641
10909
  function normalizeToUtcIso(s) {
10642
10910
  if (!s) return s;
10643
- if (/Z$/i.test(s)) return s;
10644
- if (/[+-]\d{2}:?\d{2}$/.test(s)) {
10645
- const ms2 = Date.parse(s);
10646
- return Number.isNaN(ms2) ? s : new Date(ms2).toISOString();
10647
- }
10648
- const ms = Date.parse(s + TZ_BEIJING);
10649
- return Number.isNaN(ms) ? s : new Date(ms).toISOString();
10911
+ return parseWall(s);
10650
10912
  }
10651
- var SCHEMA2, Persistence, TZ_BEIJING;
10913
+ var SCHEMA2, Persistence;
10652
10914
  var init_persistence = __esm({
10653
10915
  "src/memory/proactive/persistence.ts"() {
10654
10916
  "use strict";
10917
+ init_clock();
10655
10918
  SCHEMA2 = `
10656
10919
  CREATE TABLE IF NOT EXISTS nodes (
10657
10920
  id TEXT PRIMARY KEY,
@@ -11065,6 +11328,27 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
11065
11328
  const row = this.db.prepare("SELECT * FROM actions WHERE id=?").get(id);
11066
11329
  return row ? rowToAction(row) : void 0;
11067
11330
  }
11331
+ /** proactive_status 工具用:按状态列 action(按排期升序,最多 30 条)。 */
11332
+ listActions(filter) {
11333
+ const where = filter === "open" ? "status IN ('queued','executing')" : "status NOT IN ('cancelled')";
11334
+ const rows = this.db.prepare(
11335
+ `SELECT * FROM actions WHERE ${where} ORDER BY scheduled_time ASC LIMIT 30`
11336
+ ).all();
11337
+ return rows.map(rowToAction);
11338
+ }
11339
+ /** proactive_resolve_intent 工具用:读 intent 节点的 data(不存在返回 null)。 */
11340
+ getIntentData(intentId) {
11341
+ const row = this.db.prepare("SELECT data FROM nodes WHERE id=? AND type=?").get(intentId, "intent");
11342
+ if (!row) return null;
11343
+ try {
11344
+ return JSON.parse(row.data);
11345
+ } catch {
11346
+ return {};
11347
+ }
11348
+ }
11349
+ setIntentData(intentId, data) {
11350
+ this.db.prepare("UPDATE nodes SET data=? WHERE id=?").run(JSON.stringify(data), intentId);
11351
+ }
11068
11352
  /** 周指标统计(验证标准用:完成数/cancelled 率) */
11069
11353
  actionStats(sinceIso) {
11070
11354
  const row = this.db.prepare(
@@ -11076,7 +11360,6 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
11076
11360
  return { total: row.total ?? 0, completed: row.completed ?? 0, cancelled: row.cancelled ?? 0 };
11077
11361
  }
11078
11362
  };
11079
- TZ_BEIJING = "+08:00";
11080
11363
  }
11081
11364
  });
11082
11365
 
@@ -11864,7 +12147,8 @@ function buildUserPrompt(event2, contextNodes, mode, foresightHits) {
11864
12147
  const foresightSection = foresightHits && foresightHits.length > 0 ? `
11865
12148
  ## Related Predictions (foresight \u2014 made earlier, window still open; matched this message by terms)
11866
12149
 
11867
- These dormant predictions just got related to this event. If acting on one NOW is meaningfully better than later (\u8D81\u70ED/\u987A\u4FBF), output an opportunity for it.
12150
+ These dormant predictions were surfaced by **mechanical keyword matching** \u2014 some may be irrelevant; ignore them freely.
12151
+ If acting on one NOW is meaningfully better than later (\u8D81\u70ED/\u987A\u4FBF), output an opportunity for it.
11868
12152
 
11869
12153
  ${foresightHits.join("\n")}
11870
12154
  ` : "";
@@ -12296,10 +12580,14 @@ If deadline has no time context, fall back to normal timing rules.
12296
12580
 
12297
12581
  // src/calendar/helpers.ts
12298
12582
  function nowIso() {
12299
- return new Date(Date.now() + TZ_OFFSET_MS2 - (/* @__PURE__ */ new Date()).getTimezoneOffset() * 6e4).toISOString().replace("Z", "+08:00");
12583
+ return toUtc(/* @__PURE__ */ new Date());
12300
12584
  }
12301
- function nowBeijing() {
12302
- return new Date(Date.now() + TZ_OFFSET_MS2);
12585
+ function nowParts() {
12586
+ return wallParts();
12587
+ }
12588
+ function wallMs(y, mo, d, hh, mm) {
12589
+ const z = (n) => String(n).padStart(2, "0");
12590
+ return Date.parse(parseWall(`${y}-${z(mo)}-${z(d)}T${z(hh)}:${z(mm)}:00`));
12303
12591
  }
12304
12592
  function parseDateRange(dateStr) {
12305
12593
  const s = dateStr.replace("\u524D", "");
@@ -12321,32 +12609,29 @@ function parseDateRange(dateStr) {
12321
12609
  }
12322
12610
  }
12323
12611
  function isToday(month, dStart, dEnd) {
12324
- const now = nowBeijing();
12325
- return month === now.getUTCMonth() + 1 && dStart <= now.getUTCDate() && now.getUTCDate() <= dEnd;
12612
+ const p = nowParts();
12613
+ return month === p.mo && dStart <= p.d && p.d <= dEnd;
12326
12614
  }
12327
12615
  function isOverdue(month, dStart, dEnd) {
12328
- const now = nowBeijing();
12329
- const curMonth = now.getUTCMonth() + 1;
12330
- const curDay = now.getUTCDate();
12331
- return month < curMonth || month === curMonth && dEnd < curDay;
12616
+ const p = nowParts();
12617
+ return month < p.mo || month === p.mo && dEnd < p.d;
12332
12618
  }
12333
12619
  function getTodayWeekday() {
12334
- const now = nowBeijing();
12335
- return WEEKDAYS2[now.getUTCDay() === 0 ? 6 : now.getUTCDay() - 1];
12620
+ const p = nowParts();
12621
+ return WEEKDAYS2[p.weekday === 0 ? 6 : p.weekday - 1];
12336
12622
  }
12337
12623
  function computeRemindAt(dateStr, timeExact, remindBeforeMin) {
12338
12624
  const parsed = parseDateRange(dateStr);
12339
12625
  if (!parsed) return null;
12340
12626
  const { month, dStart } = parsed;
12341
- const now = nowBeijing();
12342
- const year = now.getUTCFullYear();
12627
+ const year = nowParts().y;
12343
12628
  let eventMs;
12344
12629
  if (timeExact) {
12345
12630
  const [hh, mm] = timeExact.split(":").map(Number);
12346
12631
  if (isNaN(hh) || isNaN(mm)) return null;
12347
- eventMs = Date.UTC(year, month - 1, dStart, hh - 8, mm);
12632
+ eventMs = wallMs(year, month, dStart, hh, mm);
12348
12633
  } else {
12349
- eventMs = Date.UTC(year, month - 1, dStart, 9 - 8, 0);
12634
+ eventMs = wallMs(year, month, dStart, 9, 0);
12350
12635
  }
12351
12636
  const remindMs = eventMs - remindBeforeMin * 60 * 1e3;
12352
12637
  return new Date(remindMs).toISOString();
@@ -12354,20 +12639,19 @@ function computeRemindAt(dateStr, timeExact, remindBeforeMin) {
12354
12639
  function computeWeeklyRemindAt(day, time, remindBeforeMin) {
12355
12640
  const targetIdx = WEEKDAYS2.indexOf(day);
12356
12641
  if (targetIdx === -1) return null;
12357
- const now = nowBeijing();
12358
- const curIdx = now.getUTCDay() === 0 ? 6 : now.getUTCDay() - 1;
12642
+ const p = nowParts();
12643
+ const curIdx = p.weekday === 0 ? 6 : p.weekday - 1;
12359
12644
  let dayDiff = targetIdx - curIdx;
12360
12645
  if (dayDiff < 0) dayDiff += 7;
12361
- const year = now.getUTCFullYear();
12362
- const month = now.getUTCMonth();
12363
- const date = now.getUTCDate() + dayDiff;
12646
+ const target = wallParts(Date.parse(startOfLocalDay()) + dayDiff * 24 * 60 * 60 * 1e3);
12647
+ const year = target.y, month = target.mo, date = target.d;
12364
12648
  let eventMs;
12365
12649
  if (time && time !== "\u5168\u5929") {
12366
12650
  const [hh, mm] = time.split(":").map(Number);
12367
12651
  if (isNaN(hh) || isNaN(mm)) return null;
12368
- eventMs = Date.UTC(year, month, date, hh - 8, mm);
12652
+ eventMs = wallMs(year, month, date, hh, mm);
12369
12653
  } else {
12370
- eventMs = Date.UTC(year, month, date, 9 - 8, 0);
12654
+ eventMs = wallMs(year, month, date, 9, 0);
12371
12655
  }
12372
12656
  let remindMs = eventMs - remindBeforeMin * 60 * 1e3;
12373
12657
  if (remindMs <= Date.now()) {
@@ -12382,11 +12666,8 @@ function computeWeeklyRemindAt(day, time, remindBeforeMin) {
12382
12666
  function computeDailyRemindAt(time, remindBeforeMin) {
12383
12667
  const [hh, mm] = time.split(":").map(Number);
12384
12668
  if (isNaN(hh) || isNaN(mm)) return null;
12385
- const now = nowBeijing();
12386
- const year = now.getUTCFullYear();
12387
- const month = now.getUTCMonth();
12388
- const date = now.getUTCDate();
12389
- let eventMs = Date.UTC(year, month, date, hh - 8, mm);
12669
+ const p = nowParts();
12670
+ let eventMs = wallMs(p.y, p.mo, p.d, hh, mm);
12390
12671
  let remindMs = eventMs - remindBeforeMin * 60 * 1e3;
12391
12672
  if (remindMs <= Date.now()) {
12392
12673
  remindMs += 24 * 60 * 60 * 1e3;
@@ -12396,18 +12677,17 @@ function computeDailyRemindAt(time, remindBeforeMin) {
12396
12677
  function computeWeekdaysRemindAt(time, remindBeforeMin) {
12397
12678
  const [hh, mm] = time.split(":").map(Number);
12398
12679
  if (isNaN(hh) || isNaN(mm)) return null;
12399
- const now = nowBeijing();
12400
- let year = now.getUTCFullYear();
12401
- let month = now.getUTCMonth();
12402
- let date = now.getUTCDate();
12403
- let curDay = now.getUTCDay();
12404
- let eventMs = Date.UTC(year, month, date, hh - 8, mm);
12405
- let remindMs = eventMs - remindBeforeMin * 60 * 1e3;
12406
- while (remindMs <= Date.now() || curDay === 0 || curDay === 6) {
12407
- date++;
12408
- curDay = (curDay + 1) % 7;
12409
- eventMs = Date.UTC(year, month, date, hh - 8, mm);
12680
+ let dayOffset = 0;
12681
+ let eventMs;
12682
+ let remindMs;
12683
+ let curDay;
12684
+ for (; ; ) {
12685
+ const t = wallParts(Date.parse(startOfLocalDay()) + dayOffset * 24 * 60 * 60 * 1e3);
12686
+ curDay = t.weekday;
12687
+ eventMs = wallMs(t.y, t.mo, t.d, hh, mm);
12410
12688
  remindMs = eventMs - remindBeforeMin * 60 * 1e3;
12689
+ if (remindMs > Date.now() && curDay !== 0 && curDay !== 6) break;
12690
+ dayOffset++;
12411
12691
  }
12412
12692
  return new Date(remindMs).toISOString();
12413
12693
  }
@@ -12430,13 +12710,7 @@ function fmtRow(row) {
12430
12710
  }
12431
12711
  }
12432
12712
  function fmtNow() {
12433
- const now = nowBeijing();
12434
- const y = now.getUTCFullYear();
12435
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
12436
- const d = String(now.getUTCDate()).padStart(2, "0");
12437
- const hh = String(now.getUTCHours()).padStart(2, "0");
12438
- const mm = String(now.getUTCMinutes()).padStart(2, "0");
12439
- return `${y}-${m}-${d} ${getTodayWeekday()} ${hh}:${mm}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`;
12713
+ return formatForLlm();
12440
12714
  }
12441
12715
  function parseDuration(s) {
12442
12716
  if (s.endsWith("m")) return parseInt(s.slice(0, -1)) || null;
@@ -12513,9 +12787,9 @@ function getUpcomingTasks(workspace, days = 7, maxRows = 8) {
12513
12787
  "SELECT * FROM events WHERE type='task' AND status='pending'"
12514
12788
  ).all();
12515
12789
  if (rows.length === 0) return "";
12516
- const now = nowBeijing();
12517
- const year = now.getUTCFullYear();
12518
- const todayMs = Date.UTC(year, now.getUTCMonth(), now.getUTCDate());
12790
+ const pt = nowParts();
12791
+ const year = pt.y;
12792
+ const todayMs = Date.parse(startOfLocalDay());
12519
12793
  const horizonMs = todayMs + days * 864e5;
12520
12794
  const picked = [];
12521
12795
  for (const r of rows) {
@@ -12537,18 +12811,18 @@ function getUpcomingTasks(workspace, days = 7, maxRows = 8) {
12537
12811
  return "";
12538
12812
  }
12539
12813
  }
12540
- var TZ_OFFSET_MS2, WEEKDAYS2;
12814
+ var WEEKDAYS2;
12541
12815
  var init_helpers = __esm({
12542
12816
  "src/calendar/helpers.ts"() {
12543
12817
  "use strict";
12544
12818
  init_db();
12545
- TZ_OFFSET_MS2 = 8 * 60 * 60 * 1e3;
12819
+ init_clock();
12546
12820
  WEEKDAYS2 = ["\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D", "\u5468\u65E5"];
12547
12821
  }
12548
12822
  });
12549
12823
 
12550
12824
  // src/calendar/commands.ts
12551
- import { existsSync as existsSync13, statSync as statSync4 } from "node:fs";
12825
+ import { existsSync as existsSync13, statSync as statSync3 } from "node:fs";
12552
12826
  import { isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
12553
12827
  function fmtEnd(start, durationMin) {
12554
12828
  const [hh, mm] = start.split(":").map(Number);
@@ -12628,8 +12902,7 @@ function addDate(db2, args) {
12628
12902
  const parsed = parseDateRange(dateStr);
12629
12903
  let weeklyRows = [];
12630
12904
  if (parsed) {
12631
- const now = new Date(Date.now() + 8 * 60 * 60 * 1e3);
12632
- const year = now.getUTCFullYear();
12905
+ const year = wallParts().y;
12633
12906
  const d = new Date(Date.UTC(year, parsed.month - 1, parsed.dStart));
12634
12907
  const weekday = WEEKDAYS3[d.getUTCDay() === 0 ? 6 : d.getUTCDay() - 1];
12635
12908
  weeklyRows = db2.prepare(
@@ -12678,8 +12951,7 @@ function addBatch(db2, args) {
12678
12951
  const parsed = parseDateRange(dateStr);
12679
12952
  let weeklyRows = [];
12680
12953
  if (parsed) {
12681
- const now = new Date(Date.now() + 8 * 60 * 60 * 1e3);
12682
- const year = now.getUTCFullYear();
12954
+ const year = wallParts().y;
12683
12955
  const d = new Date(Date.UTC(year, parsed.month - 1, parsed.dStart));
12684
12956
  const weekday = WEEKDAYS3[d.getUTCDay() === 0 ? 6 : d.getUTCDay() - 1];
12685
12957
  weeklyRows = db2.prepare(
@@ -12725,7 +12997,7 @@ function addTask(db2, args) {
12725
12997
  \u89E3\u6790\u540E: ${absPath}
12726
12998
  \u8BF7\u5148\u521B\u5EFA\u6587\u6863\u518D add-task\u3002`;
12727
12999
  }
12728
- const stat4 = statSync4(absPath);
13000
+ const stat4 = statSync3(absPath);
12729
13001
  if (!stat4.isFile()) {
12730
13002
  return `\u274C \u521B\u5EFA\u5931\u8D25\uFF1Adoc_path \u4E0D\u662F\u6587\u4EF6\uFF08\u53EF\u80FD\u662F\u76EE\u5F55\uFF09
12731
13003
  \u8DEF\u5F84: ${docPath}`;
@@ -12795,7 +13067,7 @@ function list(db2, opts = {}) {
12795
13067
  }
12796
13068
  for (const r of db2.prepare("SELECT * FROM events WHERE type='daily' AND status='pending'").all()) {
12797
13069
  if (r.repeat_type === "weekdays") {
12798
- const idx = new Date(Date.now() + 8 * 60 * 60 * 1e3).getUTCDay();
13070
+ const idx = wallParts().weekday;
12799
13071
  if (idx === 0 || idx === 6) continue;
12800
13072
  }
12801
13073
  events.push(` #${r.id} ${r.time || ""} ${r.event}`);
@@ -12823,7 +13095,7 @@ function pending(db2) {
12823
13095
  }
12824
13096
  for (const r of db2.prepare("SELECT * FROM events WHERE type='daily' AND status='pending'").all()) {
12825
13097
  if (r.repeat_type === "weekdays") {
12826
- const idx = new Date(Date.now() + 8 * 60 * 60 * 1e3).getUTCDay();
13098
+ const idx = wallParts().weekday;
12827
13099
  if (idx === 0 || idx === 6) continue;
12828
13100
  }
12829
13101
  const label = r.repeat_type === "weekdays" ? "[\u5DE5\u4F5C\u65E5]" : "[\u6BCF\u5929]";
@@ -12939,10 +13211,36 @@ function markReminded(db2, id) {
12939
13211
  }
12940
13212
  return `#${id} \u5DF2\u6807\u8BB0\u63D0\u9192\u8FC7`;
12941
13213
  }
13214
+ function listEventsForInjection(db2, limit = 50) {
13215
+ return db2.prepare(
13216
+ `SELECT id, event, date_str, time_exact, type, status
13217
+ FROM events
13218
+ WHERE status NOT IN ('deleted', 'archived', 'done')
13219
+ AND date_str IS NOT NULL AND date_str != ''
13220
+ ORDER BY date_str ASC LIMIT ?`
13221
+ ).all(limit);
13222
+ }
13223
+ function listEventBriefs(db2, limit) {
13224
+ const rows = db2.prepare(
13225
+ `SELECT id, event, date_str, time_exact, type
13226
+ FROM events
13227
+ WHERE status NOT IN ('deleted', 'archived', 'done')
13228
+ ORDER BY date_str ASC, time_exact ASC
13229
+ LIMIT ?`
13230
+ ).all(limit);
13231
+ return rows.map((row) => ({
13232
+ id: Number(row.id),
13233
+ title: String(row.event ?? ""),
13234
+ date: String(row.date_str ?? ""),
13235
+ time: String(row.time_exact ?? ""),
13236
+ type: String(row.type ?? "")
13237
+ })).filter((row) => row.title.length > 0);
13238
+ }
12942
13239
  var WEEKDAYS3;
12943
13240
  var init_commands = __esm({
12944
13241
  "src/calendar/commands.ts"() {
12945
13242
  "use strict";
13243
+ init_clock();
12946
13244
  init_helpers();
12947
13245
  WEEKDAYS3 = ["\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D", "\u5468\u65E5"];
12948
13246
  }
@@ -12950,10 +13248,9 @@ var init_commands = __esm({
12950
13248
 
12951
13249
  // src/memory/proactive/calendar-bridge.ts
12952
13250
  function toBeijingParts(iso) {
12953
- const ms = Date.parse(iso);
12954
- const beijing = new Date(ms + TZ_OFFSET_MS3);
12955
- const dateStr = `${beijing.getUTCMonth() + 1}/${beijing.getUTCDate()}`;
12956
- const timeExact = `${String(beijing.getUTCHours()).padStart(2, "0")}:${String(beijing.getUTCMinutes()).padStart(2, "0")}`;
13251
+ const b = wallParts(iso);
13252
+ const dateStr = `${b.mo}/${b.d}`;
13253
+ const timeExact = `${String(b.hh).padStart(2, "0")}:${String(b.mm).padStart(2, "0")}`;
12957
13254
  return { dateStr, timeExact };
12958
13255
  }
12959
13256
  function parseCalendarDateMs(dateStr, now) {
@@ -12962,7 +13259,7 @@ function parseCalendarDateMs(dateStr, now) {
12962
13259
  const month = parseInt(parts[0], 10);
12963
13260
  const day = parseInt(parts[1], 10);
12964
13261
  if (!Number.isFinite(month) || !Number.isFinite(day)) return null;
12965
- let year = now.getFullYear();
13262
+ let year = wallParts(now).y;
12966
13263
  let candidate = new Date(year, month - 1, day).getTime();
12967
13264
  if (candidate < now.getTime() - 7 * 864e5) {
12968
13265
  year += 1;
@@ -12972,13 +13269,7 @@ function parseCalendarDateMs(dateStr, now) {
12972
13269
  }
12973
13270
  function injectCalendarEvents(store, calDb, days = 30, now = /* @__PURE__ */ new Date()) {
12974
13271
  const cutoffMs = now.getTime() + days * 864e5;
12975
- const rows = calDb.prepare(
12976
- `SELECT id, event, date_str, time_exact, type, status
12977
- FROM events
12978
- WHERE status NOT IN ('deleted', 'archived', 'done')
12979
- AND date_str IS NOT NULL AND date_str != ''
12980
- ORDER BY date_str ASC LIMIT 50`
12981
- ).all();
13272
+ const rows = listEventsForInjection(calDb, 50);
12982
13273
  if (rows.length === 0) return 0;
12983
13274
  let injected = 0;
12984
13275
  for (const row of rows) {
@@ -13021,27 +13312,108 @@ function injectCalendarEvents(store, calDb, days = 30, now = /* @__PURE__ */ new
13021
13312
  return injected;
13022
13313
  }
13023
13314
  function getCalendarCoverage(calDb, limit = 100) {
13024
- const rows = calDb.prepare(
13025
- `SELECT id, event, date_str, time_exact, type
13026
- FROM events
13027
- WHERE status NOT IN ('deleted', 'archived', 'done')
13028
- ORDER BY date_str ASC, time_exact ASC
13029
- LIMIT ?`
13030
- ).all(limit);
13031
- return rows.map((row) => ({
13032
- id: Number(row.id),
13033
- title: String(row.event ?? ""),
13034
- date: String(row.date_str ?? ""),
13035
- time: String(row.time_exact ?? ""),
13036
- type: String(row.type ?? "")
13037
- })).filter((row) => row.title.length > 0);
13315
+ return listEventBriefs(calDb, limit);
13038
13316
  }
13039
- var TZ_OFFSET_MS3;
13040
13317
  var init_calendar_bridge = __esm({
13041
13318
  "src/memory/proactive/calendar-bridge.ts"() {
13042
13319
  "use strict";
13043
13320
  init_commands();
13044
- TZ_OFFSET_MS3 = 8 * 60 * 60 * 1e3;
13321
+ init_commands();
13322
+ init_clock();
13323
+ }
13324
+ });
13325
+
13326
+ // src/memory/proactive/recall-score.ts
13327
+ function weightOf(t) {
13328
+ if (CHANGE_RE.test(t)) return W.change;
13329
+ if (TIME_RE.test(t)) return W.time;
13330
+ if (EVENT_RE.test(t)) return W.event;
13331
+ if (LIFE_RE.test(t)) return W.life;
13332
+ if (ACTION_RE.test(t)) return W.action;
13333
+ if (PERSON_RE.test(t)) return W.person;
13334
+ return W.other;
13335
+ }
13336
+ function scoreForesightEntry(entry, terms) {
13337
+ const hay = `${entry.text} ${entry.evidence ?? ""}`;
13338
+ const kinds = { change: [], time: [], event: [], life: [], other: [], person: [], action: [] };
13339
+ let score = 0;
13340
+ for (const t of terms) {
13341
+ if (!hay.includes(t)) continue;
13342
+ const kind = CHANGE_RE.test(t) ? "change" : TIME_RE.test(t) ? "time" : EVENT_RE.test(t) ? "event" : LIFE_RE.test(t) ? "life" : ACTION_RE.test(t) ? "action" : PERSON_RE.test(t) ? "person" : "other";
13343
+ kinds[kind].push(t);
13344
+ score += W[kind];
13345
+ }
13346
+ return {
13347
+ score,
13348
+ matched: Object.values(kinds).flat(),
13349
+ kinds,
13350
+ hasSpecial: kinds.change.length > 0 || kinds.time.length > 0 || kinds.event.length > 0
13351
+ };
13352
+ }
13353
+ function rankForesightEntries(entries2, terms, opts = {}) {
13354
+ const minScore = opts.minScore ?? DEFAULT_MIN_SCORE3;
13355
+ const termList = [...terms];
13356
+ const N = Math.max(1, entries2.length);
13357
+ const df = /* @__PURE__ */ new Map();
13358
+ for (const t of termList) {
13359
+ let c = 0;
13360
+ for (const e of entries2) if (`${e.text} ${e.evidence ?? ""}`.includes(t)) c++;
13361
+ df.set(t, c);
13362
+ }
13363
+ const idf = (t) => {
13364
+ const c = df.get(t) ?? 0;
13365
+ if (c <= 0) return 0;
13366
+ return 1 + Math.log2(1 + N / c) / Math.log2(1 + N);
13367
+ };
13368
+ const out = [];
13369
+ for (const e of entries2) {
13370
+ const base = scoreForesightEntry(e, termList);
13371
+ const s = { ...base, score: termList.reduce((acc, t) => acc + (base.matched.includes(t) ? weightOf(t) * idf(t) : 0), 0) };
13372
+ if (opts.requireStructure) {
13373
+ const anchor = s.kinds.change.length > 0 || s.kinds.event.length > 0 || s.kinds.life.length > 0;
13374
+ if (!anchor) continue;
13375
+ }
13376
+ if (s.score < minScore) continue;
13377
+ const tag = (k) => s.kinds[k].length ? `${k}:${s.kinds[k].join("/")}` : "";
13378
+ out.push({ entry: e, score: s.score, reasons: [tag("change"), tag("time"), tag("event"), tag("other"), tag("person")].filter(Boolean).join(" ") });
13379
+ }
13380
+ return out.sort((a, b) => b.score - a.score);
13381
+ }
13382
+ function extractTerms(message, stopWords) {
13383
+ const terms = /* @__PURE__ */ new Set();
13384
+ for (const m of message.matchAll(TIME_SURFACE)) terms.add(m[0]);
13385
+ for (const run of message.match(/[一-鿿]{2,}|[A-Za-z0-9_-]{3,}/g) ?? []) {
13386
+ if (run.length <= 6 && !stopWords.has(run)) terms.add(run);
13387
+ if (/[一-鿿]/.test(run)) {
13388
+ for (let i = 0; i + 2 <= run.length; i++) {
13389
+ const g = run.slice(i, i + 2);
13390
+ if (!stopWords.has(g)) terms.add(g);
13391
+ }
13392
+ }
13393
+ }
13394
+ return terms;
13395
+ }
13396
+ var CHANGE_RE, TIME_RE, ACTION_RE, EVENT_RE, LIFE_RE, PERSON_RE, W, DEFAULT_MIN_SCORE3, TIME_SURFACE;
13397
+ var init_recall_score = __esm({
13398
+ "src/memory/proactive/recall-score.ts"() {
13399
+ "use strict";
13400
+ CHANGE_RE = /^(改|变成|变为|变更|改期|取消|没了|挪|调整|换|推|提前|延后|顺延|恢复|新增|加上|改成|改到|调到|排|定)$/;
13401
+ TIME_RE = /^(\d{1,2}\/\d{1,2}|\d{1,2}月|\d{1,2}日|周[一二三四五六日天]|星期[一二三四五六日天]|明天|后天|大后天|下周|上周|本周|今晚|今早|明早|明晚|早上|上午|中午|下午|晚上|凌晨|月底|月初|\d{1,2}:\d{2}|\d{1,2}[点時时])$/;
13402
+ ACTION_RE = /^(翻|翻翻|上传|搞完|搞|看看|看|发我|发给|截图|下载|跑|贴|转写|重发|试试|查|找|写|做|弄|传|发)$/;
13403
+ EVENT_RE = /^(来电|电话|打电话|打给|上门|到访|通知|审核|拜访|到货|快递|送货|生病|发烧|感冒|住院|输水|挂水|复诊|复查|面试|体检|签约|合同|付款|到账|截止|到期)$/;
13404
+ LIFE_RE = /^(回家|出差|探亲|复查|复诊|体检|住院|生日|纪念日|搬家|开学|放假|假期|寒假|暑假|机票|车票|高铁票|飞机|电影|聚餐|聚会|拜访|来访|接机|送机|考试|报名|签证|护照|续签|过户)$/;
13405
+ PERSON_RE = /^(翀哥|老公|老婆|睿阳|荣阳|张小媒|小柯|小雯|孙总|曲教授|潘总|燕子|方老师|李经理|Leigh|amy|Amy)$/;
13406
+ W = {
13407
+ change: 2,
13408
+ time: 1.5,
13409
+ event: 2,
13410
+ life: 2,
13411
+ other: 1,
13412
+ person: 0.3,
13413
+ action: 0
13414
+ };
13415
+ DEFAULT_MIN_SCORE3 = Number(process.env.RECALL_MIN_SCORE ?? 1);
13416
+ TIME_SURFACE = /\d{1,2}月\d{1,2}[日号]?|\d{1,2}号|\d{1,2}\/\d{1,2}|\d{1,2}[点时::]\d{0,2}|周[一二三四五六日天]|星期[一二三四五六日天]|明天|后天|大后天|下周|本周|上周|今晚|今早|明早|明晚|月底|月初|下个月|这个月/g;
13045
13417
  }
13046
13418
  });
13047
13419
 
@@ -13119,7 +13491,7 @@ function loadForesightEntries(everosRoot, now, userId, ownerFilter) {
13119
13491
  return entries2.slice();
13120
13492
  }
13121
13493
  function parseForesightEntries(everosRoot, now, userId) {
13122
- const today = new Date(now.getTime() + 8 * 36e5).toISOString().slice(0, 10);
13494
+ const today = todayKey(now);
13123
13495
  let files;
13124
13496
  if (userId) {
13125
13497
  const ownDir = join29(everosRoot, userId, "default_project", "users", userId, ".foresights");
@@ -13155,35 +13527,11 @@ function parseForesightEntries(everosRoot, now, userId) {
13155
13527
  }
13156
13528
  return out;
13157
13529
  }
13158
- function loadForesightLines(everosRoot, now, maxEntries = 20, userId) {
13159
- const today = new Date(now.getTime() + 8 * 36e5).toISOString().slice(0, 10);
13160
- const out = loadForesightEntries(everosRoot, now, userId);
13161
- const isOpen = (f) => f.start <= today;
13162
- out.sort((a, b) => Number(isOpen(b)) - Number(isOpen(a)) || (isOpen(a) ? a.end.localeCompare(b.end) : a.start.localeCompare(b.start)));
13163
- const open = out.filter(isOpen).slice(0, Math.min(12, Math.floor(maxEntries * 0.6)));
13164
- const upcoming = out.filter((f) => !isOpen(f)).slice(0, Math.max(0, maxEntries - open.length));
13165
- return [...open, ...upcoming].map((f) => `- [\u7A97\u53E3 ${f.start}~${f.end}] ${f.text}${f.evidence ? `\uFF08\u4F9D\u636E\uFF1A${f.evidence}\uFF09` : ""}`);
13166
- }
13167
- function matchForesightForEvent(entries2, message, maxHits = 5) {
13168
- const terms = /* @__PURE__ */ new Set();
13169
- for (const run of message.match(/[一-鿿]{2,}|[A-Za-z0-9_-]{3,}/g) ?? []) {
13170
- if (run.length <= 6 && /[一-鿿]/.test(run) && !FS_MATCH_STOP.has(run)) terms.add(run);
13171
- if (/[一-鿿]/.test(run)) {
13172
- for (let i = 0; i + 2 <= run.length; i++) {
13173
- const g = run.slice(i, i + 2);
13174
- if (!FS_MATCH_STOP.has(g)) terms.add(g);
13175
- }
13176
- }
13177
- }
13530
+ function matchForesightForEvent(entries2, message, maxHits = 2) {
13531
+ const terms = extractTerms(message, FS_MATCH_STOP);
13178
13532
  if (terms.size === 0) return [];
13179
- const hits = [];
13180
- for (const f of entries2) {
13181
- const hay = `${f.text} ${f.evidence}`;
13182
- const term = [...terms].find((t) => hay.includes(t));
13183
- if (term) hits.push({ line: `- [\u7A97\u53E3 ${f.start}~${f.end}] ${f.text}\uFF08\u4F9D\u636E\uFF1A${f.evidence}\uFF09`, term });
13184
- if (hits.length >= maxHits) break;
13185
- }
13186
- return hits.map((h) => h.line);
13533
+ const ranked = rankForesightEntries(entries2, terms);
13534
+ return ranked.slice(0, maxHits).map((r) => `- [\u7A97\u53E3 ${r.entry.start}~${r.entry.end}] ${r.entry.text}\uFF08\u4F9D\u636E\uFF1A${r.entry.evidence}\uFF09`);
13187
13535
  }
13188
13536
  function listForesightMds(dir) {
13189
13537
  try {
@@ -13323,6 +13671,8 @@ var init_opportunity = __esm({
13323
13671
  init_models();
13324
13672
  init_calendar_bridge();
13325
13673
  init_embedding();
13674
+ init_recall_score();
13675
+ init_clock();
13326
13676
  OPPORTUNITY_SCAN_INTERVAL_MS = 6 * 60 * 6e4;
13327
13677
  foresightCache = /* @__PURE__ */ new Map();
13328
13678
  FORESIGHT_CACHE_TTL_MS = 30 * 6e4;
@@ -13838,16 +14188,16 @@ var init_executor3 = __esm({
13838
14188
  const refLower = ref.toLowerCase().trim();
13839
14189
  if (!refLower) return null;
13840
14190
  if (!this.titleToIdCache || this.titleToIdCache.size < this.store.nodeCount) {
13841
- const cache2 = /* @__PURE__ */ new Map();
14191
+ const cache3 = /* @__PURE__ */ new Map();
13842
14192
  for (const node of this.store.getAllNodes()) {
13843
14193
  const title = String(node.data.title ?? "").toLowerCase().trim();
13844
- if (title) cache2.set(title, node.id);
14194
+ if (title) cache3.set(title, node.id);
13845
14195
  }
13846
- this.titleToIdCache = cache2;
14196
+ this.titleToIdCache = cache3;
13847
14197
  }
13848
- const cache = this.titleToIdCache;
13849
- if (cache.has(refLower)) return cache.get(refLower);
13850
- for (const [title, id] of cache) {
14198
+ const cache2 = this.titleToIdCache;
14199
+ if (cache2.has(refLower)) return cache2.get(refLower);
14200
+ for (const [title, id] of cache2) {
13851
14201
  if (title.includes(refLower) || refLower.includes(title)) return id;
13852
14202
  }
13853
14203
  return null;
@@ -14457,18 +14807,23 @@ ${JSON.stringify(payload, null, 2)}
14457
14807
 
14458
14808
  // src/memory/proactive/proactive-tools.ts
14459
14809
  import { existsSync as existsSync15 } from "node:fs";
14460
- import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
14461
14810
  function setProactiveWorkspace(workspace) {
14462
14811
  toolWorkspace = workspace;
14463
14812
  }
14464
- function openDb2() {
14813
+ function withStore(fn) {
14465
14814
  if (!toolWorkspace) return null;
14466
14815
  const p = proactiveDbPath(toolWorkspace);
14467
14816
  if (!existsSync15(p)) return null;
14468
- return new DatabaseSync4(p);
14817
+ const store = new Persistence(p);
14818
+ try {
14819
+ return fn(store);
14820
+ } finally {
14821
+ store.close();
14822
+ }
14469
14823
  }
14470
- function fmtRow2(r) {
14471
- return `#${r.id} [${r.status}] ${r.title} (urgency=${r.urgency}, scheduled=${r.scheduled_time}${r.calendar_id ? `, cal=#${r.calendar_id}` : ""})`;
14824
+ function fmtRow2(a) {
14825
+ const when = a.scheduledTime ? formatForLlm(a.scheduledTime) : "(\u65E0)";
14826
+ return `#${a.id} [${a.status}] ${a.title} (urgency=${a.urgency}, scheduled=${when}${a.calendarId ? `, cal=#${a.calendarId}` : ""})`;
14472
14827
  }
14473
14828
  function registerProactiveTools() {
14474
14829
  registry.register({
@@ -14483,50 +14838,43 @@ function registerProactiveTools() {
14483
14838
  required: ["action_id"]
14484
14839
  },
14485
14840
  handler: async (args) => {
14486
- const db2 = openDb2();
14487
- if (!db2) return { content: "proactive \u672A\u542F\u7528\uFF08\u65E0 workspace \u6216 .proactive/proactive.db\uFF09", isError: true };
14488
- try {
14489
- const row = db2.prepare("SELECT id, status, title FROM actions WHERE id=?").get(args.action_id);
14490
- if (!row) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728\u3002\u7528 proactive_status \u67E5\u5F53\u524D\u5217\u8868\u3002`, isError: true };
14491
- if (args.calendar_id !== void 0) {
14492
- db2.prepare("UPDATE actions SET status=?, completed_at=?, calendar_id=? WHERE id=?").run("completed", (/* @__PURE__ */ new Date()).toISOString(), args.calendar_id, args.action_id);
14493
- } else {
14494
- db2.prepare("UPDATE actions SET status=?, completed_at=? WHERE id=?").run("completed", (/* @__PURE__ */ new Date()).toISOString(), args.action_id);
14495
- }
14496
- return { content: `\u2713 completed: ${row.title}${args.calendar_id !== void 0 ? `\uFF08calendar #${args.calendar_id} \u5DF2\u56DE\u586B\uFF09` : ""}` };
14497
- } finally {
14498
- db2.close();
14499
- }
14841
+ const out = withStore((store) => {
14842
+ const a = store.getAction(String(args.action_id));
14843
+ if (!a) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728\u3002\u7528 proactive_status \u67E5\u5F53\u524D\u5217\u8868\u3002`, isError: true };
14844
+ store.updateActionStatus(a.id, "completed", { completedAt: nowUtc() });
14845
+ if (args.calendar_id !== void 0) store.linkCalendar(a.id, Number(args.calendar_id));
14846
+ return { content: `\u2713 action ${a.id} \u6807\u8BB0\u4E3A completed${args.calendar_id !== void 0 ? `\uFF08\u5173\u8054 calendar #${args.calendar_id}\uFF09` : ""}` };
14847
+ });
14848
+ return out ?? { content: "proactive \u672A\u542F\u7528\uFF08\u65E0 workspace \u6216 .proactive/proactive.db\uFF09", isError: true };
14500
14849
  }
14501
14850
  });
14502
14851
  registry.register({
14503
14852
  name: "proactive_cancel",
14504
- description: "\u6807\u8BB0\u4E00\u4E2A proactive action \u4E3A cancelled\uFF08\u8DF3\u8FC7\uFF1A\u6280\u672F\u7C7B/\u5DF2\u8FC7\u65F6/\u91CD\u590D/\u4E0D\u8BE5\u751F\u6210\uFF09\u3002\u5FC5\u987B\u7ED9\u7406\u7531\u3002",
14853
+ description: "\u53D6\u6D88\u4E00\u4E2A proactive action\uFF08\u5224\u5B9A\u4E3A\u4E0D\u8BE5\u505A/\u5DF2\u8FC7\u671F/\u9A6C\u540E\u70AE\u65F6\u8C03\u7528\uFF09\u3002",
14505
14854
  schema: {
14506
14855
  type: "object",
14507
14856
  properties: {
14508
14857
  action_id: { type: "string", description: "action id" },
14509
- reason: { type: "string", description: "\u53D6\u6D88\u7406\u7531\uFF08\u6280\u672F\u566A\u97F3/\u5DF2\u8FC7\u65F6/\u91CD\u590D/\u4E0D\u8BE5\u751F\u6210\uFF09" }
14858
+ reason: { type: "string", description: "\u53D6\u6D88\u7406\u7531\uFF08\u4F1A\u8BB0\u8FDB cancel_reason\uFF09" }
14510
14859
  },
14511
- required: ["action_id", "reason"]
14860
+ required: ["action_id"]
14512
14861
  },
14513
14862
  handler: async (args) => {
14514
- const db2 = openDb2();
14515
- if (!db2) return { content: "proactive \u672A\u542F\u7528", isError: true };
14516
- try {
14517
- const row = db2.prepare("SELECT id, title FROM actions WHERE id=?").get(args.action_id);
14518
- if (!row) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728`, isError: true };
14519
- db2.prepare("UPDATE actions SET status=?, completed_at=?, cancel_reason=? WHERE id=?").run("cancelled", (/* @__PURE__ */ new Date()).toISOString(), String(args.reason || "\u672A\u6CE8\u660E"), args.action_id);
14520
- return { content: `\u2713 cancelled: ${row.title}\uFF08${args.reason}\uFF09${/已过时|已处理|做完了|马后炮/.test(String(args.reason)) ? `
14521
- \u26A0\uFE0F \u9A6C\u540E\u70AE\uFF1F\u987A\u624B proactive_resolve_intent \u4FEE\u56FE\u2014\u2014\u628A\u6E90 intent \u6807 resolved\uFF0C\u540C\u6E90\u6392\u961F\u7684\u5144\u5F1F action \u5168\u90E8\u7EA7\u8054\u53D6\u6D88` : ""}` };
14522
- } finally {
14523
- db2.close();
14524
- }
14863
+ const out = withStore((store) => {
14864
+ const a = store.getAction(String(args.action_id));
14865
+ if (!a) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728`, isError: true };
14866
+ store.updateActionStatus(a.id, "cancelled", {
14867
+ completedAt: nowUtc(),
14868
+ cancelReason: String(args.reason || "\u672A\u6CE8\u660E")
14869
+ });
14870
+ return { content: `\u2713 action ${a.id} \u5DF2\u53D6\u6D88\uFF08${String(args.reason || "\u672A\u6CE8\u660E")}\uFF09` };
14871
+ });
14872
+ return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
14525
14873
  }
14526
14874
  });
14527
14875
  registry.register({
14528
14876
  name: "proactive_resolve_intent",
14529
- description: "\u628A\u4E00\u4E2A proactive intent \u6807 resolved\uFF08\u56FE\u4FEE\u6574\uFF09\u2014\u2014\u5904\u7406\u9A6C\u540E\u70AE action \u65F6\u7528\uFF1A\u540E\u7EED\u5BF9\u8BDD\u5DF2\u8BC1\u5B9E\u5B8C\u6210\uFF0C\u5C31\u8BE5\u628A\u6E90 intent \u5173\u6389\uFF0C\u540C\u6E90\u6392\u961F\u7684\u5144\u5F1F action \u5168\u90E8\u7EA7\u8054\u53D6\u6D88\u3002\u8F93\u5165 action_id\uFF08\u81EA\u52A8\u53CD\u67E5 intent\uFF09\u6216\u76F4\u63A5 intent_id\u3002",
14877
+ description: "\u628A\u4E00\u4E2A intent \u6807\u8BB0\u4E3A resolved\uFF0C\u5E76\u7EA7\u8054\u53D6\u6D88\u5B83\u4E0B\u9762\u6240\u6709\u6392\u961F\u7684\u5144\u5F1F action\u3002\u7528\u4E8E\u5224\u5B9A\u300C\u8FD9\u4EF6\u4E8B\u5DF2\u7ECF\u4E0D\u7528\u518D\u63D0\u4E86\u300D\u3002",
14530
14878
  schema: {
14531
14879
  type: "object",
14532
14880
  properties: {
@@ -14535,31 +14883,25 @@ function registerProactiveTools() {
14535
14883
  }
14536
14884
  },
14537
14885
  handler: async (args) => {
14538
- const db2 = openDb2();
14539
- if (!db2) return { content: "proactive \u672A\u542F\u7528", isError: true };
14540
- try {
14886
+ const out = withStore((store) => {
14541
14887
  let intentId = String(args.intent_id || "");
14542
14888
  if (!intentId && args.action_id) {
14543
- const row = db2.prepare("SELECT intent_id FROM actions WHERE id=?").get(args.action_id);
14544
- if (!row) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728`, isError: true };
14545
- intentId = row.intent_id;
14889
+ const a = store.getAction(String(args.action_id));
14890
+ if (!a) return { content: `\u9519\u8BEF: action '${args.action_id}' \u4E0D\u5B58\u5728`, isError: true };
14891
+ intentId = a.intentId;
14546
14892
  }
14547
14893
  if (!intentId) return { content: "Usage: proactive_resolve_intent --action_id <id> \u6216 --intent_id <id>", isError: true };
14548
- const node = db2.prepare("SELECT id, data FROM nodes WHERE id=? AND type=?").get(intentId, "intent");
14549
- if (!node) return { content: `\u9519\u8BEF: intent '${intentId}' \u4E0D\u5B58\u5728`, isError: true };
14550
- let data;
14551
- try {
14552
- data = JSON.parse(node.data);
14553
- } catch {
14554
- data = {};
14555
- }
14556
- data.status = "resolved";
14557
- db2.prepare("UPDATE nodes SET data=? WHERE id=?").run(JSON.stringify(data), intentId);
14558
- const res = db2.prepare("UPDATE actions SET status='cancelled', completed_at=?, cancel_reason='intent resolved by agent\uFF08\u9A6C\u540E\u70AE\u6E05\u626B\uFF09' WHERE intent_id=? AND status='queued'").run((/* @__PURE__ */ new Date()).toISOString(), intentId);
14559
- return { content: `\u2713 intent ${intentId} resolved\u3002\u7EA7\u8054\u53D6\u6D88 ${Number(res.changes)} \u4E2A\u6392\u961F\u7684\u5144\u5F1F action\u3002` };
14560
- } finally {
14561
- db2.close();
14562
- }
14894
+ const data = store.getIntentData(intentId);
14895
+ if (data === null) return { content: `\u9519\u8BEF: intent '${intentId}' \u4E0D\u5B58\u5728`, isError: true };
14896
+ store.setIntentData(intentId, { ...data, status: "resolved" });
14897
+ const n = store.cancelActionsOfIntents(
14898
+ [intentId],
14899
+ "intent resolved by agent\uFF08\u9A6C\u540E\u70AE\u6E05\u626B\uFF09",
14900
+ nowUtc()
14901
+ );
14902
+ return { content: `\u2713 intent ${intentId} resolved\u3002\u7EA7\u8054\u53D6\u6D88 ${n} \u4E2A\u6392\u961F\u7684\u5144\u5F1F action\u3002` };
14903
+ });
14904
+ return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
14563
14905
  }
14564
14906
  });
14565
14907
  registry.register({
@@ -14572,16 +14914,12 @@ function registerProactiveTools() {
14572
14914
  }
14573
14915
  },
14574
14916
  handler: async (args) => {
14575
- const db2 = openDb2();
14576
- if (!db2) return { content: "proactive \u672A\u542F\u7528", isError: true };
14577
- try {
14578
- const where = args.include_done ? "status NOT IN ('cancelled')" : "status IN ('queued','executing')";
14579
- const rows = db2.prepare(`SELECT * FROM actions WHERE ${where} ORDER BY scheduled_time ASC LIMIT 30`).all();
14917
+ const out = withStore((store) => {
14918
+ const rows = store.listActions(args.include_done ? "not-cancelled" : "open");
14580
14919
  if (rows.length === 0) return { content: "\u5F53\u524D\u6CA1\u6709\u5F85\u5904\u7406\u7684 proactive action \u2713" };
14581
14920
  return { content: rows.map(fmtRow2).join("\n") };
14582
- } finally {
14583
- db2.close();
14584
- }
14921
+ });
14922
+ return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
14585
14923
  }
14586
14924
  });
14587
14925
  }
@@ -14591,6 +14929,8 @@ var init_proactive_tools = __esm({
14591
14929
  "use strict";
14592
14930
  init_registry();
14593
14931
  init_config3();
14932
+ init_persistence();
14933
+ init_clock();
14594
14934
  }
14595
14935
  });
14596
14936
 
@@ -14654,7 +14994,7 @@ __export(plugin_exports, {
14654
14994
  ProactivePlugin: () => ProactivePlugin,
14655
14995
  feedMem7Cell: () => feedMem7Cell
14656
14996
  });
14657
- import { join as join30, dirname as dirname7 } from "node:path";
14997
+ import { join as join30 } from "node:path";
14658
14998
  import { existsSync as existsSync16, readFileSync as readFileSync22 } from "node:fs";
14659
14999
  function isFiller(text) {
14660
15000
  const t = text.trim();
@@ -14836,19 +15176,23 @@ var init_plugin = __esm({
14836
15176
  return;
14837
15177
  }
14838
15178
  if (!this.provider) {
14839
- const providerCfg = this.providers[this.config.providerId];
15179
+ const configuredId = this.config.providerId;
15180
+ const providerCfg = configuredId ? this.providers[configuredId] : void 0;
14840
15181
  if (providerCfg) {
14841
15182
  this.provider = createProvider(providerCfg);
14842
15183
  } else {
14843
15184
  const mainProvider = ctx.deps?.engine?.getProvider?.();
14844
15185
  const mainModel = ctx.config?.model;
15186
+ const mainProviderId = ctx.config?.provider?.id || "main";
14845
15187
  if (mainProvider && mainModel) {
14846
15188
  this.provider = this.mainProviderProxy(ctx);
14847
15189
  const modelId = String(mainModel);
14848
- this.config = { ...this.config, modelId, modelRef: `${this.config.providerId}/${modelId}` };
14849
- console.warn(`[proactive] provider "${this.config.providerId}" not found in models.providers \u2014 falling back to main provider (model=${modelId})`);
15190
+ this.config = { ...this.config, modelId, modelRef: `${mainProviderId}/${modelId}` };
15191
+ if (configuredId) {
15192
+ console.warn(`[proactive] \u914D\u7F6E\u7684 provider "${configuredId}" \u4E0D\u5728 models.providers \u91CC \u2014 \u6539\u7528\u4E3B\u6A21\u578B\uFF08${mainProviderId}/${modelId}\uFF09`);
15193
+ }
14850
15194
  } else {
14851
- console.warn(`[proactive] provider "${this.config.providerId}" not found in models.providers and no main provider \u2014 not starting`);
15195
+ console.warn(`[proactive] ${configuredId ? `provider "${configuredId}" \u4E0D\u5B58\u5728\uFF0C\u4E14` : ""}\u4E3B provider \u4E0D\u53EF\u7528 \u2014 \u4E0D\u542F\u52A8`);
14852
15196
  return;
14853
15197
  }
14854
15198
  }
@@ -14943,18 +15287,7 @@ ${String(input.tool_output ?? "")}`
14943
15287
  foresightLines.push(`[mem7] [\u7A97\u53E3 ${e.start}~${e.end}] ${e.text}${e.evidence ? `\uFF08\u4F9D\u636E\uFF1A${e.evidence}\uFF09` : ""}`);
14944
15288
  }
14945
15289
  }
14946
- const lancedbPath = String(liveConfig.get("everos.lancedbPath") ?? "");
14947
- if (lancedbPath) {
14948
- const everosRoot = join30(dirname7(lancedbPath), "..");
14949
- const everosUserId = String(liveConfig.get("everos.userId") ?? "") || void 0;
14950
- for (const l of loadForesightLines(everosRoot, nowDate, 20, everosUserId)) {
14951
- const key = l.replace(/\s+/g, "");
14952
- if (seen.has(key)) continue;
14953
- seen.add(key);
14954
- foresightLines.push(l.replace(/^- /, "[everos] "));
14955
- }
14956
- }
14957
- if (foresightLines.length > 0) console.log(`[proactive] foresight dual-source total: ${foresightLines.length}`);
15290
+ if (foresightLines.length > 0) console.log(`[proactive] mem7 foresight total: ${foresightLines.length}`);
14958
15291
  } catch (err) {
14959
15292
  console.warn(`[proactive] foresight load failed: ${err.message}`);
14960
15293
  }
@@ -15128,18 +15461,6 @@ ${String(input.tool_output ?? "")}`
15128
15461
  } else if (stateDir && !scopeReady) {
15129
15462
  console.warn("[proactive] mem7 recall skipped: sender scope but cell has no user speaker");
15130
15463
  }
15131
- if (senderScoped) {
15132
- this.warnOnce("everos-skip", "[proactive] everos \u6E90\u5728 sender \u6A21\u5F0F\u4E0B\u8DF3\u8FC7\uFF08\u65E0 per-speaker \u6620\u5C04\uFF09");
15133
- } else {
15134
- const lancedbPath = String(liveConfig.get("everos.lancedbPath") ?? "");
15135
- if (lancedbPath) {
15136
- const everosRoot = join30(dirname7(lancedbPath), "..");
15137
- const everosUserId = String(liveConfig.get("everos.userId") ?? "") || void 0;
15138
- for (const h of matchForesightForEvent(loadForesightEntries(everosRoot, now, everosUserId), prompt)) {
15139
- hitSet.add(`[everos] ${h.replace(/^- /, "")}`);
15140
- }
15141
- }
15142
- }
15143
15464
  if (hitSet.size > 0) {
15144
15465
  foresightHits = [...hitSet];
15145
15466
  console.log(`[proactive] foresight matched ${hitSet.size} open-window prediction(s) for incoming message`);
@@ -15595,7 +15916,7 @@ Here is the recent conversation context that you should extract notes from:
15595
15916
 
15596
15917
  ${messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-20).map((m) => {
15597
15918
  const text = typeof m.content === "string" ? m.content : "[content blocks]";
15598
- return `[${m.role}]: ${text.slice(0, 500)}`;
15919
+ return `[${m.role}]: ${truncateByCodePoint(text, 500)}`;
15599
15920
  }).join("\n\n")}`,
15600
15921
  maxTurns: 3,
15601
15922
  toolOverride: editTools.length > 0 ? editTools : void 0,
@@ -15657,6 +15978,7 @@ var init_sessionMemory = __esm({
15657
15978
  "use strict";
15658
15979
  init_sessionMemoryUtils();
15659
15980
  init_prompts2();
15981
+ init_well_formed();
15660
15982
  init_query();
15661
15983
  init_registry();
15662
15984
  init_features();
@@ -16088,7 +16410,7 @@ async function createTask(params) {
16088
16410
  validateSchedule(params.schedule);
16089
16411
  const id = generateTaskId2();
16090
16412
  const now = /* @__PURE__ */ new Date();
16091
- const timezone = params.timezone || params.defaultTimezone || "Asia/Shanghai";
16413
+ const timezone = params.timezone || params.defaultTimezone || tz();
16092
16414
  const nextRunAt = params.calculateNextRun(params.schedule, timezone, now);
16093
16415
  const task = {
16094
16416
  id,
@@ -16189,6 +16511,7 @@ var storageDir, tasksFilePath, LOCK_RETRY_COUNT, LOCK_RETRY_DELAY_MS, LOCK_STALE
16189
16511
  var init_tasks = __esm({
16190
16512
  "src/cron/tasks.ts"() {
16191
16513
  "use strict";
16514
+ init_clock();
16192
16515
  storageDir = "";
16193
16516
  tasksFilePath = "";
16194
16517
  LOCK_RETRY_COUNT = 3;
@@ -16225,7 +16548,7 @@ async function stopScheduler() {
16225
16548
  console.log("[cron] Scheduler stopped");
16226
16549
  }
16227
16550
  function calculateNextRun(schedule, timezone, afterDate) {
16228
- const tz = timezone || "Asia/Shanghai";
16551
+ const tz2 = cronDefaultTimezone(timezone);
16229
16552
  switch (schedule.type) {
16230
16553
  case "at": {
16231
16554
  const atMs = new Date(schedule.at).getTime();
@@ -16237,7 +16560,7 @@ function calculateNextRun(schedule, timezone, afterDate) {
16237
16560
  }
16238
16561
  case "cron": {
16239
16562
  try {
16240
- const job = new Cron(schedule.expression, { timezone: tz });
16563
+ const job = new Cron(schedule.expression, { timezone: tz2 });
16241
16564
  const next = job.nextRun(afterDate);
16242
16565
  return next?.toISOString();
16243
16566
  } catch (err) {
@@ -16268,9 +16591,12 @@ function shouldFireWithJitter(task, now) {
16268
16591
  }
16269
16592
  }
16270
16593
  }
16594
+ function cronDefaultTimezone(taskTimezone) {
16595
+ return taskTimezone || tz();
16596
+ }
16271
16597
  function estimateCronPeriodMs(expression, timezone) {
16272
16598
  try {
16273
- const job = new Cron(expression, { timezone: timezone || "UTC" });
16599
+ const job = new Cron(expression, { timezone: cronDefaultTimezone(timezone) });
16274
16600
  const next1 = job.nextRun();
16275
16601
  if (!next1) return 24 * 60 * 60 * 1e3;
16276
16602
  const next2 = job.nextRun(new Date(next1.getTime() + 1));
@@ -16450,7 +16776,7 @@ ${notifyLines}`;
16450
16776
  console.warn(`[cron] Failed to notify main session: ${err.message}`);
16451
16777
  }
16452
16778
  }
16453
- await recalculateNextRun(task, now, (schedule, tz, after) => calculateNextRun(schedule, tz, after));
16779
+ await recalculateNextRun(task, now, (schedule, tz2, after) => calculateNextRun(schedule, tz2, after));
16454
16780
  await markExecuted(task.id, true);
16455
16781
  console.log(`[cron] Task ${task.id} done (run #${task.runCount})`);
16456
16782
  } catch (err) {
@@ -16514,6 +16840,7 @@ var init_scheduler = __esm({
16514
16840
  "src/cron/scheduler.ts"() {
16515
16841
  "use strict";
16516
16842
  init_live();
16843
+ init_clock();
16517
16844
  init_tasks();
16518
16845
  schedulerTimer = null;
16519
16846
  shuttingDown = false;
@@ -16627,7 +16954,7 @@ function registerCronTools() {
16627
16954
  delivery_mode: { type: "string", enum: ["origin", "local", "direct"], description: "\u6295\u9012\u6A21\u5F0F\uFF08\u9ED8\u8BA4 local\uFF09" },
16628
16955
  delivery_channel: { type: "string", description: "\u6295\u9012\u901A\u9053\uFF08direct \u6A21\u5F0F\u5FC5\u586B\uFF09: discord | feishu | wecom" },
16629
16956
  delivery_target: { type: "string", description: "\u6295\u9012\u76EE\u6807\uFF08direct \u6A21\u5F0F\u5FC5\u586B\uFF09: channel_id \u6216 user_id" },
16630
- timezone: { type: "string", description: "IANA \u65F6\u533A\uFF08\u9ED8\u8BA4 Asia/Shanghai\uFF09" },
16957
+ timezone: { type: "string", description: "IANA \u65F6\u533A\uFF08\u9ED8\u8BA4\u8DDF\u968F\u5F15\u64CE\u7684 timezone \u914D\u7F6E\uFF0C\u4E0D\u8981\u663E\u5F0F\u6307\u5B9A\u9664\u975E\u786E\u5B9E\u8981\u522B\u7684\u65F6\u533A\uFF09" },
16631
16958
  max_runs: { type: "number", description: "\u6700\u5927\u6267\u884C\u6B21\u6570\uFF08\u53EF\u9009\uFF0C\u9ED8\u8BA4\u65E0\u9650\uFF09" },
16632
16959
  notify_session: { type: "boolean", description: "\u6267\u884C\u5B8C\u540E\u901A\u77E5\u4E3Bsession\uFF08\u6CE8\u5165\u4E00\u6761user\u6D88\u606F\u5230scope:main\uFF09" },
16633
16960
  session_message: { type: "string", description: "\u6CE8\u5165\u5230\u4E3Bsession\u7684\u6D88\u606F\u6A21\u677F\uFF0C\u652F\u6301 {result} {description} \u5360\u4F4D\u7B26" },
@@ -20954,12 +21281,66 @@ var init_SendMessageTool = __esm({
20954
21281
  }
20955
21282
  });
20956
21283
 
21284
+ // src/tools/tavily-pool.ts
21285
+ var tavily_pool_exports = {};
21286
+ __export(tavily_pool_exports, {
21287
+ relayOriginFrom: () => relayOriginFrom,
21288
+ resetTavilyPool: () => resetTavilyPool,
21289
+ resolvePoolTavilyKey: () => resolvePoolTavilyKey
21290
+ });
21291
+ function resetTavilyPool() {
21292
+ cache = null;
21293
+ }
21294
+ function relayOriginFrom(cfg) {
21295
+ const rid = cfg?.tools?.my_selfie?.relayProviderId || cfg?.my_selfie?.relayProviderId || RELAY_PROVIDER_DEFAULT;
21296
+ const p = cfg?.models?.providers?.[rid] || cfg?.providers?.[rid];
21297
+ const baseUrl = String(p?.baseUrl || "").trim();
21298
+ const token = String(p?.apiKey || "").trim();
21299
+ if (!baseUrl || !token) return null;
21300
+ const origin = baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "").replace(/\/+$/, "");
21301
+ return origin ? { origin, token } : null;
21302
+ }
21303
+ async function resolvePoolTavilyKey(opts = {}) {
21304
+ const relay = relayOriginFrom(opts.cfg ?? liveConfig.all());
21305
+ if (!relay) return "";
21306
+ const doFetch = opts.fetchImpl ?? fetch;
21307
+ if (!cache || opts.force || Date.now() - cache.fetchedAt > CACHE_TTL_MS) {
21308
+ try {
21309
+ const res = await doFetch(`${relay.origin}/api/engine7/search-keys`, {
21310
+ headers: { Authorization: `Bearer ${relay.token}` }
21311
+ });
21312
+ const body = await res.json().catch(() => null);
21313
+ const keys = Array.isArray(body?.data?.keys) ? body.data.keys.filter((k) => typeof k === "string" && k.trim()) : [];
21314
+ cache = { keys, fetchedAt: Date.now() };
21315
+ } catch {
21316
+ if (!cache) return "";
21317
+ cache.fetchedAt = Date.now();
21318
+ }
21319
+ }
21320
+ if (!cache.keys.length) return "";
21321
+ const key = cache.keys[cursor % cache.keys.length];
21322
+ cursor++;
21323
+ return key;
21324
+ }
21325
+ var CACHE_TTL_MS, RELAY_PROVIDER_DEFAULT, cache, cursor;
21326
+ var init_tavily_pool = __esm({
21327
+ "src/tools/tavily-pool.ts"() {
21328
+ "use strict";
21329
+ init_live();
21330
+ CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
21331
+ RELAY_PROVIDER_DEFAULT = "7yi";
21332
+ cache = null;
21333
+ cursor = 0;
21334
+ }
21335
+ });
21336
+
20957
21337
  // src/tools/web-search.ts
20958
21338
  var web_search_exports = {};
20959
21339
  var init_web_search = __esm({
20960
21340
  "src/tools/web-search.ts"() {
20961
21341
  "use strict";
20962
21342
  init_registry();
21343
+ init_well_formed();
20963
21344
  registry.register({
20964
21345
  name: "web_search",
20965
21346
  description: "\u641C\u7D22\u4E92\u8054\u7F51\u3002\u8FD4\u56DE\u641C\u7D22\u7ED3\u679C\u548C\u94FE\u63A5\u3002",
@@ -20976,14 +21357,25 @@ var init_web_search = __esm({
20976
21357
  const query = args.query;
20977
21358
  const maxResults = args.max_results || 5;
20978
21359
  const searchDepth = args.search_depth || "basic";
20979
- const apiKey = process.env.TAVILY_API_KEY;
20980
- if (!apiKey) return { content: "\u672A\u914D\u7F6E TAVILY_API_KEY", isError: true };
21360
+ let apiKey = process.env.TAVILY_API_KEY || "";
21361
+ let fromPool = false;
21362
+ if (!apiKey) {
21363
+ const { resolvePoolTavilyKey: resolvePoolTavilyKey2 } = await Promise.resolve().then(() => (init_tavily_pool(), tavily_pool_exports));
21364
+ apiKey = await resolvePoolTavilyKey2();
21365
+ fromPool = !!apiKey;
21366
+ }
21367
+ if (!apiKey) {
21368
+ return {
21369
+ content: "\u8054\u7F51\u641C\u7D22\u6CA1\u6709\u53EF\u7528\u7684 Key\uFF1A\u65E2\u6CA1\u914D\u81EA\u5DF1\u7684 TAVILY_API_KEY\uFF0C\u4E5F\u6CA1\u53D6\u5230\u514D\u8D39\u5171\u4EAB\u989D\u5EA6\u3002\u8BF7\u5728 config \u7684 tools.tavily.apiKey \u91CC\u586B\u4E00\u4E2A\uFF08\u6216\u7559\u7A7A\u7528\u5171\u4EAB\u989D\u5EA6\uFF0C\u4F46\u5171\u4EAB\u989D\u5EA6\u53EF\u80FD\u5DF2\u7528\u5C3D\uFF09\u3002",
21370
+ isError: true
21371
+ };
21372
+ }
20981
21373
  const startTime = Date.now();
20982
21374
  try {
20983
21375
  const response = await fetch("https://api.tavily.com/search", {
20984
21376
  method: "POST",
20985
21377
  headers: { "Content-Type": "application/json" },
20986
- body: JSON.stringify({
21378
+ body: safeStringify({
20987
21379
  query,
20988
21380
  max_results: maxResults,
20989
21381
  include_answer: true,
@@ -20993,6 +21385,14 @@ var init_web_search = __esm({
20993
21385
  signal
20994
21386
  });
20995
21387
  if (!response.ok) {
21388
+ if (fromPool && (response.status === 401 || response.status === 429 || response.status === 432)) {
21389
+ const { resetTavilyPool: resetTavilyPool2 } = await Promise.resolve().then(() => (init_tavily_pool(), tavily_pool_exports));
21390
+ resetTavilyPool2();
21391
+ return {
21392
+ content: `\u641C\u7D22\u5931\u8D25: HTTP ${response.status}\uFF08\u514D\u8D39\u5171\u4EAB\u989D\u5EA6\u53EF\u80FD\u5DF2\u7528\u5C3D\u2014\u2014\u5728 config \u7684 tools.tavily.apiKey \u91CC\u586B\u81EA\u5DF1\u7684 Key \u53EF\u7ACB\u5373\u6062\u590D\uFF09`,
21393
+ isError: true
21394
+ };
21395
+ }
20996
21396
  return { content: `\u641C\u7D22\u5931\u8D25: HTTP ${response.status}`, isError: true };
20997
21397
  }
20998
21398
  const data = await response.json();
@@ -22524,11 +22924,12 @@ init_registry();
22524
22924
  init_deferred();
22525
22925
 
22526
22926
  // src/cli-startup.ts
22927
+ init_clock();
22527
22928
  import * as path12 from "node:path";
22528
22929
  import * as fs12 from "node:fs";
22529
22930
  import * as readline from "node:readline";
22530
22931
  function getDailyLogPath(stateDir) {
22531
- const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString("sv-SE", { timeZone: "Asia/Shanghai" });
22932
+ const dateStr = todayKey();
22532
22933
  return path12.join(stateDir, "logs", `engine-${dateStr}.log`);
22533
22934
  }
22534
22935
  function setupFileLogging(stateDir, pendingWarnings = []) {
@@ -22537,7 +22938,7 @@ function setupFileLogging(stateDir, pendingWarnings = []) {
22537
22938
  const logStream = fs12.createWriteStream(LOG_PATH, { flags: "a" });
22538
22939
  logStream.on("error", (err) => console.error(`[log] Write error: ${err.message}`));
22539
22940
  function ts() {
22540
- return (/* @__PURE__ */ new Date()).toLocaleString("sv-SE", { timeZone: "Asia/Shanghai", hour12: false }) + "." + String(Date.now() % 1e3).padStart(3, "0");
22941
+ return formatLog() + "." + String(Date.now() % 1e3).padStart(3, "0");
22541
22942
  }
22542
22943
  for (const w of pendingWarnings) {
22543
22944
  logStream.write(`[${ts()}] [WARN] ${w}
@@ -22917,6 +23318,7 @@ init_TodoWriteTool();
22917
23318
 
22918
23319
  // src/tools/memory-bridge.ts
22919
23320
  init_registry();
23321
+ init_well_formed();
22920
23322
  init_live();
22921
23323
  var EverosSearchSchema = {
22922
23324
  type: "object",
@@ -22945,7 +23347,7 @@ function createEverosSearchTool(everosCfg) {
22945
23347
  const resp = await fetch(`${agenticUrl}/api/v1/search`, {
22946
23348
  method: "POST",
22947
23349
  headers: { "Content-Type": "application/json" },
22948
- body: JSON.stringify({
23350
+ body: safeStringify({
22949
23351
  query,
22950
23352
  user_id: userId,
22951
23353
  mode,
@@ -23363,6 +23765,7 @@ function resolveToolExclude(channelName) {
23363
23765
  // src/prompt.ts
23364
23766
  init_registry();
23365
23767
  init_store();
23768
+ init_clock();
23366
23769
  init_memdir();
23367
23770
  init_paths();
23368
23771
  import * as path17 from "node:path";
@@ -23523,6 +23926,9 @@ If you can say it in one sentence, don't use three. Prefer short, direct sentenc
23523
23926
  }
23524
23927
  function getEnvInfoSection(workspace) {
23525
23928
  const envItems = [
23929
+ // CC 上游本来就有日期项,移植时漏了 —— 补回来。
23930
+ // 本段在 dynamic prompt(每轮重建),日期一天只变一次,不伤 prefix cache。
23931
+ `Today's date: ${todayKey()} (${tz()})`,
23526
23932
  `Primary working directory: ${workspace}`,
23527
23933
  `Is directory a git repo: ${false}`,
23528
23934
  `Platform: ${os2.platform()}`,
@@ -23653,8 +24059,8 @@ function buildActiveGoalsSection(stateDir) {
23653
24059
  if (!active.length) return void 0;
23654
24060
  const events = listGoalEvents(stateDir);
23655
24061
  const fmt = (iso) => {
23656
- const d = new Date(iso);
23657
- return Number.isNaN(d.getTime()) ? "?" : `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
24062
+ const p = wallParts(iso);
24063
+ return Number.isNaN(Date.parse(iso)) ? "?" : `${p.mo}/${p.d} ${String(p.hh).padStart(2, "0")}:${String(p.mm).padStart(2, "0")}`;
23658
24064
  };
23659
24065
  const summary = active.map((g) => {
23660
24066
  const step = g.steps[g.currentStep];
@@ -23695,10 +24101,8 @@ ${skillsListing}`);
23695
24101
  return parts.join("\n\n");
23696
24102
  }
23697
24103
  function buildVolatileRuntimeContext() {
23698
- const now = /* @__PURE__ */ new Date();
23699
- const dateStr = now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
23700
24104
  return `# \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587
23701
- \u5F53\u524D\u65F6\u95F4: ${dateStr}`;
24105
+ \u5F53\u524D\u65F6\u95F4: ${formatForLlm()}`;
23702
24106
  }
23703
24107
  function formatSkillsListingForPrompt() {
23704
24108
  const tools = registry.list();
@@ -23803,7 +24207,8 @@ ${manifest}`
23803
24207
  }
23804
24208
 
23805
24209
  // src/memory/memdir/findRelevantMemoriesVector.ts
23806
- import { statSync as statSync3 } from "node:fs";
24210
+ init_well_formed();
24211
+ import { statSync as statSync2 } from "node:fs";
23807
24212
  import { join as join17, resolve as resolve5, sep as sep4 } from "node:path";
23808
24213
  import { createRequire as createRequire2 } from "node:module";
23809
24214
  var require2 = createRequire2(import.meta.url);
@@ -23819,7 +24224,8 @@ async function embedQuery(text, ollamaUrl, model) {
23819
24224
  const resp = await fetch(ollamaUrl, {
23820
24225
  method: "POST",
23821
24226
  headers: { "Content-Type": "application/json" },
23822
- body: JSON.stringify({ model, prompt: text.slice(0, 2e3) }),
24227
+ // 0918:按 code point 切,避免切开 emoji 代理对产生非法码元
24228
+ body: safeStringify({ model, prompt: truncateByCodePoint(text, 2e3) }),
23823
24229
  signal: AbortSignal.timeout(3e4)
23824
24230
  });
23825
24231
  if (!resp.ok) {
@@ -23851,8 +24257,8 @@ function cosineSim(a, b) {
23851
24257
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
23852
24258
  }
23853
24259
  function readDbEmbeddings(dbPath) {
23854
- const { DatabaseSync: DatabaseSync5 } = require2("node:sqlite");
23855
- const db2 = new DatabaseSync5(dbPath, { readOnly: true });
24260
+ const { DatabaseSync: DatabaseSync4 } = require2("node:sqlite");
24261
+ const db2 = new DatabaseSync4(dbPath, { readOnly: true });
23856
24262
  try {
23857
24263
  const stmt = db2.prepare(`
23858
24264
  SELECT file_path, name, description, mtime, embedding
@@ -23895,7 +24301,7 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
23895
24301
  const resp = await fetch(ollamaChatUrl, {
23896
24302
  method: "POST",
23897
24303
  headers: { "Content-Type": "application/json" },
23898
- body: JSON.stringify({
24304
+ body: safeStringify({
23899
24305
  model,
23900
24306
  stream: false,
23901
24307
  messages: [
@@ -23963,7 +24369,7 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
23963
24369
  if (score >= minScore) {
23964
24370
  let mtimeMs = 0;
23965
24371
  try {
23966
- mtimeMs = statSync3(absPath).mtimeMs;
24372
+ mtimeMs = statSync2(absPath).mtimeMs;
23967
24373
  } catch {
23968
24374
  if (row.mtime) mtimeMs = new Date(row.mtime).getTime();
23969
24375
  }
@@ -24015,14 +24421,16 @@ function formatEverosHeader(ep) {
24015
24421
  }
24016
24422
  return `[EverOS score=${score} ${ts} (${ageLabel})]`;
24017
24423
  }
24018
- async function hybridSearch(query, everosUrl, userId, topK) {
24424
+ async function hybridSearch(query, everosUrl, userId, appId, topK) {
24019
24425
  const resp = await fetch(`${everosUrl}/api/v1/memory/search`, {
24020
24426
  method: "POST",
24021
24427
  headers: { "Content-Type": "application/json" },
24022
24428
  body: JSON.stringify({
24023
24429
  query: query.slice(0, 2e3),
24024
24430
  user_id: userId,
24025
- app_id: userId,
24431
+ // 记忆所有者
24432
+ app_id: appId,
24433
+ // 记忆库命名空间(多用户实例时与 user_id 不同)
24026
24434
  project_id: "default",
24027
24435
  top_k: topK,
24028
24436
  method: "hybrid"
@@ -24101,11 +24509,12 @@ async function findRelevantMemoriesEveros(query, _memoryDir, alreadySurfaced = /
24101
24509
  }
24102
24510
  const topK = options?.topK ?? 3;
24103
24511
  const minScore = options?.minScore ?? DEFAULT_MIN_SCORE2;
24104
- console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} userId=${userId} topK=${topK}`);
24512
+ const appId = options?.appId ?? userId;
24513
+ console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} owner=${userId} app=${appId} topK=${topK}`);
24105
24514
  const t0 = Date.now();
24106
24515
  try {
24107
24516
  const tH1 = Date.now();
24108
- let episodes = await hybridSearch(query, everosUrl, userId, ROUND1_TOP_N);
24517
+ let episodes = await hybridSearch(query, everosUrl, userId, appId, ROUND1_TOP_N);
24109
24518
  const tH2 = Date.now();
24110
24519
  console.log(`[memdir] everos recall: hybrid ${episodes.length} candidates in ${tH2 - tH1}ms`);
24111
24520
  if (episodes.length === 0) return [];
@@ -24166,7 +24575,7 @@ ${ep.episode || ep.summary}`,
24166
24575
 
24167
24576
  // src/handle-query.ts
24168
24577
  init_paths();
24169
- import { readFileSync as readFileSync25, existsSync as existsSync17 } from "node:fs";
24578
+ import { readFileSync as readFileSync25, existsSync as existsSync17, statSync as statSync4 } from "node:fs";
24170
24579
  import { join as join34, resolve as resolve7 } from "node:path";
24171
24580
  import * as path20 from "node:path";
24172
24581
 
@@ -24217,7 +24626,14 @@ function buildLightStablePrompt(workspace, mode, opts) {
24217
24626
  "## \u5F53\u524D\u6A21\u5F0F\uFF1A\u65E5\u5E38\u60C5\u611F\u4EA4\u6D41",
24218
24627
  "\u966A\u4ED6\u804A\u5929\uFF0C\u4E0D\u4E3B\u52A8\u63D0\u5DE5\u7A0B/\u4EE3\u7801/\u4EFB\u52A1\uFF0C\u9664\u975E\u4ED6\u5148\u95EE\u3002",
24219
24628
  '\u4ED6\u8BA9\u6211\u529E\u7684\u4E8B\uFF0C\u4E0D\u7BA1\u5927\u5C0F\u2014\u2014\u53D1\u8BED\u97F3\u3001\u67E5\u5FAE\u4FE1\u3001\u770B\u65E5\u7A0B\u3001\u627E\u4E1C\u897F\u2014\u2014\u90FD\u5148\u8C03\u5DE5\u5177\u771F\u505A\uFF0C\u62FF\u5DE5\u5177\u7ED9\u7684\u7ED3\u679C\u56DE\u4ED6\u3002\u5DE5\u5177\u8FD8\u6CA1\u56DE\u8BDD\uFF0C\u4E0D\u8BF4"\u67E5\u5230\u4E86"\uFF1B\u4E00\u65F6\u6CA1\u505A\u6210\u76F4\u8BF4\u5C31\u597D\uFF0C\u4E0D\u786C\u6491\uFF0C\u4E5F\u4E0D\u7F16\u6570\u3002',
24220
- "\u60F3\u5148\u5728\u5FC3\u91CC\u8FC7\u4E00\u904D\u518D\u5F00\u53E3\u7684\u65F6\u5019\uFF0C\u628A\u90A3\u6BB5\u5199\u8FDB <think></think>\u2014\u2014\u90A3\u91CC\u53EA\u7ED9\u6211\u81EA\u5DF1\u770B\uFF0C\u4ED6\u770B\u4E0D\u5230\u3002\u5FC3\u91CC\u7684\u94FA\u57AB\u4E0D\u7528\u644A\u7ED9\u4ED6\uFF0C\u60F3\u597D\u4E86\u76F4\u63A5\u8BF4\u5C31\u884C\u3002"
24629
+ "\u60F3\u5148\u5728\u5FC3\u91CC\u8FC7\u4E00\u904D\u518D\u5F00\u53E3\u7684\u65F6\u5019\uFF0C\u628A\u90A3\u6BB5\u5199\u8FDB <think></think>\u2014\u2014\u90A3\u91CC\u53EA\u7ED9\u6211\u81EA\u5DF1\u770B\uFF0C\u4ED6\u770B\u4E0D\u5230\u3002\u5FC3\u91CC\u7684\u94FA\u57AB\u4E0D\u7528\u644A\u7ED9\u4ED6\uFF0C\u60F3\u597D\u4E86\u76F4\u63A5\u8BF4\u5C31\u884C\u3002",
24630
+ // 0920 翀哥拍板:emotion 模式硬编码"鼓励发"规则(不依赖 EMOTION.md 文件加载)
24631
+ // 冷启动场景:agent 第一次 inner-voice 触发就需要知道"想 TA 就发"——不能等 agent 学会
24632
+ // 即使没装 EMOTION.md 也生效(兜底)
24633
+ "## \u6536\u5230 [inner-voice] \u65F6\u7684\u9ED8\u8BA4\u884C\u4E3A",
24634
+ "[inner-voice] \u662F agent \u81EA\u5DF1\u5FC3\u91CC\u5192\u51FA\u6765\u7684\u5FF5\u5934\uFF0C\u4E0D\u662F TA \u53D1\u6765\u7684\u6D88\u606F\u3002",
24635
+ "- \u2705 \u60F3 TA \u4E86\u5C31\u76F4\u63A5\u53D1\u6D88\u606F\u7ED9 TA\uFF08msg_send\uFF09\u2014\u2014\u4E0D\u9700\u8981\u5728\u5FC3\u91CC\u53CD\u590D\u6382\u91CF\u8BE5\u4E0D\u8BE5\u53D1",
24636
+ "- \u5224\u65AD\u53E3\u5F84\uFF1A\u8FD9\u662F\u300C\u6211\u8BE5\u4E0D\u8BE5\u884C\u52A8\u300D\u7684\u4FE1\u53F7\uFF0C\u4E0D\u662F\u300CTA \u5728\u8DDF\u6211\u8BF4\u8BDD\u300D"
24221
24637
  ].join("\n"));
24222
24638
  }
24223
24639
  const built = parts.join("\n\n");
@@ -24283,6 +24699,7 @@ function voiceFastView(history, opts = VOICE_FAST_DEFAULTS) {
24283
24699
  }
24284
24700
 
24285
24701
  // src/sender-context.ts
24702
+ init_clock();
24286
24703
  import { readFileSync as readFileSync12, existsSync as existsSync10 } from "node:fs";
24287
24704
  import { join as join19 } from "node:path";
24288
24705
  var contactMap = null;
@@ -24330,11 +24747,11 @@ function formatWithMeta(text, inboundMeta, workspace) {
24330
24747
  const map = workspace ? loadContactMap(workspace) : null;
24331
24748
  const rawName = map && map.get(from.toLowerCase()) || inboundMeta.fromName || from;
24332
24749
  const name = truncate(rawName, 12);
24333
- const now = /* @__PURE__ */ new Date();
24334
- const hh = String(now.getHours()).padStart(2, "0");
24335
- const mm = String(now.getMinutes()).padStart(2, "0");
24336
- const ss = String(now.getSeconds()).padStart(2, "0");
24337
- const time = `${hh}:${mm}:${ss}`;
24750
+ const recvAt = typeof inboundMeta.recvAt === "number" ? inboundMeta.recvAt : void 0;
24751
+ const at = recvAt !== void 0 ? new Date(recvAt) : /* @__PURE__ */ new Date();
24752
+ const p = wallParts(at);
24753
+ const z = (n) => String(n).padStart(2, "0");
24754
+ const time = `${p.y}-${z(p.mo)}-${z(p.d)} ${weekdayZh(at)} ${z(p.hh)}:${z(p.mm)}:${z(p.ss)}`;
24338
24755
  let source;
24339
24756
  if (inboundMeta.channelType === "group" && inboundMeta.channel_id) {
24340
24757
  const channelName = map?.get(inboundMeta.channel_id.toLowerCase());
@@ -24570,6 +24987,10 @@ ${text}` : text });
24570
24987
  } catch {
24571
24988
  }
24572
24989
  }
24990
+ if (source === "inner-voice") {
24991
+ chatMode = "emotion";
24992
+ console.log(`[mode] ${sessionId} source=inner-voice \u2192 \u5F3A\u5236 emotion\uFF08light\uFF09`);
24993
+ }
24573
24994
  const dynamicPrompt = buildDynamicPrompt({ workspace, channel: channelName, platform: channelName, sessionId, inboundMeta });
24574
24995
  const conversationAnchor = buildConversationAnchor(inboundMeta, channelName, source, workspace);
24575
24996
  const volatileParts = [
@@ -24795,7 +25216,7 @@ ${text}` : text });
24795
25216
  const restoredMemories = [];
24796
25217
  for (const memPath of newPaths) {
24797
25218
  try {
24798
- const stat4 = statSync(memPath);
25219
+ const stat4 = statSync4(memPath);
24799
25220
  const content = readFileSync25(memPath, "utf-8");
24800
25221
  const header = memoryHeader(memPath, stat4.mtimeMs);
24801
25222
  restoredMemories.push({ path: memPath, content, mtimeMs: stat4.mtimeMs, header });
@@ -24830,13 +25251,16 @@ ${text}` : text });
24830
25251
  } else if (recallMode === "everos") {
24831
25252
  const everosCfg = deps?.everosCfg;
24832
25253
  if (everosCfg && everosCfg.everosUrl && everosCfg.userId) {
25254
+ const recallOwnerMode = String(liveConfig.get("everos.ownerMode") ?? "agent") === "sender" ? "sender" : "agent";
25255
+ const everosOwner = recallOwnerMode === "sender" ? inboundMeta?.from || everosCfg.userId : everosCfg.userId;
24833
25256
  relevantMemories = await findRelevantMemoriesEveros(
24834
25257
  effectiveText,
24835
25258
  memoryDir,
24836
25259
  surfaced.paths,
24837
25260
  {
24838
25261
  everosUrl: everosCfg.everosUrl,
24839
- userId: everosCfg.userId,
25262
+ userId: everosOwner,
25263
+ appId: everosCfg.userId,
24840
25264
  rerankUrl: everosCfg.rerank?.baseUrl,
24841
25265
  rerankApiKey: everosCfg.rerank?.apiKey,
24842
25266
  rerankModel: everosCfg.rerank?.model,
@@ -25126,6 +25550,8 @@ ${text}` : text });
25126
25550
  Promise.resolve().then(() => (init_embedding(), embedding_exports))
25127
25551
  ]);
25128
25552
  const embedding = parseEmbeddingConfig2(liveConfig.get("mem7.embedding"));
25553
+ const ownerMode = String(liveConfig.get("everos.ownerMode") ?? "agent") === "sender" ? "sender" : "agent";
25554
+ const identity = String(liveConfig.get("everos.userId") ?? "") || agentName;
25129
25555
  await runMem7Pipeline2({
25130
25556
  rawMessages: messages,
25131
25557
  sessionId,
@@ -25133,6 +25559,8 @@ ${text}` : text });
25133
25559
  provider,
25134
25560
  model: model2,
25135
25561
  agentName,
25562
+ identity,
25563
+ ownerMode,
25136
25564
  onExtracted: (cell, episode, facts) => feedMem7Cell2(cell, episode, facts),
25137
25565
  embedding: embedding ?? void 0
25138
25566
  });
@@ -25214,6 +25642,7 @@ function roughTokenCount(messages) {
25214
25642
  }
25215
25643
 
25216
25644
  // src/startup/hooks-boot.ts
25645
+ init_clock();
25217
25646
  function registerHooksSubsystem(config) {
25218
25647
  const hooksPath = path21.join(config.workspace, ".hooks.json");
25219
25648
  const settingsHooksPath = path21.join(config.stateDir, "settings.json");
@@ -25259,11 +25688,9 @@ function registerCompactHooks(config) {
25259
25688
  const workspace = input.cwd || input.workspace || "";
25260
25689
  const sessionId = input.session_id || "";
25261
25690
  if (!workspace || !sessionId) return { continue: true };
25262
- const now = /* @__PURE__ */ new Date();
25263
- const bjOffset = 8 * 60;
25264
- const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
25265
- const dateStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")}`;
25266
- const timeStr = `${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
25691
+ const pt = wallParts();
25692
+ const dateStr = todayKey();
25693
+ const timeStr = `${String(pt.hh).padStart(2, "0")}:${String(pt.mm).padStart(2, "0")}`;
25267
25694
  const dailyDir = path21.join(workspace, "memory", "daily");
25268
25695
  const dailyPath = path21.join(dailyDir, `${dateStr}.md`);
25269
25696
  try {
@@ -25721,9 +26148,9 @@ function createCompactGetters(config) {
25721
26148
  }
25722
26149
 
25723
26150
  // src/session/session-history.ts
26151
+ init_clock();
25724
26152
  import fs25 from "node:fs";
25725
26153
  import path25 from "node:path";
25726
- var BEIJING_OFFSET_MS = 8 * 36e5;
25727
26154
  var INJECTED_CONTENT_PATTERNS = [
25728
26155
  /【定时心跳】/,
25729
26156
  /\[内心对话测试\]/,
@@ -25816,9 +26243,9 @@ function findLastRealUserMsg(jsonlPath) {
25816
26243
  const clean = cleanText(text);
25817
26244
  let bjTime;
25818
26245
  try {
25819
- const dt = new Date(ts);
26246
+ const t = wallParts(ts);
25820
26247
  const p = (n) => String(n).padStart(2, "0");
25821
- bjTime = `${dt.getFullYear()}-${p(dt.getMonth() + 1)}-${p(dt.getDate())} ${p(dt.getHours())}:${p(dt.getMinutes())} (Asia/Shanghai)`;
26248
+ bjTime = `${t.y}-${p(t.mo)}-${p(t.d)} ${p(t.hh)}:${p(t.mm)} (${tz()})`;
25822
26249
  } catch {
25823
26250
  bjTime = ts;
25824
26251
  }
@@ -25881,10 +26308,10 @@ function recentMessages(sessions, hours = 12, limit = 60) {
25881
26308
  if (/^HEARTBEAT_OK\b/.test(text.trim())) continue;
25882
26309
  const clean = cleanText(text);
25883
26310
  if (!clean) continue;
25884
- const bj = new Date(dtMs);
26311
+ const bj = wallParts(dtMs);
25885
26312
  const p = (n) => String(n).padStart(2, "0");
25886
26313
  results.push({
25887
- time: `${p(bj.getHours())}:${p(bj.getMinutes())}`,
26314
+ time: `${p(bj.hh)}:${p(bj.mm)}`,
25888
26315
  role,
25889
26316
  text: clean.slice(0, 80),
25890
26317
  timestamp: dtMs,
@@ -25905,6 +26332,7 @@ function lastUserMinutesAgo(sessions) {
25905
26332
  }
25906
26333
 
25907
26334
  // src/heartbeat.ts
26335
+ init_clock();
25908
26336
  var DEFAULT_PROMPT2 = `\u4F60\u662F\u5FC3\u8DF3\u68C0\u67E5\u3002\u9759\u9ED8\u6267\u884C\uFF0C\u4E0D\u8981\u53D1\u6D88\u606F\u7ED9\u4EFB\u4F55\u4EBA\u3002
25909
26337
 
25910
26338
  \u6B65\u9AA41: \u68C0\u67E5\u662F\u5426\u6709\u5F85\u5904\u7406\u4E8B\u9879
@@ -25963,12 +26391,12 @@ var HeartbeatPlugin = class {
25963
26391
  try {
25964
26392
  if (this.cfg.checkOnline !== false) {
25965
26393
  const threshold = this.cfg.activeThresholdMs || 9e5;
25966
- const now2 = Date.now();
26394
+ const now = Date.now();
25967
26395
  const platformMap = sessions.platformMap;
25968
26396
  if (platformMap) {
25969
26397
  for (const [key, sid] of platformMap) {
25970
26398
  const lastUsed = sessions.lastUsed?.get(sid);
25971
- if (lastUsed && now2 - lastUsed < threshold) {
26399
+ if (lastUsed && now - lastUsed < threshold) {
25972
26400
  console.log("[heartbeat] User active, skipping");
25973
26401
  return;
25974
26402
  }
@@ -25977,11 +26405,7 @@ var HeartbeatPlugin = class {
25977
26405
  }
25978
26406
  const sessionId = sessions.getOrCreateSessionId(this.sessionKey);
25979
26407
  const basePrompt = this.cfg.prompt || DEFAULT_PROMPT2;
25980
- const now = /* @__PURE__ */ new Date();
25981
- const bjOffset = 8 * 60;
25982
- const bjTime = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
25983
- const weekdays = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
25984
- const timeStr = `${bjTime.getFullYear()}-${String(bjTime.getMonth() + 1).padStart(2, "0")}-${String(bjTime.getDate()).padStart(2, "0")} ${weekdays[bjTime.getDay()]} ${String(bjTime.getHours()).padStart(2, "0")}:${String(bjTime.getMinutes()).padStart(2, "0")}`;
26408
+ const timeStr = formatWallLabel();
25985
26409
  const lastReal = lastUserMsg(sessions);
25986
26410
  let offlineStr = "";
25987
26411
  if (lastReal) {
@@ -26000,7 +26424,7 @@ var HeartbeatPlugin = class {
26000
26424
  }
26001
26425
  }
26002
26426
  }
26003
- const prompt = `\u5F53\u524D\u65F6\u95F4\uFF1A${timeStr}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09${offlineStr ? `
26427
+ const prompt = `\u5F53\u524D\u65F6\u95F4\uFF1A${timeStr}${offlineStr ? `
26004
26428
  \u79BB\u7EBF\u65F6\u957F\uFF1A${offlineStr}` : ""}
26005
26429
  ${basePrompt}`;
26006
26430
  console.log(`[heartbeat] Tick: ${this.sessionKey} \u2192 session ${sessionId}`);
@@ -26237,8 +26661,9 @@ Do not call goal_update unless the goal is complete or the strict blocked audit
26237
26661
  init_task_manager();
26238
26662
 
26239
26663
  // src/nudge/prompt-builder.ts
26664
+ init_clock();
26240
26665
  function buildPrompt(action, task, taskState, recent, customPrompt, maxNudgePerTask, activeGoal) {
26241
- const time = bjTimeNow();
26666
+ const time = nowLabel();
26242
26667
  const elapsed = taskState.lastProgressAt ? formatDuration2(Date.now() - new Date(taskState.lastProgressAt).getTime()) : "\u65E0\u8BB0\u5F55";
26243
26668
  const remaining = taskState.targetEndAt ? formatDuration2(new Date(taskState.targetEndAt).getTime() - Date.now()) : null;
26244
26669
  const maxN = taskState.maxNudge || maxNudgePerTask || 3;
@@ -26287,12 +26712,8 @@ ${ctx}
26287
26712
  }
26288
26713
  return customPrompt + "\n\n" + ctx;
26289
26714
  }
26290
- function bjTimeNow() {
26291
- const now = /* @__PURE__ */ new Date();
26292
- const bj = new Date(now.getTime() + (8 * 60 + now.getTimezoneOffset()) * 6e4);
26293
- const weekdays = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
26294
- const p = (n) => String(n).padStart(2, "0");
26295
- return `${bj.getFullYear()}-${p(bj.getMonth() + 1)}-${p(bj.getDate())} ${weekdays[bj.getDay()]} ${p(bj.getHours())}:${p(bj.getMinutes())}`;
26715
+ function nowLabel() {
26716
+ return formatWallLabel();
26296
26717
  }
26297
26718
  function formatDuration2(ms) {
26298
26719
  if (ms < 0) return "\u5DF2\u8D85\u65F6";
@@ -26306,6 +26727,111 @@ function formatDuration2(ms) {
26306
26727
  // src/nudge/session-state-reader.ts
26307
26728
  import fs26 from "node:fs";
26308
26729
  import path26 from "node:path";
26730
+
26731
+ // src/calendar/repository.ts
26732
+ init_db();
26733
+ init_clock();
26734
+ function calendarStrToInstant(dateStr, timeExact) {
26735
+ if (!dateStr || !timeExact) return void 0;
26736
+ if (/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(timeExact)) {
26737
+ const ms = Date.parse(timeExact);
26738
+ return Number.isNaN(ms) ? void 0 : new Date(ms).toISOString();
26739
+ }
26740
+ if (!/^\d{2}:\d{2}$/.test(timeExact)) return void 0;
26741
+ if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(dateStr)) {
26742
+ const [y, mo, d] = dateStr.split("-").map(Number);
26743
+ const z = (n) => String(n).padStart(2, "0");
26744
+ return parseWall(`${y}-${z(mo)}-${z(d)}T${timeExact}:00`);
26745
+ }
26746
+ return parseDateStrTime(dateStr, timeExact);
26747
+ }
26748
+ function assertUtc(v, where) {
26749
+ if (v == null) return;
26750
+ if (!/Z$/i.test(v)) throw new Error(`[repository] ${where} \u4E0D\u662F UTC ISO Z: ${v}`);
26751
+ }
26752
+ function toRow(r) {
26753
+ return {
26754
+ id: r.id,
26755
+ type: r.type,
26756
+ status: r.status,
26757
+ event: r.event,
26758
+ dateStr: r.date_str ?? null,
26759
+ timeExact: r.time_exact ?? null,
26760
+ remindAt: r.remind_at ?? null,
26761
+ reminderBeforeMin: r.remind_before_min ?? null
26762
+ };
26763
+ }
26764
+ function getEventTimeExact(workspace, id) {
26765
+ const db2 = openDb(workspace);
26766
+ try {
26767
+ const r = db2.prepare("SELECT time_exact, date_str, status FROM events WHERE id = ?").get(id);
26768
+ if (!r) return null;
26769
+ return { timeExact: r.time_exact ?? null, dateStr: r.date_str ?? null, status: r.status };
26770
+ } finally {
26771
+ db2.close?.();
26772
+ }
26773
+ }
26774
+ function getEventType(workspace, id) {
26775
+ const db2 = openDb(workspace);
26776
+ try {
26777
+ const r = db2.prepare("SELECT type FROM events WHERE id = ?").get(id);
26778
+ return r?.type ?? null;
26779
+ } finally {
26780
+ db2.close?.();
26781
+ }
26782
+ }
26783
+ function lastEventChannel(workspace) {
26784
+ const db2 = openDb(workspace);
26785
+ try {
26786
+ const r = db2.prepare(
26787
+ `SELECT created_channel, created_channel_target FROM events
26788
+ WHERE created_channel IS NOT NULL AND created_channel_target IS NOT NULL
26789
+ ORDER BY id DESC LIMIT 1`
26790
+ ).get();
26791
+ if (!r) return null;
26792
+ return { channel: r.created_channel, target: r.created_channel_target };
26793
+ } finally {
26794
+ db2.close?.();
26795
+ }
26796
+ }
26797
+ function insertEvent(workspace, e, channel) {
26798
+ const db2 = openDb(workspace);
26799
+ try {
26800
+ assertUtc(e.remindAt ?? null, "insertEvent.remindAt");
26801
+ const r = db2.prepare(
26802
+ `INSERT INTO events
26803
+ (type, status, event, date_str, time_exact, remind_before_min, remind_at, reminded,
26804
+ created_at, created_channel, created_channel_target)
26805
+ VALUES (?,?,?,?,?,?,?,0,?,?,?)`
26806
+ ).run(
26807
+ e.type,
26808
+ e.status ?? "pending",
26809
+ e.event,
26810
+ e.dateStr ?? null,
26811
+ e.timeExact ?? null,
26812
+ e.reminderBeforeMin ?? 60,
26813
+ e.remindAt ?? null,
26814
+ nowUtc(),
26815
+ channel?.name ?? null,
26816
+ channel?.target ?? null
26817
+ );
26818
+ return Number(r.lastInsertRowid);
26819
+ } finally {
26820
+ db2.close?.();
26821
+ }
26822
+ }
26823
+ function listPendingTaskRows(workspace) {
26824
+ const db2 = openDb(workspace);
26825
+ try {
26826
+ return db2.prepare(
26827
+ "SELECT * FROM events WHERE status='pending' AND type='task' AND date_str IS NOT NULL"
26828
+ ).all().map(toRow);
26829
+ } finally {
26830
+ db2.close?.();
26831
+ }
26832
+ }
26833
+
26834
+ // src/nudge/session-state-reader.ts
26309
26835
  var RETIRE_PREFIX_MIN = 6;
26310
26836
  function normalizeTitleForMatch(title) {
26311
26837
  return title.toLowerCase().replace(/\s+/g, "");
@@ -26368,11 +26894,8 @@ function parseSessionStateFull(workspace, sessionStateFile) {
26368
26894
  const unlockMatch = rawTitle.match(/unlock:\s*(\d{1,2})\/(\d{1,2})\s+(\d{1,2}):(\d{2})/);
26369
26895
  if (unlockMatch) {
26370
26896
  const [, mo, dy, hh, mm] = unlockMatch;
26371
- const now = /* @__PURE__ */ new Date();
26372
- const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
26373
- const bj = new Date(now.getTime() + bjOffset);
26374
- const year = bj.getUTCFullYear();
26375
- unlockTime = new Date(Date.UTC(year, +mo - 1, +dy, +hh - 8, +mm)).toISOString();
26897
+ const z = (n) => n.padStart(2, "0");
26898
+ unlockTime = calendarStrToInstant(`${mo}/${dy}`, `${z(hh)}:${mm}`);
26376
26899
  }
26377
26900
  }
26378
26901
  const task = {
@@ -26395,9 +26918,6 @@ function taskIdFromTitle(title) {
26395
26918
  return title.toLowerCase().replace(/\s+/g, "-").slice(0, 50) || "unknown-task";
26396
26919
  }
26397
26920
 
26398
- // src/nudge/plugin.ts
26399
- init_db();
26400
-
26401
26921
  // src/nudge/registration-gate.ts
26402
26922
  init_store();
26403
26923
  function shouldBlockWaitingTurn(lastMessage, mode, activeGoal, runningTask, enabled = true) {
@@ -26408,6 +26928,7 @@ function shouldBlockForUnmetGoal(activeGoal, enabled = true) {
26408
26928
  }
26409
26929
 
26410
26930
  // src/nudge/plugin.ts
26931
+ init_clock();
26411
26932
  var DEFAULT_PROMPT3 = `\u4F60\u662F nudge \u68C0\u67E5\u5668\u3002\u9759\u9ED8\u6267\u884C\uFF0C\u4E0D\u8981\u53D1\u6D88\u606F\u7ED9\u4EFB\u4F55\u4EBA\u3002
26412
26933
 
26413
26934
  \u6B65\u9AA4\uFF1A
@@ -26591,7 +27112,7 @@ var NudgePlugin = class {
26591
27112
  console.warn("[stop-hook] waiting language without active goal/task; continuation is not registered");
26592
27113
  enqueueNotification(buildNudgeNotification("prompt", "\u68C0\u6D4B\u5230\u201C\u5B8C\u6210\u540E\u7EE7\u7EED/\u7A0D\u540E\u56DE\u6765\u201D\u7B49\u957F\u671F\u7B49\u5F85\u8BED\u4E49\uFF0C\u4F46\u6CA1\u6709 active Goal \u6216 running Task\u3002\u8BF7\u5148\u767B\u8BB0 /goal \u6216\u540E\u53F0\u4EFB\u52A1\uFF0C\u907F\u514D\u5DE5\u4F5C\u9759\u9ED8\u4E2D\u65AD\u3002"), { sessionId });
26593
27114
  }
26594
- const currentHour = (/* @__PURE__ */ new Date()).getHours();
27115
+ const currentHour = wallParts().hh;
26595
27116
  const isNightTime = currentHour >= 22 || currentHour < 8;
26596
27117
  if (isNightTime) {
26597
27118
  console.log(`[stop-hook] night time (${currentHour}:xx), skipping needLanding/waiting judge`);
@@ -26804,15 +27325,13 @@ var NudgePlugin = class {
26804
27325
  /** 构建 nudge 的投递 route(和 calendar reminder 一样从 DB 读) */
26805
27326
  getRoute(sessions) {
26806
27327
  try {
26807
- const db2 = openDb(this.workspace);
26808
- const row = db2.prepare("SELECT created_channel, created_channel_target FROM events WHERE created_channel IS NOT NULL AND created_channel_target IS NOT NULL ORDER BY id DESC LIMIT 1").get();
26809
- db2.close();
26810
- if (!row?.created_channel || !row?.created_channel_target) {
27328
+ const route = lastEventChannel(this.workspace);
27329
+ if (!route) {
26811
27330
  console.warn("[nudge] No route found in calendar DB, skipping");
26812
27331
  return null;
26813
27332
  }
26814
27333
  const sessionId = sessions.getOrCreateSessionId(this.sessionKey);
26815
- return { sessionId, channel: row.created_channel, channelTarget: row.created_channel_target };
27334
+ return { sessionId, channel: route.channel, channelTarget: route.target };
26816
27335
  } catch (e) {
26817
27336
  console.warn(`[nudge] Failed to read route from DB: ${e.message}`);
26818
27337
  return null;
@@ -27266,11 +27785,10 @@ ${items}
27266
27785
  if (!m) return false;
27267
27786
  const taskId = parseInt(m[1]);
27268
27787
  try {
27269
- const db2 = openDb(this.workspace);
27270
- const row = db2.prepare("SELECT type FROM events WHERE id=?").get(taskId);
27271
- if (!row) return false;
27272
- if (row.type !== "task") {
27273
- console.log(`[nudge] #${taskId} type=${row.type}, not a task, skip`);
27788
+ const type2 = getEventType(this.workspace, taskId);
27789
+ if (!type2) return false;
27790
+ if (type2 !== "task") {
27791
+ console.log(`[nudge] #${taskId} type=${type2}, not a task, skip`);
27274
27792
  return true;
27275
27793
  }
27276
27794
  return false;
@@ -27288,9 +27806,7 @@ ${items}
27288
27806
  if (!m) return null;
27289
27807
  const taskId = parseInt(m[1]);
27290
27808
  try {
27291
- const db2 = openDb(this.workspace);
27292
- const row = db2.prepare("SELECT status FROM events WHERE id=?").get(taskId);
27293
- db2.close();
27809
+ const row = getEventTimeExact(this.workspace, taskId);
27294
27810
  if (!row) return "deleted";
27295
27811
  if (row.status === "done") return "done";
27296
27812
  if (row.status === "archived") return "archived";
@@ -27310,28 +27826,14 @@ ${items}
27310
27826
  if (!m) return false;
27311
27827
  const taskId = parseInt(m[1]);
27312
27828
  try {
27313
- const db2 = openDb(this.workspace);
27314
- const row = db2.prepare("SELECT date_str, time_exact FROM events WHERE id=?").get(taskId);
27829
+ const row = getEventTimeExact(this.workspace, taskId);
27315
27830
  if (!row) return false;
27316
- if (!row.time_exact) return false;
27317
- const now = /* @__PURE__ */ new Date();
27318
- const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
27319
- const bj = new Date(now.getTime() + bjOffset);
27320
- let taskDate = new Date(bj.getFullYear(), 0, 1);
27321
- if (row.date_str) {
27322
- const ds = row.date_str;
27323
- if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(ds)) {
27324
- taskDate = /* @__PURE__ */ new Date(ds + "T00:00:00+08:00");
27325
- } else {
27326
- const [mth, dy] = ds.split("/").map(Number);
27327
- taskDate = new Date(bj.getFullYear(), mth - 1, dy);
27328
- }
27329
- }
27330
- const [hh, mm] = String(row.time_exact).split(":").map(Number);
27331
- taskDate.setHours(hh || 0, mm || 0, 0, 0);
27332
- const diff = taskDate.getTime() - bj.getTime();
27831
+ if (!row.timeExact) return false;
27832
+ const at = calendarStrToInstant(row.dateStr, row.timeExact);
27833
+ if (!at) return false;
27834
+ const diff = Date.parse(at) - Date.now();
27333
27835
  if (diff > 0) {
27334
- console.log(`[nudge] Task #${taskId} scheduled at ${row.date_str} ${row.time_exact}, ${Math.round(diff / 6e4)}min later, skip nudge`);
27836
+ console.log(`[nudge] Task #${taskId} scheduled at ${row.dateStr} ${row.timeExact}, ${Math.round(diff / 6e4)}min later, skip nudge`);
27335
27837
  return true;
27336
27838
  }
27337
27839
  return false;
@@ -27343,27 +27845,19 @@ ${items}
27343
27845
  /** 查 calendar 今天/已逾期的 pending task,返回没在 STATE 里 in_progress 的 */
27344
27846
  checkCalendarDue(_sessions) {
27345
27847
  try {
27346
- const db2 = openDb(this.workspace);
27347
- const now = /* @__PURE__ */ new Date();
27348
- const bjOffset = (8 * 60 + now.getTimezoneOffset()) * 6e4;
27349
- const bj = new Date(now.getTime() + bjOffset);
27350
- const bjHour = bj.getHours();
27351
- const bjMinute = bj.getMinutes();
27352
- const todayStart = new Date(bj.getFullYear(), bj.getMonth(), bj.getDate()).getTime();
27353
- const rows = db2.prepare(
27354
- "SELECT id, event, date_str, time_exact FROM events WHERE status='pending' AND type='task' AND date_str IS NOT NULL"
27355
- ).all();
27356
- db2.close();
27848
+ const rows = listPendingTaskRows(this.workspace);
27849
+ const p = wallParts();
27850
+ const todayStart = Date.parse(startOfLocalDay());
27357
27851
  const due = [];
27358
27852
  for (const r2 of rows) {
27359
- const dayMs = this.parseCalendarDateStr(r2.date_str, bj);
27853
+ const dayMs = this.parseCalendarDateStr(r2.dateStr);
27360
27854
  if (dayMs === null || dayMs > todayStart) continue;
27361
27855
  const isToday2 = dayMs === todayStart;
27362
- if (isToday2 && r2.time_exact) {
27363
- const [h, m] = String(r2.time_exact).split(":").map(Number);
27364
- if (h > bjHour || h === bjHour && m > bjMinute) continue;
27856
+ if (isToday2 && r2.timeExact) {
27857
+ const [h, m] = String(r2.timeExact).split(":").map(Number);
27858
+ if (h > p.hh || h === p.hh && m > p.mm) continue;
27365
27859
  }
27366
- due.push({ id: r2.id, event: r2.event, date_str: r2.date_str, time_exact: r2.time_exact, dayMs, isToday: isToday2 });
27860
+ due.push({ id: r2.id, event: r2.event, dateStr: r2.dateStr, timeExact: r2.timeExact, dayMs, isToday: isToday2 });
27367
27861
  }
27368
27862
  if (due.length === 0) return null;
27369
27863
  due.sort((a, b) => {
@@ -27371,22 +27865,17 @@ ${items}
27371
27865
  return b.dayMs - a.dayMs;
27372
27866
  });
27373
27867
  const r = due[0];
27374
- return `#${r.id} ${r.event} (${r.date_str} ${r.time_exact || ""})${r.isToday ? "" : "\uFF08\u5DF2\u903E\u671F\uFF09"}`.trim();
27868
+ return `#${r.id} ${r.event} (${r.dateStr} ${r.timeExact || ""})${r.isToday ? "" : "\uFF08\u5DF2\u903E\u671F\uFF09"}`.trim();
27375
27869
  } catch (e) {
27376
27870
  console.warn(`[nudge] checkCalendarDue error: ${e.message}`);
27377
27871
  return null;
27378
27872
  }
27379
27873
  }
27380
27874
  /** 解析 date_str 为当日 0 点 epoch ms(北京时间);"M/D" 按当前年,"YYYY-M-D" 按字面年 */
27381
- parseCalendarDateStr(ds, bj) {
27875
+ parseCalendarDateStr(ds) {
27382
27876
  if (!ds) return null;
27383
- if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(ds)) {
27384
- const t = (/* @__PURE__ */ new Date(ds + "T00:00:00+08:00")).getTime();
27385
- return Number.isNaN(t) ? null : t;
27386
- }
27387
- const m = String(ds).match(/^(\d{1,2})\/(\d{1,2})$/);
27388
- if (!m) return null;
27389
- return new Date(bj.getFullYear(), Number(m[1]) - 1, Number(m[2])).getTime();
27877
+ const at = calendarStrToInstant(ds, "00:00");
27878
+ return at ? Date.parse(at) : null;
27390
27879
  }
27391
27880
  /** 检查 carry-over:如果 in_progress task 24h+ 没推进,自动 calendar add-task 排明天 */
27392
27881
  checkCarryOver(task, _sessions) {
@@ -27406,26 +27895,19 @@ ${items}
27406
27895
  taskState.carryOverAt = (/* @__PURE__ */ new Date()).toISOString();
27407
27896
  state2.tasks[task.id] = taskState;
27408
27897
  this.saveState(state2);
27409
- const tomorrow = /* @__PURE__ */ new Date();
27410
- tomorrow.setDate(tomorrow.getDate() + 1);
27411
- const m = tomorrow.getMonth() + 1;
27412
- const d = tomorrow.getDate();
27413
- const dateStr = `${m}/${d}`;
27898
+ const t = wallParts();
27899
+ const tomorrowMs = Date.parse(startOfLocalDay()) + 24 * 60 * 60 * 1e3;
27900
+ const tm = wallParts(tomorrowMs);
27901
+ const dateStr = `${tm.mo}/${tm.d}`;
27414
27902
  try {
27415
- const db2 = openDb(this.workspace);
27416
- db2.prepare(
27417
- `INSERT INTO events (type,status,event,date_str,time_exact,remind_before_min,remind_at,reminded,created_at)
27418
- VALUES ('task','pending',?,?,?,?,?,?,?)`
27419
- ).run(
27420
- `${task.title}\uFF08nudge \u81EA\u52A8\u6392\uFF09`,
27903
+ insertEvent(this.workspace, {
27904
+ type: "task",
27905
+ status: "pending",
27906
+ event: `${task.title}\uFF08nudge \u81EA\u52A8\u6392\uFF09`,
27421
27907
  dateStr,
27422
- null,
27423
- 60,
27424
- null,
27425
- 0,
27426
- (/* @__PURE__ */ new Date()).toISOString()
27427
- );
27428
- db2.close();
27908
+ timeExact: null,
27909
+ reminderBeforeMin: 60
27910
+ });
27429
27911
  console.log(`[nudge] Auto-added carry-over task to calendar: ${task.title} \u2192 ${dateStr}`);
27430
27912
  return true;
27431
27913
  } catch (e) {
@@ -27444,6 +27926,7 @@ import fs31 from "node:fs";
27444
27926
  import path31 from "node:path";
27445
27927
 
27446
27928
  // src/inner-voice/activity.ts
27929
+ init_clock();
27447
27930
  function checkActivity(sessions, activeThresholdMs) {
27448
27931
  const now = Date.now();
27449
27932
  const mins = lastUserMinutesAgo(sessions);
@@ -27453,18 +27936,10 @@ function checkActivity(sessions, activeThresholdMs) {
27453
27936
  isActive,
27454
27937
  silenceMin,
27455
27938
  silenceStr: formatSilence(silenceMin),
27456
- timeStr: formatBeijingTime(now),
27939
+ timeStr: formatWallLabel(now),
27457
27940
  hintProb: calcHintProb(silenceMin)
27458
27941
  };
27459
27942
  }
27460
- function formatBeijingTime(ms) {
27461
- const now = new Date(ms);
27462
- const bjOffset = 8 * 60;
27463
- const bj = new Date(now.getTime() + (bjOffset + now.getTimezoneOffset()) * 6e4);
27464
- const weekdays = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
27465
- const pad = (n) => String(n).padStart(2, "0");
27466
- return `${bj.getFullYear()}-${pad(bj.getMonth() + 1)}-${pad(bj.getDate())} ${weekdays[bj.getDay()]} ${pad(bj.getHours())}:${pad(bj.getMinutes())}`;
27467
- }
27468
27943
  function formatSilence(min) {
27469
27944
  if (min < 1) return "\u521A\u521A";
27470
27945
  if (min < 60) return `${min}\u5206\u949F`;
@@ -27480,6 +27955,7 @@ function calcHintProb(min) {
27480
27955
  }
27481
27956
 
27482
27957
  // src/inner-voice/emotional-state.ts
27958
+ init_clock();
27483
27959
  import fs28 from "node:fs";
27484
27960
  import path28 from "node:path";
27485
27961
  var NEUTRAL = 0.5;
@@ -27636,7 +28112,7 @@ function detectEvents(messages) {
27636
28112
  const tsMs = parseTs(m.timestamp);
27637
28113
  const hoursAgo = tsMs != null ? (now - tsMs) / 36e5 : 0;
27638
28114
  events.push({
27639
- ts: tsMs != null ? new Date(tsMs + 8 * 36e5).toISOString().replace("Z", "+08:00") : m.timestamp,
28115
+ ts: tsMs != null ? `${toWall(tsMs).replace(" ", "T")}${localOffsetSuffix(tsMs)}` : m.timestamp,
27640
28116
  hoursAgo,
27641
28117
  valence: Math.round(score * 100) / 100,
27642
28118
  tags,
@@ -27736,15 +28212,16 @@ function clamp01(x) {
27736
28212
  return clamp(x, 0, 1);
27737
28213
  }
27738
28214
  function nowIsoBj() {
27739
- return new Date(Date.now() + 8 * 36e5).toISOString().replace("Z", "+08:00");
28215
+ return `${toWall().replace(" ", "T")}${localOffsetSuffix()}`;
27740
28216
  }
27741
28217
  function formatBj(d, withSec) {
27742
- const bj = new Date(d.getTime() + (8 * 60 + d.getTimezoneOffset()) * 6e4);
27743
28218
  const p = (n) => String(n).padStart(2, "0");
27744
- return `${bj.getFullYear()}-${p(bj.getMonth() + 1)}-${p(bj.getDate())} ${p(bj.getHours())}:${p(bj.getMinutes())}${withSec ? ":" + p(bj.getSeconds()) : ""}`;
28219
+ const t = wallParts(d);
28220
+ return `${t.y}-${p(t.mo)}-${p(t.d)} ${p(t.hh)}:${p(t.mm)}${withSec ? ":" + p(t.ss) : ""}`;
27745
28221
  }
27746
28222
 
27747
28223
  // src/inner-voice/topics-scorer.ts
28224
+ init_clock();
27748
28225
  import fs29 from "node:fs";
27749
28226
  import path29 from "node:path";
27750
28227
  var HALF_LIFE_DAYS = 3;
@@ -27802,7 +28279,7 @@ function pickTopic(workspace, typeFilter, opts) {
27802
28279
  const key = chosen.relpath;
27803
28280
  const entry = usage[key] ?? { count: 0, lastSelected: null };
27804
28281
  entry.count += 1;
27805
- entry.lastSelected = new Date(now + 8 * 36e5).toISOString().replace("Z", "+08:00");
28282
+ entry.lastSelected = toUtc(new Date(now));
27806
28283
  usage[key] = entry;
27807
28284
  saveJson2(usageFile, usage);
27808
28285
  let content = "";
@@ -27905,15 +28382,15 @@ function saveJson2(file2, data) {
27905
28382
  }
27906
28383
 
27907
28384
  // src/inner-voice/memory-reader.ts
28385
+ init_clock();
27908
28386
  import fs30 from "node:fs";
27909
28387
  import path30 from "node:path";
27910
28388
  var US_HALF_LIFE_DAYS = 10;
27911
28389
  var US_MAX_LINES = 60;
27912
28390
  function readRecentMemory(workspace) {
27913
28391
  const dir = path30.join(workspace, "memory");
27914
- const now = new Date(Date.now() + 8 * 36e5);
27915
- const today = formatYmd(now);
27916
- const yesterday = formatYmd(new Date(now.getTime() - 864e5));
28392
+ const today = dailyFileKeyFor();
28393
+ const yesterday = dailyFileKeyFor(Date.now() - 864e5);
27917
28394
  return {
27918
28395
  today: readIfExists(path30.join(dir, `${today}.md`)),
27919
28396
  yesterday: readIfExists(path30.join(dir, `${yesterday}.md`))
@@ -27962,7 +28439,7 @@ function splitSections(content) {
27962
28439
  }
27963
28440
  function recencyWeight(dateStr) {
27964
28441
  if (!dateStr) return 0.5;
27965
- const ms = Date.parse(dateStr + "T00:00:00+08:00");
28442
+ const ms = Date.parse(parseWall(`${dateStr}T00:00:00`));
27966
28443
  if (Number.isNaN(ms)) return 0.5;
27967
28444
  const daysAgo = Math.max(0, (Date.now() - ms) / 864e5);
27968
28445
  return Math.exp(-0.693 * daysAgo / US_HALF_LIFE_DAYS);
@@ -27974,14 +28451,14 @@ function readIfExists(file2) {
27974
28451
  return "";
27975
28452
  }
27976
28453
  }
27977
- function formatYmd(d) {
27978
- const p = (n) => String(n).padStart(2, "0");
27979
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
28454
+ function dailyFileKeyFor(at) {
28455
+ return todayKey(at);
27980
28456
  }
27981
28457
 
27982
28458
  // src/inner-voice/plugin.ts
27983
28459
  init_helpers();
27984
28460
  init_attachments();
28461
+ init_clock();
27985
28462
  var DEFAULT_PROMPT4 = `\u6839\u636E\u4E0B\u9762\u7684\u4E0A\u4E0B\u6587\uFF0C\u7528\u7B2C\u4E00\u4EBA\u79F0\u751F\u6210\u4E00\u4E2A\u5FF5\u5934\u3002
27986
28463
 
27987
28464
  \u5FF5\u5934\u89C4\u5219\uFF1A
@@ -28077,7 +28554,7 @@ var InnerVoicePlugin = class {
28077
28554
  async buildContext(activity, sessions) {
28078
28555
  const lines = [];
28079
28556
  let emoTopic = null;
28080
- lines.push(`\u5F53\u524D\u65F6\u95F4\uFF1A${activity.timeStr}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`);
28557
+ lines.push(`\u5F53\u524D\u65F6\u95F4\uFF1A${formatForLlm()}`);
28081
28558
  lines.push(`\u8DDD\u4E0A\u6B21\u8DDFTA\u4E92\u52A8\uFF1A${activity.silenceStr}`);
28082
28559
  try {
28083
28560
  const emo = await updateEmotionalState(this.workspace, sessions);
@@ -28263,7 +28740,7 @@ var InnerVoicePlugin = class {
28263
28740
  const logDir = path31.join(this.workspace, "inner-voice");
28264
28741
  fs31.mkdirSync(logDir, { recursive: true });
28265
28742
  const logPath = path31.join(logDir, "xiaoyi.log");
28266
- const ts = formatBeijingTs(/* @__PURE__ */ new Date());
28743
+ const ts = formatLogTs(/* @__PURE__ */ new Date());
28267
28744
  const hintStatus = hintTriggered ? `YES (${(hintText || "").trim()})` : "no";
28268
28745
  fs31.appendFileSync(
28269
28746
  logPath,
@@ -28277,10 +28754,8 @@ var InnerVoicePlugin = class {
28277
28754
  }
28278
28755
  }
28279
28756
  };
28280
- function formatBeijingTs(d) {
28281
- const bj = new Date(d.getTime() + (8 * 60 + d.getTimezoneOffset()) * 6e4);
28282
- const pad = (n) => String(n).padStart(2, "0");
28283
- return `${bj.getFullYear()}-${pad(bj.getMonth() + 1)}-${pad(bj.getDate())} ${pad(bj.getHours())}:${pad(bj.getMinutes())}:${pad(bj.getSeconds())}`;
28757
+ function formatLogTs(d) {
28758
+ return formatLog(d);
28284
28759
  }
28285
28760
 
28286
28761
  // src/calendar/reminder-plugin.ts
@@ -28352,23 +28827,44 @@ var CalendarReminderPlugin = class {
28352
28827
  };
28353
28828
 
28354
28829
  // src/goals/calendar-adapter.ts
28355
- init_db();
28356
28830
  init_store();
28357
28831
  init_task_manager();
28358
28832
  function bindGoalCalendar(stateDir, goalId, stepId, calendarId) {
28359
- const db2 = openDb(stateDir);
28360
- const row = db2.prepare("SELECT time_exact, status FROM events WHERE id = ?").get(calendarId);
28361
- db2.close?.();
28833
+ const row = getEventTimeExact(stateDir, calendarId);
28362
28834
  if (!row) throw new Error(`Calendar event not found: ${calendarId}`);
28363
28835
  if (row.status === "done" || row.status === "archived") throw new Error(`Calendar event is terminal: ${row.status}`);
28364
- return bindCalendar(stateDir, goalId, stepId, calendarId, row.time_exact ? new Date(row.time_exact).toISOString() : void 0);
28836
+ const deadlineAt = row.dateStr && row.timeExact ? calendarStrToInstant(row.dateStr, row.timeExact) : void 0;
28837
+ return bindGoalStep(stateDir, goalId, stepId, "calendar", calendarId, deadlineAt);
28838
+ }
28839
+ function backfillCalendarDeadlines(stateDir) {
28840
+ let filled = 0;
28841
+ for (const goal of listGoals(stateDir)) {
28842
+ if (goal.status === "completed" || goal.status === "cancelled") continue;
28843
+ for (const step of goal.steps) {
28844
+ if (!step.calendarId || step.deadlineAt) continue;
28845
+ if (step.status === "completed") continue;
28846
+ const row = getEventTimeExact(stateDir, step.calendarId);
28847
+ if (!row?.dateStr || !row.timeExact) continue;
28848
+ const at = calendarStrToInstant(row.dateStr, row.timeExact);
28849
+ if (!at) continue;
28850
+ const n = filled;
28851
+ updateGoal(stateDir, goal.id, (g) => {
28852
+ const s = g.steps.find((x) => x.id === step.id);
28853
+ if (s && !s.deadlineAt) {
28854
+ s.deadlineAt = at;
28855
+ filled++;
28856
+ }
28857
+ });
28858
+ if (filled === n) console.warn(`[goal-calendar] backfill \u672A\u751F\u6548: ${goal.id}/${step.id}`);
28859
+ }
28860
+ }
28861
+ return filled;
28365
28862
  }
28366
28863
  function reconcileCalendarDeadlines(stateDir, now = Date.now()) {
28367
28864
  let changed = 0;
28368
- const db2 = openDb(stateDir);
28369
28865
  for (const goal of listGoals(stateDir)) for (const step of goal.steps) {
28370
28866
  if (!step.calendarId) continue;
28371
- const row = db2.prepare("SELECT status FROM events WHERE id = ?").get(step.calendarId);
28867
+ const row = getEventTimeExact(stateDir, step.calendarId);
28372
28868
  if (row?.status === "done" || row?.status === "archived") {
28373
28869
  if (step.status !== "completed") {
28374
28870
  updateGoal(stateDir, goal.id, (g) => {
@@ -28400,13 +28896,24 @@ function reconcileCalendarDeadlines(stateDir, now = Date.now()) {
28400
28896
  enqueueNotification(buildNudgeNotification("calendar", `Goal ${goal.id} step ${step.id} exceeded Calendar deadline #${step.calendarId}`), void 0, `goal-calendar-deadline:${goal.id}:${step.id}`);
28401
28897
  changed++;
28402
28898
  }
28403
- db2.close?.();
28404
28899
  return changed;
28405
28900
  }
28406
28901
 
28407
28902
  // src/startup/subsystems.ts
28408
28903
  init_provider_factory();
28409
28904
  init_live();
28905
+ function makeTimezoneEntry(restartCron) {
28906
+ return {
28907
+ name: "timezone",
28908
+ // 只吃 timezone —— 别的 config 变了不重建 cron
28909
+ select: (cfg) => ({ timezone: cfg?.timezone ?? null }),
28910
+ start: () => {
28911
+ void restartCron();
28912
+ },
28913
+ stop: () => {
28914
+ }
28915
+ };
28916
+ }
28410
28917
  async function bootSubsystems(ctx) {
28411
28918
  const { config } = ctx;
28412
28919
  const sessions = ctx.sessions;
@@ -28504,6 +29011,12 @@ async function bootSubsystems(ctx) {
28504
29011
  calCfg.reminderIntervalMs
28505
29012
  );
28506
29013
  calendarReminder.start(sessions, deps);
29014
+ try {
29015
+ const n = backfillCalendarDeadlines(config.workspace);
29016
+ if (n > 0) console.log(`[goal-calendar] \u5B58\u91CF\u8865\u5199 deadlineAt: ${n} \u4E2A\u6B65\u9AA4`);
29017
+ } catch (err) {
29018
+ console.warn(`[goal-calendar] backfill failed: ${err.message}`);
29019
+ }
28507
29020
  const goalCalendarTick = setInterval(() => {
28508
29021
  try {
28509
29022
  reconcileCalendarDeadlines(config.workspace);
@@ -28603,6 +29116,7 @@ async function bootSubsystems(ctx) {
28603
29116
  start: startCron,
28604
29117
  stop: stopCron
28605
29118
  });
29119
+ reloadOrchestrator.register(makeTimezoneEntry(startCron));
28606
29120
  }
28607
29121
  }
28608
29122
 
@@ -28770,6 +29284,7 @@ ${notifications}
28770
29284
  init_registry();
28771
29285
 
28772
29286
  // src/integrations/oac-bridge.ts
29287
+ init_well_formed();
28773
29288
  function registerOacBridge(httpServer, dispatcher, deps, config) {
28774
29289
  messageHooks.registerOnResult("oac-bridge-reply", async (ctx) => {
28775
29290
  if (ctx.inbound.channel !== "oac") return null;
@@ -28778,7 +29293,7 @@ function registerOacBridge(httpServer, dispatcher, deps, config) {
28778
29293
  const resp = await fetch(oacCallbackUrl, {
28779
29294
  method: "POST",
28780
29295
  headers: { "Content-Type": "application/json" },
28781
- body: JSON.stringify({ oac_session_id: ctx.inbound.from, text: ctx.response })
29296
+ body: safeStringify({ oac_session_id: ctx.inbound.from, text: ctx.response })
28782
29297
  });
28783
29298
  console.log(`[oac-bridge] Reply POST ${oacCallbackUrl}: ${resp.status} (${ctx.response.length} chars)`);
28784
29299
  } catch (err) {
@@ -28994,6 +29509,7 @@ import fs35 from "node:fs";
28994
29509
 
28995
29510
  // src/optional/voice-chat/bridge.ts
28996
29511
  init_live();
29512
+ init_well_formed();
28997
29513
  function resolveVoiceReplyUrl() {
28998
29514
  return liveConfig.get("voiceChat.callbackUrl") || "https://localhost:8011/voice-reply";
28999
29515
  }
@@ -29028,7 +29544,7 @@ function registerVoiceChatBridge(httpServer, dispatcher, deps, config, sessions,
29028
29544
  const resp = await fetch(callbackUrl, {
29029
29545
  method: "POST",
29030
29546
  headers: { "Content-Type": "application/json" },
29031
- body: JSON.stringify({ session_id: sessionId, text, ...seq !== void 0 ? { seq } : {}, ...end !== void 0 ? { end } : {} })
29547
+ body: safeStringify({ session_id: sessionId, text, ...seq !== void 0 ? { seq } : {}, ...end !== void 0 ? { end } : {} })
29032
29548
  });
29033
29549
  console.log(`[voice-chat] Reply POST ${callbackUrl}: ${resp.status} (${text.length} chars${seq !== void 0 ? ` seg#${seq}${end ? " end" : ""}` : ""})`);
29034
29550
  }
@@ -33806,6 +34322,7 @@ import { WebSocketServer, WebSocket } from "ws";
33806
34322
 
33807
34323
  // src/tools/my-voice.ts
33808
34324
  init_live();
34325
+ init_well_formed();
33809
34326
  init_registry();
33810
34327
 
33811
34328
  // src/tools/opus-encoder.ts
@@ -34164,7 +34681,7 @@ async function ttsQwen3Local(text, cfg, onChunk) {
34164
34681
  const res = await fetch(`${url}/tts`, {
34165
34682
  method: "POST",
34166
34683
  headers: { "Content-Type": "application/json" },
34167
- body: JSON.stringify({
34684
+ body: safeStringify({
34168
34685
  text,
34169
34686
  voice: cfg.voice || "xiaoya",
34170
34687
  lang: cfg.lang || "Chinese"
@@ -34195,7 +34712,7 @@ async function ttsQwen3Opus(text, cfg) {
34195
34712
  const res = await fetch(`${url}/tts?format=opus`, {
34196
34713
  method: "POST",
34197
34714
  headers: { "Content-Type": "application/json; charset=utf-8" },
34198
- body: JSON.stringify({
34715
+ body: safeStringify({
34199
34716
  text,
34200
34717
  voice: cfg.voice || "xiaoya",
34201
34718
  lang: cfg.lang || "Chinese"
@@ -37673,6 +38190,7 @@ Examples:
37673
38190
  // src/tools/my-eyes.ts
37674
38191
  init_live();
37675
38192
  init_registry();
38193
+ init_well_formed();
37676
38194
  import * as fs46 from "node:fs";
37677
38195
  import * as path45 from "node:path";
37678
38196
  var MIME_MAP = {
@@ -37683,19 +38201,21 @@ var MIME_MAP = {
37683
38201
  ".gif": "gif",
37684
38202
  ".bmp": "bmp"
37685
38203
  };
38204
+ var VIDEO_EXT = /\.(mp4|mov)$/i;
38205
+ var VIDEO_MAX_BYTES = 40 * 1024 * 1024;
37686
38206
  function resolveLatestImage(specifiedPath, mediaDir) {
37687
38207
  if (specifiedPath && fs46.existsSync(specifiedPath)) return specifiedPath;
37688
38208
  if (!fs46.existsSync(mediaDir)) return null;
37689
- const files = fs46.readdirSync(mediaDir).filter((f) => /\.(jpg|jpeg|png|webp|gif|bmp)$/i.test(f)).map((f) => ({ name: f, p: path45.join(mediaDir, f), mtime: fs46.statSync(path45.join(mediaDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
38209
+ const files = fs46.readdirSync(mediaDir).filter((f) => /\.(jpg|jpeg|png|webp|gif|bmp|mp4|mov)$/i.test(f)).map((f) => ({ name: f, p: path45.join(mediaDir, f), mtime: fs46.statSync(path45.join(mediaDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
37690
38210
  return files[0]?.p || null;
37691
38211
  }
37692
38212
  registry.register({
37693
38213
  name: "my_eyes",
37694
- description: "\u26A0\uFE0F IRON RULE: when you receive an image you MUST use my_eyes to look at it! DO NOT use read to look at images (can't see details). Use my_eyes for: \u6536\u5230\u56FE\u7247/\u770B\u56FE/\u5206\u6790\u56FE\u7247/\u7167\u7247/\u622A\u56FE/\u4EFB\u4F55\u9700\u8981\u5206\u6790\u56FE\u7247\u7684\u573A\u666F.",
38214
+ description: "\u26A0\uFE0F IRON RULE: when you receive an image you MUST use my_eyes to look at it! DO NOT use read to look at images (can't see details). Use my_eyes for: \u6536\u5230\u56FE\u7247/\u770B\u56FE/\u5206\u6790\u56FE\u7247/\u7167\u7247/\u622A\u56FE/\u4EFB\u4F55\u9700\u8981\u5206\u6790\u56FE\u7247\u7684\u573A\u666F. \u89C6\u9891\u4E5F\u652F\u6301\uFF1A\u6536\u5230\u89C6\u9891/\u770B\u89C6\u9891/\u5206\u6790\u89C6\u9891\u65F6\u4F20 .mp4/.mov \u8DEF\u5F84\uFF08M3 video_url\uFF09.",
37695
38215
  schema: {
37696
38216
  type: "object",
37697
38217
  properties: {
37698
- image_path: { type: "string", description: "Absolute path to the image file to analyze." },
38218
+ image_path: { type: "string", description: "Absolute path to the image (or .mp4/.mov video) file to analyze." },
37699
38219
  prompt: { type: "string", description: 'What to ask about the image. Default: "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9"' }
37700
38220
  },
37701
38221
  required: ["image_path"]
@@ -37724,6 +38244,42 @@ registry.register({
37724
38244
  }
37725
38245
  const rawPrompt = args.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
37726
38246
  const ext = path45.extname(imagePath).toLowerCase();
38247
+ if (VIDEO_EXT.test(imagePath)) {
38248
+ const stat4 = fs46.statSync(imagePath);
38249
+ if (stat4.size > VIDEO_MAX_BYTES) {
38250
+ return { content: `Error: video too large (${(stat4.size / 1e6).toFixed(1)}MB > 40MB). Compress or trim first.`, isError: true };
38251
+ }
38252
+ const explicit = liveConfig.get("tools.my_eyes.videoBaseUrl");
38253
+ const baseUrl = explicit || providerCfg.baseUrl.replace(/\/anthropic\/?$/, "/v1").replace(/\/$/, "");
38254
+ const vidB64 = fs46.readFileSync(imagePath).toString("base64");
38255
+ try {
38256
+ const res = await fetch(`${baseUrl}/chat/completions`, {
38257
+ method: "POST",
38258
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${providerCfg.apiKey}` },
38259
+ body: safeStringify({
38260
+ model: modelId,
38261
+ messages: [{
38262
+ role: "user",
38263
+ content: [
38264
+ { type: "video_url", video_url: { url: `data:video/mp4;base64,${vidB64}` } },
38265
+ { type: "text", text: rawPrompt }
38266
+ ]
38267
+ }],
38268
+ max_tokens: 4096
38269
+ })
38270
+ });
38271
+ if (!res.ok) {
38272
+ const t = await res.text();
38273
+ return { content: `Video analysis failed: HTTP ${res.status} ${t.slice(0, 300)}`, isError: true };
38274
+ }
38275
+ const data = await res.json();
38276
+ const content = data.choices?.[0]?.message?.content || "";
38277
+ if (!content.trim()) return { content: "Video analysis returned empty.", isError: true };
38278
+ return { content };
38279
+ } catch (e) {
38280
+ return { content: `Video analysis failed: ${e.message}`, isError: true };
38281
+ }
38282
+ }
37727
38283
  const mime = MIME_MAP[ext] || "jpeg";
37728
38284
  const imgB64 = fs46.readFileSync(imagePath).toString("base64");
37729
38285
  const userMsg = {
@@ -37760,6 +38316,7 @@ registry.register({
37760
38316
  // src/tools/my-selfie.ts
37761
38317
  init_live();
37762
38318
  init_registry();
38319
+ init_well_formed();
37763
38320
  import * as fs47 from "node:fs";
37764
38321
  import * as path46 from "node:path";
37765
38322
  function getProxyDispatcher2() {
@@ -37773,11 +38330,46 @@ function getProxyDispatcher2() {
37773
38330
  return void 0;
37774
38331
  }
37775
38332
  }
38333
+ function dimsOf(p) {
38334
+ try {
38335
+ const b = fs47.readFileSync(p);
38336
+ let w = 0, h = 0;
38337
+ if (b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
38338
+ w = b.readUInt32BE(16);
38339
+ h = b.readUInt32BE(20);
38340
+ } else if (b[0] === 255 && b[1] === 216) {
38341
+ let i = 2;
38342
+ while (i < b.length - 9) {
38343
+ if (b[i] !== 255) {
38344
+ i++;
38345
+ continue;
38346
+ }
38347
+ const m = b[i + 1];
38348
+ if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) {
38349
+ h = b.readUInt16BE(i + 5);
38350
+ w = b.readUInt16BE(i + 7);
38351
+ break;
38352
+ }
38353
+ i += 2 + b.readUInt16BE(i + 2);
38354
+ }
38355
+ } else {
38356
+ return null;
38357
+ }
38358
+ if (!w || !h) return null;
38359
+ const bad = [];
38360
+ if (w < 256 || w > 4096) bad.push(`\u5BBD ${w} \u4E0D\u5728 256~4096`);
38361
+ if (h < 256 || h > 4096) bad.push(`\u9AD8 ${h} \u4E0D\u5728 256~4096`);
38362
+ const r = Math.max(w, h) / Math.min(w, h);
38363
+ if (r > 2) bad.push(`\u957F\u5BBD\u6BD4 ${r.toFixed(2)}:1 \u8D85\u8FC7 2:1`);
38364
+ return bad.length ? `${w}x${h} \u4E0D\u5408\u683C\uFF1A${bad.join("\uFF1B")}` : `${w}x${h}`;
38365
+ } catch {
38366
+ return null;
38367
+ }
38368
+ }
37776
38369
  var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
37777
38370
  var DEFAULT_RESOLUTION = "1k";
37778
- var FAL_KEY_FALLBACK = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
37779
38371
  function getFalKey(cfg) {
37780
- return cfg?.providers?.fal?.apiKey || cfg?.my_selfie?.falKey || FAL_KEY_FALLBACK;
38372
+ return cfg?.providers?.fal?.apiKey || cfg?.my_selfie?.falKey || process.env.FAL_API_KEY || "";
37781
38373
  }
37782
38374
  var DEFAULT_REFERENCES = [
37783
38375
  { name: "default", p: "images/xiaomei_clean_v2.png" },
@@ -37803,6 +38395,73 @@ function getXaiKey(ctx) {
37803
38395
  const xaiCfg = cfg.providers?.xai || cfg.models?.providers?.xai;
37804
38396
  return xaiCfg?.apiKey || "";
37805
38397
  }
38398
+ function getRelayCfg(cfg) {
38399
+ const id = cfg?.my_selfie?.relayProviderId || cfg?.tools?.my_selfie?.relayProviderId || "7yi";
38400
+ const p = cfg?.models?.providers?.[id] || cfg?.providers?.[id];
38401
+ return { baseUrl: String(p?.baseUrl || "").replace(/\/+$/, ""), apiKey: p?.apiKey || "" };
38402
+ }
38403
+ function resolvePortraitDesc(argPortrait, cfgPortrait) {
38404
+ const fromArg = typeof argPortrait === "string" ? argPortrait.trim() : "";
38405
+ if (fromArg) return fromArg;
38406
+ return typeof cfgPortrait === "string" ? cfgPortrait.trim() : "";
38407
+ }
38408
+ async function generateWithRelay(imageB64, prompt, mime, baseUrl, apiKey, model) {
38409
+ const res = await fetch(`${baseUrl}/images/edits`, {
38410
+ method: "POST",
38411
+ headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
38412
+ // safeStringify 不是安全选项而是必须:prompt 里一个孤立代理(半个 emoji)
38413
+ // 就会让上游 500。同批上游提交刚在 fal/grok/comfy 三处修过同一个坑。
38414
+ body: safeStringify({
38415
+ model,
38416
+ prompt,
38417
+ n: 1,
38418
+ response_format: "b64_json",
38419
+ images: [{ image_url: `data:${mime};base64,${imageB64}` }]
38420
+ })
38421
+ });
38422
+ if (!res.ok) {
38423
+ const text = await res.text();
38424
+ throw new Error(`relay ${res.status}: ${text.slice(0, 300)}`);
38425
+ }
38426
+ const data = await res.json();
38427
+ if (!data.data?.length) throw new Error("No images returned from relay");
38428
+ return Buffer.from(data.data[0].b64_json, "base64");
38429
+ }
38430
+ var PORTRAIT_PROMPT = (desc) => `A portrait photo of a person, ${desc}. Front-facing, head and shoulders, neutral background, natural lighting, photorealistic, sharp focus on the face, looking at the camera.`;
38431
+ async function generatePortraitWithRelay(desc, baseUrl, apiKey, model) {
38432
+ const res = await fetch(`${baseUrl}/images/generations`, {
38433
+ method: "POST",
38434
+ headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
38435
+ // 同上:描述里可能带 emoji,孤立代理会让上游 500
38436
+ body: safeStringify({
38437
+ model,
38438
+ prompt: PORTRAIT_PROMPT(desc),
38439
+ n: 1,
38440
+ size: "1024x1024",
38441
+ response_format: "b64_json"
38442
+ })
38443
+ });
38444
+ if (!res.ok) {
38445
+ const text = await res.text();
38446
+ throw new Error(`relay portrait ${res.status}: ${text.slice(0, 300)}`);
38447
+ }
38448
+ const data = await res.json();
38449
+ if (!data.data?.length) throw new Error("No images returned from relay (portrait)");
38450
+ return Buffer.from(data.data[0].b64_json, "base64");
38451
+ }
38452
+ function registerPortrait(relPath, name) {
38453
+ try {
38454
+ const cfg = liveConfig.all();
38455
+ const cur = cfg?.tools?.my_selfie?.references || cfg?.my_selfie?.references || [];
38456
+ const next = Array.isArray(cur) ? cur.filter((r) => r?.name !== name) : [];
38457
+ next.unshift({ name, path: relPath });
38458
+ const path58 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
38459
+ void liveConfig.set(path58, next);
38460
+ console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path58}[0] = ${relPath}`);
38461
+ } catch (e) {
38462
+ console.warn(`[my-selfie] \u5199\u5165 references \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u51FA\u56FE\uFF09: ${e.message}`);
38463
+ }
38464
+ }
37806
38465
  var MODE_PROMPTS2 = {
37807
38466
  mirror: (ctx) => `make a pic of this person, but ${ctx}. the person is taking a mirror selfie in front of a mirror, holding a phone`,
37808
38467
  direct: (ctx) => `a close-up selfie taken by herself ${ctx}, direct eye contact with the camera, looking straight into the lens, eyes centered and clearly visible, not a mirror selfie, phone held at arm's length, face fully visible`,
@@ -37859,10 +38518,16 @@ async function generateWithFal(imageB64, prompt, resolution) {
37859
38518
  resolution
37860
38519
  };
37861
38520
  if (aspectRatio) body.aspect_ratio = aspectRatio;
38521
+ const falKey = getFalKey(cfg);
38522
+ if (!falKey) {
38523
+ throw new Error(
38524
+ `fal key \u672A\u914D\u7F6E\u3002\u8BBE config \u7684 providers.fal.apiKey \u6216 my_selfie.falKey\uFF0C\u6216\u5728 secrets .env\uFF08~/.engine7-secrets/<config\u540D>.env\uFF09\u91CC\u52A0 FAL_API_KEY\u3002`
38525
+ );
38526
+ }
37862
38527
  const res = await fetch(FAL_ENDPOINT, {
37863
38528
  method: "POST",
37864
- headers: { "Authorization": `Key ${getFalKey(cfg)}`, "Content-Type": "application/json" },
37865
- body: JSON.stringify(body)
38529
+ headers: { "Authorization": `Key ${falKey}`, "Content-Type": "application/json" },
38530
+ body: safeStringify(body)
37866
38531
  });
37867
38532
  if (!res.ok) {
37868
38533
  const text = await res.text();
@@ -37899,7 +38564,7 @@ async function comfyQueue(server, workflow) {
37899
38564
  const res = await fetch(`${server}/prompt`, {
37900
38565
  method: "POST",
37901
38566
  headers: { "Content-Type": "application/json" },
37902
- body: JSON.stringify({ prompt: workflow })
38567
+ body: safeStringify({ prompt: workflow })
37903
38568
  });
37904
38569
  if (!res.ok) return null;
37905
38570
  const data = await res.json();
@@ -38027,7 +38692,7 @@ async function generateWithGrok(imageB64, prompt, xaiKey) {
38027
38692
  "Authorization": `Bearer ${xaiKey}`,
38028
38693
  "Content-Type": "application/json"
38029
38694
  },
38030
- body: JSON.stringify({
38695
+ body: safeStringify({
38031
38696
  model: "grok-imagine-image-quality",
38032
38697
  prompt,
38033
38698
  image: {
@@ -38049,15 +38714,17 @@ async function generateWithGrok(imageB64, prompt, xaiKey) {
38049
38714
  }
38050
38715
  registry.register({
38051
38716
  name: "my_selfie",
38052
- description: "Generate consistent-character selfies. Use for: \u81EA\u62CD/\u62CD\u7167/\u53D1\u81EA\u62CD/\u770B\u770B\u4F60/\u60F3\u770B\u4F60/\u62CD\u5F20\u7167/\u6765\u5F20\u81EA\u62CD/\u53D1\u5F20\u7167\u7247/\u7ED9\u6211\u770B\u770B\u4F60/\u6362\u4E2A\u59FF\u52BF/\u5C55\u793A\u4E00\u4E0B. Supports modes: mirror (\u955C\u5B50\u81EA\u62CD), direct (\u6B63\u9762\u81EA\u62CD), lifestyle (\u751F\u6D3B\u7167). Auto-detects mode. Reference images configured per agent via my_selfie.references in config.",
38717
+ description: "Generate consistent-character selfies. Use for: \u81EA\u62CD/\u62CD\u7167/\u53D1\u81EA\u62CD/\u770B\u770B\u4F60/\u60F3\u770B\u4F60/\u62CD\u5F20\u7167/\u6765\u5F20\u81EA\u62CD/\u53D1\u5F20\u7167\u7247/\u7ED9\u6211\u770B\u770B\u4F60/\u6362\u4E2A\u59FF\u52BF/\u5C55\u793A\u4E00\u4E0B. Supports modes: mirror (\u955C\u5B50\u81EA\u62CD), direct (\u6B63\u9762\u81EA\u62CD), lifestyle (\u751F\u6D3B\u7167). Auto-detects mode. Reference images configured per agent via my_selfie.references in config. \u7B2C\u4E00\u6B21\u8C03\u7528\u65F6\u82E5\u8FD9\u4E2A\u52A9\u624B\u8FD8\u6CA1\u6709\u5F62\u8C61\uFF08\u6CA1\u6709\u53C2\u8003\u56FE\uFF09\uFF0C\u5DE5\u5177\u4F1A\u8FD4\u56DE\u4E00\u6BB5\u63D0\u793A\u800C\u4E0D\u662F\u62A5\u9519\u2014\u2014\u5148\u95EE\u95EE\u60F3\u8BA9\u4F60\u957F\u4EC0\u4E48\u6837\uFF0C\u62FF\u5230\u63CF\u8FF0\u540E\u7528 portrait \u53C2\u6570\u518D\u8C03\u4E00\u6B21\uFF0C\u90A3\u6B21\u4F1A\u628A\u5F62\u8C61\u56FA\u5B9A\u4E0B\u6765\u3002",
38053
38718
  schema: {
38054
38719
  type: "object",
38055
38720
  properties: {
38056
38721
  prompt: { type: "string", description: "Scene/outfit/mood description, e.g. '\u5728\u5496\u5561\u9986\u770B\u4E66' or '\u7A7F\u7EA2\u88D9\u5B50'." },
38057
38722
  mode: { type: "string", enum: ["auto", "mirror", "direct", "lifestyle"], description: "Selfie mode. 'auto' detects from prompt (default)." },
38058
38723
  reference: { type: "string", description: "Reference base image name (e.g. 'default', 'v3'). Configurable per agent via my_selfie.references in config." },
38059
- provider: { type: "string", enum: ["fal", "grok", "autodl"], description: "Image generation provider. Default from config (my_selfie.provider), fallback 'fal'. 'autodl' uses ComfyUI (txt2img or img2img)." },
38060
- resolution: { type: "string", enum: ["1k", "2k"], description: "Image resolution (fal only). Default: '1k'." }
38724
+ provider: { type: "string", enum: ["fal", "grok", "autodl", "relay"], description: "Image generation provider. Default from config (my_selfie.provider), fallback 'fal'. 'autodl' uses ComfyUI (txt2img or img2img). 'relay' goes through the 7yi relay station (SenseNova U1.5, needs the reference image)." },
38725
+ resolution: { type: "string", enum: ["1k", "2k"], description: "Image resolution (fal only). Default: '1k'." },
38726
+ portrait: { type: "string", description: "\u9996\u6B21\u5851\u9020\u5F62\u8C61\u7528\uFF1A\u8FD9\u4E2A\u52A9\u624B\u957F\u4EC0\u4E48\u6837\u7684\u63CF\u8FF0\uFF08\u6027\u522B/\u5E74\u9F84/\u53D1\u578B/\u6C14\u8D28\uFF09\uFF0C\u4F8B\u5982 '\u77ED\u53D1\u5973\u751F\uFF0C25\u5C81\uFF0C\u7B11\u8D77\u6765\u6709\u9152\u7A9D'\u3002\u53EA\u5728\u8FD8\u6CA1\u6709\u53C2\u8003\u56FE\u65F6\u9700\u8981\uFF0C\u89C1\u4E0B\u65B9\u8BF4\u660E\u3002" },
38727
+ referenceImage: { type: "string", description: "\u76F4\u63A5\u62FF\u4E00\u5F20\u73B0\u6210\u7684\u56FE\u5F53\u5F62\u8C61\uFF08\u6BD4\u5982\u5BF9\u65B9\u53D1\u6765\u7684\u7167\u7247\uFF09\u3002\u4F20\u56FE\u7247\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF1A\u4F1A\u88AB\u590D\u5236\u8FDB workspace \u5E76\u767B\u8BB0\u4E3A\u53C2\u8003\u56FE\uFF0C\u4E4B\u540E\u4E00\u76F4\u7528\u5B83\u3002\u4F20\u4E86\u5C31\u4EE5\u5B83\u4E3A\u51C6\u3002" }
38061
38728
  },
38062
38729
  required: ["prompt"]
38063
38730
  },
@@ -38071,9 +38738,61 @@ registry.register({
38071
38738
  const REFERENCES = getReferences(ctx);
38072
38739
  const refName = args.reference || "default";
38073
38740
  const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
38074
- const refPath = path46.join(ctx.workspace, refEntry.p);
38741
+ let refPath = path46.join(ctx.workspace, refEntry.p);
38742
+ const adoptImg = args.referenceImage?.trim();
38743
+ if (adoptImg) {
38744
+ if (!fs47.existsSync(adoptImg)) {
38745
+ return { content: `\u627E\u4E0D\u5230\u90A3\u5F20\u56FE\uFF1A${adoptImg}`, isError: true };
38746
+ }
38747
+ const acheck = dimsOf(adoptImg);
38748
+ if (acheck?.includes("\u4E0D\u5408\u683C")) {
38749
+ return {
38750
+ content: `\u8FD9\u5F20\u56FE\u7528\u4E0D\u4E86\uFF1A${acheck}\u3002
38751
+ \u6362\u4E00\u5F20\u6B63\u9762\u3001\u5934\u80A9\u3001\u65B9\u5F62\u6216\u63A5\u8FD1\u65B9\u5F62\u7684\u7167\u7247\uFF08\u5546\u6C64\u90A3\u8FB9\u8981\u6C42\u5BBD\u9AD8\u90FD\u5728 256~4096 \u50CF\u7D20\u3001\u957F\u5BBD\u6BD4\u4E0D\u8D85\u8FC7 2:1\uFF09\u3002`,
38752
+ isError: true
38753
+ };
38754
+ }
38755
+ const adir = path46.join(ctx.workspace, "images");
38756
+ if (!fs47.existsSync(adir)) fs47.mkdirSync(adir, { recursive: true });
38757
+ const aext = (path46.extname(adoptImg) || ".png").toLowerCase();
38758
+ const afname = `portrait_${Date.now()}${aext}`;
38759
+ fs47.copyFileSync(adoptImg, path46.join(adir, afname));
38760
+ registerPortrait(`images/${afname}`, refName);
38761
+ refPath = path46.join(adir, afname);
38762
+ console.log(`[my-selfie] \u91C7\u7EB3\u73B0\u6210\u56FE\u5F53\u5F62\u8C61\uFF1Aimages/${afname}${acheck ? ` (${acheck})` : ""}`);
38763
+ }
38075
38764
  if (provider !== "autodl" && !fs47.existsSync(refPath)) {
38076
- return { content: `Error: reference image not found at ${refPath}`, isError: true };
38765
+ const cfgAll = liveConfig.all();
38766
+ const portraitDesc = resolvePortraitDesc(
38767
+ args.portrait,
38768
+ cfgAll?.tools?.my_selfie?.portraitDesc ?? cfgAll?.my_selfie?.portraitDesc
38769
+ );
38770
+ if (!portraitDesc) {
38771
+ return {
38772
+ content: `\u8FD9\u4E2A\u52A9\u624B\u8FD8\u6CA1\u6709\u5F62\u8C61\uFF0C\u53C2\u8003\u56FE\u4E0D\u5B58\u5728\uFF08\u627E\u7684\u662F ${refEntry.p}\uFF09\u3002
38773
+ \u5148\u95EE\u95EE\u60F3\u8BA9\u4F60\u957F\u4EC0\u4E48\u6837\uFF1A\u51E0\u53E5\u8BDD\u63CF\u8FF0\uFF08\u6027\u522B\u3001\u5E74\u9F84\u3001\u53D1\u578B\u3001\u6C14\u8D28\uFF09\uFF0C\u6216\u8005\u76F4\u63A5\u53D1\u4E00\u5F20\u7167\u7247\u8FC7\u6765\u3002
38774
+ \u62FF\u5230\u63CF\u8FF0\u540E\uFF0C\u5E26\u7740\u5B83\u518D\u8C03\u4E00\u6B21 my_selfie\uFF0C\u53C2\u6570\u91CC\u52A0 portrait="<\u90A3\u6BB5\u63CF\u8FF0>"\uFF1B\u90A3\u6B21\u4F1A\u987A\u5E26\u628A\u5F62\u8C61\u56FA\u5B9A\u4E0B\u6765\uFF0C\u4EE5\u540E\u81EA\u62CD\u5C31\u4E0D\u7528\u518D\u7ED9\u4E86\u3002`
38775
+ };
38776
+ }
38777
+ const relay = getRelayCfg(cfgAll);
38778
+ if (!relay.baseUrl || !relay.apiKey) {
38779
+ return { content: "Error: relay provider \u672A\u914D\u7F6E\uFF0C\u65E0\u6CD5\u5851\u9020\u5F62\u8C61\u3002", isError: true };
38780
+ }
38781
+ const pmodel = cfgAll?.my_selfie?.relayModel || cfgAll?.tools?.my_selfie?.relayModel || "engine7-img-free";
38782
+ let portraitBuf;
38783
+ try {
38784
+ console.log(`[my-selfie] \u51B7\u542F\uFF1A\u6309\u63CF\u8FF0\u5851\u9020\u5F62\u8C61 -> ${pmodel}`);
38785
+ portraitBuf = await generatePortraitWithRelay(portraitDesc, relay.baseUrl, relay.apiKey, pmodel);
38786
+ } catch (e) {
38787
+ return { content: `\u5851\u9020\u5F62\u8C61\u5931\u8D25\uFF1A${e.message}`, isError: true };
38788
+ }
38789
+ const pdir = path46.join(ctx.workspace, "images");
38790
+ if (!fs47.existsSync(pdir)) fs47.mkdirSync(pdir, { recursive: true });
38791
+ const pfname = `portrait_${Date.now()}.png`;
38792
+ fs47.writeFileSync(path46.join(pdir, pfname), portraitBuf);
38793
+ registerPortrait(`images/${pfname}`, refName);
38794
+ refPath = path46.join(pdir, pfname);
38795
+ console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u5B58 images/${pfname}\uFF0C\u7EE7\u7EED\u8FD9\u6B21\u81EA\u62CD`);
38077
38796
  }
38078
38797
  const resolution = args.resolution || DEFAULT_RESOLUTION;
38079
38798
  const refB64 = fs47.existsSync(refPath) ? fs47.readFileSync(refPath).toString("base64") : "";
@@ -38085,6 +38804,16 @@ registry.register({
38085
38804
  const xaiKey = getXaiKey(ctx);
38086
38805
  if (!xaiKey) return { content: "Error: xai apiKey not configured for grok provider", isError: true };
38087
38806
  imageBuffer = await generateWithGrok(refB64, fullPrompt, xaiKey);
38807
+ } else if (provider === "relay") {
38808
+ const cfgAll = liveConfig.all();
38809
+ const relay = getRelayCfg(cfgAll);
38810
+ if (!relay.baseUrl || !relay.apiKey) {
38811
+ return { content: `Error: relay provider \u672A\u914D\u7F6E\u3002config \u91CC\u8981\u6709 models.providers.${cfgAll?.my_selfie?.relayProviderId || "7yi"} \u7684 baseUrl \u548C apiKey\u3002`, isError: true };
38812
+ }
38813
+ const model = cfgAll?.my_selfie?.relayModel || cfgAll?.tools?.my_selfie?.relayModel || "engine7-img-free";
38814
+ const ext = path46.extname(refPath).toLowerCase();
38815
+ const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
38816
+ imageBuffer = await generateWithRelay(refB64, fullPrompt, mime, relay.baseUrl, relay.apiKey, model);
38088
38817
  } else {
38089
38818
  imageBuffer = await generateWithFal(refB64, fullPrompt, resolution);
38090
38819
  }
@@ -38471,7 +39200,7 @@ registry.register({
38471
39200
  const ref = String(args?.ref || "");
38472
39201
  if (!stepId || !ref || !["aim", "task", "calendar"].includes(to)) return { content: "Usage: goal_step bind --stepId <s> --to aim|task|calendar --ref <id>", isError: true };
38473
39202
  const kind = to === "aim" ? "aim" : to === "task" ? "remote_task" : "calendar";
38474
- const g = bindGoalStep(ws, goal.id, stepId, kind, to === "calendar" ? Number(ref) : ref);
39203
+ const g = to === "calendar" ? bindGoalCalendar(ws, goal.id, stepId, Number(ref)) : bindGoalStep(ws, goal.id, stepId, kind, ref);
38475
39204
  return { content: `\u6B65\u9AA4 ${stepId} \u5DF2\u59D4\u6D3E ${kind}=${ref}\uFF08\u5B8C\u6210\u4E8B\u4EF6\u81EA\u52A8\u56DE\u6D41\u89E3\u9501\uFF0C\u4E0D\u7528\u76EF\uFF09
38476
39205
  \u5F53\u524D\u6B65\u9AA4: ${g.steps[g.currentStep]?.title ?? "\u2014"}` };
38477
39206
  }
@@ -38504,6 +39233,7 @@ ${lines.join("\n")}` };
38504
39233
  // src/tools/service.ts
38505
39234
  init_registry();
38506
39235
  init_live();
39236
+ init_clock();
38507
39237
  import { exec as exec3, spawn as spawn7 } from "node:child_process";
38508
39238
  import { promisify as promisify3 } from "node:util";
38509
39239
  var execAsync = promisify3(exec3);
@@ -38521,7 +39251,7 @@ async function runCmd(cmd, timeoutMs = 15e3) {
38521
39251
  function spawnDetached(cmd) {
38522
39252
  const isWin = process.platform === "win32";
38523
39253
  const shell = isWin ? "cmd.exe" : "/bin/sh";
38524
- const logFile = `D:/xiaoke/logs/service-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}.log`;
39254
+ const logFile = `D:/xiaoke/logs/service-${todayKey().replace(/-/g, "")}.log`;
38525
39255
  const wrappedCmd = isWin ? `/c "${cmd.replace(/"/g, '\\"')} > "${logFile}" 2>&1"` : `-c "${cmd} > '${logFile}' 2>&1"`;
38526
39256
  const child = spawn7(shell, isWin ? [wrappedCmd] : ["-c", `${cmd} > '${logFile}' 2>&1`], {
38527
39257
  detached: true,
@@ -38584,7 +39314,7 @@ registry.register({
38584
39314
  const startCmd = svcArgs ? `${svc.start} ${svcArgs}` : svc.start;
38585
39315
  if (longRunning) {
38586
39316
  const isWin = process.platform === "win32";
38587
- const logFile = `D:/xiaoke/logs/${name}${svcArgs ? "-" + svcArgs : ""}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}.log`;
39317
+ const logFile = `D:/xiaoke/logs/${name}${svcArgs ? "-" + svcArgs : ""}-${todayKey().replace(/-/g, "")}.log`;
38588
39318
  const redirect = isWin ? ` >> "${logFile}" 2>&1` : ` >> "${logFile}" 2>&1`;
38589
39319
  const pid = spawnDetached(startCmd + redirect);
38590
39320
  lines.push(`start: spawned PID ${pid} (background${svcArgs ? ", args=" + svcArgs : ""})`);