engine7 7.1.105 → 7.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +445 -57
- package/dist/engine-startup.mjs +448 -63
- package/dist/main.mjs +448 -63
- package/package.json +4 -2
- package/templates/workspace/prompts/proactive.md +40 -0
- package/templates/workspace/scripts/emotion_state_machine.mjs +6 -6
package/dist/main.mjs
CHANGED
|
@@ -295,6 +295,10 @@ function formatLog(i) {
|
|
|
295
295
|
function formatWallLabel(i) {
|
|
296
296
|
return formatForLlm(i).replace(/\s*(.*)$/, "");
|
|
297
297
|
}
|
|
298
|
+
function formatShort(i) {
|
|
299
|
+
const p = wallParts(i ?? /* @__PURE__ */ new Date());
|
|
300
|
+
return `${p.mo}/${p.d} ${p2(p.hh)}:${p2(p.mm)}`;
|
|
301
|
+
}
|
|
298
302
|
function warnOnce(callSite, shape, sample) {
|
|
299
303
|
const key = `${callSite}:${shape}`;
|
|
300
304
|
if (warned.has(key)) return;
|
|
@@ -9648,17 +9652,46 @@ var init_filters = __esm({
|
|
|
9648
9652
|
});
|
|
9649
9653
|
|
|
9650
9654
|
// src/mem7/core/vector-store.ts
|
|
9651
|
-
import * as lancedb from "@lancedb/lancedb";
|
|
9652
9655
|
import { Schema, Field, Utf8, Float32, Int64, FixedSizeList, TimestampMicrosecond, List } from "apache-arrow";
|
|
9653
9656
|
import { mkdirSync as mkdirSync8 } from "node:fs";
|
|
9654
9657
|
import { join as join23 } from "node:path";
|
|
9655
|
-
|
|
9658
|
+
function lanceSpecifierFor(platform2, arch) {
|
|
9659
|
+
return platform2 === "darwin" && arch === "x64" ? "@engine7/lancedb-intel" : "@lancedb/lancedb";
|
|
9660
|
+
}
|
|
9661
|
+
function loadLance() {
|
|
9662
|
+
if (!lancePromise) {
|
|
9663
|
+
const spec = lanceSpecifierFor(process.platform, process.arch);
|
|
9664
|
+
lancePromise = import(spec).then(
|
|
9665
|
+
// 转发包是 CJS(module.exports = require(...)),命名导出可能落在 default 上
|
|
9666
|
+
(m) => m?.connect ? m : m?.default,
|
|
9667
|
+
(err) => {
|
|
9668
|
+
mem7VectorWarning = err?.message ? String(err.message) : String(err);
|
|
9669
|
+
console.warn(
|
|
9670
|
+
`[mem7] \u5411\u91CF\u7D22\u5F15\u4E0D\u53EF\u7528\uFF1A\u52A0\u8F7D ${spec} \u5931\u8D25 \u2014\u2014 ${mem7VectorWarning}
|
|
9671
|
+
mem7 \u964D\u7EA7\u505C\u7528\uFF0C\u5F15\u64CE\u5176\u4F59\u529F\u80FD\u4E0D\u53D7\u5F71\u54CD\u3002` + (isIntelMac ? "\n Intel Mac \u63D0\u793A\uFF1Ae7 \u4F9D\u8D56 @engine7/lancedb-intel\uFF08lancedb \u4E0D\u518D\u53D1\u5E03 darwin-x64 \u4E8C\u8FDB\u5236\uFF09\u3002\u82E5\u62A5\u300CCannot find native binding\u300D\uFF0C\u5220\u6389 node_modules \u91CD\u88C5\u4E00\u6B21\u5373\u53EF\u3002" : "")
|
|
9672
|
+
);
|
|
9673
|
+
return null;
|
|
9674
|
+
}
|
|
9675
|
+
);
|
|
9676
|
+
}
|
|
9677
|
+
return lancePromise;
|
|
9678
|
+
}
|
|
9679
|
+
var isIntelMac, lancePromise, mem7VectorWarning, Mem7VectorUnavailableError, VECTOR_DIM, BM25_FIELDS, RRF_K, warnedFts, vecField, commonFields, SCHEMAS, Mem7VectorStore;
|
|
9656
9680
|
var init_vector_store = __esm({
|
|
9657
9681
|
"src/mem7/core/vector-store.ts"() {
|
|
9658
9682
|
"use strict";
|
|
9659
9683
|
init_store2();
|
|
9660
9684
|
init_tokenizer();
|
|
9661
9685
|
init_filters();
|
|
9686
|
+
isIntelMac = process.platform === "darwin" && process.arch === "x64";
|
|
9687
|
+
lancePromise = null;
|
|
9688
|
+
mem7VectorWarning = null;
|
|
9689
|
+
Mem7VectorUnavailableError = class extends Error {
|
|
9690
|
+
constructor() {
|
|
9691
|
+
super(`mem7 \u5411\u91CF\u7D22\u5F15\u4E0D\u53EF\u7528\uFF08lancedb \u672A\u80FD\u52A0\u8F7D\uFF09${mem7VectorWarning ? `\uFF1A${mem7VectorWarning}` : ""}`);
|
|
9692
|
+
this.name = "Mem7VectorUnavailableError";
|
|
9693
|
+
}
|
|
9694
|
+
};
|
|
9662
9695
|
VECTOR_DIM = 1024;
|
|
9663
9696
|
BM25_FIELDS = {
|
|
9664
9697
|
foresight: ["foresight_tokens", "evidence_tokens"],
|
|
@@ -9721,8 +9754,10 @@ var init_vector_store = __esm({
|
|
|
9721
9754
|
}
|
|
9722
9755
|
async conn() {
|
|
9723
9756
|
if (!this.db) {
|
|
9757
|
+
const lance = await loadLance();
|
|
9758
|
+
if (!lance) throw new Mem7VectorUnavailableError();
|
|
9724
9759
|
mkdirSync8(this.dir, { recursive: true });
|
|
9725
|
-
this.db = await
|
|
9760
|
+
this.db = await lance.connect(this.dir);
|
|
9726
9761
|
}
|
|
9727
9762
|
return this.db;
|
|
9728
9763
|
}
|
|
@@ -9744,10 +9779,12 @@ var init_vector_store = __esm({
|
|
|
9744
9779
|
* 幂等:已建则跳过(索引缺失才建)。
|
|
9745
9780
|
*/
|
|
9746
9781
|
async ensureFtsIndexes(kind, tbl) {
|
|
9782
|
+
const lance = await loadLance();
|
|
9783
|
+
if (!lance) return;
|
|
9747
9784
|
for (const col of BM25_FIELDS[kind]) {
|
|
9748
9785
|
try {
|
|
9749
9786
|
await tbl.createIndex(col, {
|
|
9750
|
-
config:
|
|
9787
|
+
config: lance.Index.fts({
|
|
9751
9788
|
baseTokenizer: "whitespace",
|
|
9752
9789
|
removeStopWords: false,
|
|
9753
9790
|
lowercase: true,
|
|
@@ -10220,7 +10257,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10220
10257
|
blocks.push(block);
|
|
10221
10258
|
count++;
|
|
10222
10259
|
}
|
|
10223
|
-
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${(
|
|
10260
|
+
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${formatMdTimestamp()}'`);
|
|
10224
10261
|
writeFileSync7(file2, head + "\n\n" + blocks.join("\n\n") + "\n", "utf8");
|
|
10225
10262
|
}
|
|
10226
10263
|
/**
|
|
@@ -10640,7 +10677,7 @@ function calcDurationDays(startDate, endDate) {
|
|
|
10640
10677
|
const ms = Date.parse(endDate + "T00:00:00Z") - Date.parse(startDate + "T00:00:00Z");
|
|
10641
10678
|
return Number.isNaN(ms) ? null : Math.round(ms / 864e5);
|
|
10642
10679
|
}
|
|
10643
|
-
function
|
|
10680
|
+
function cellDateKey(cell) {
|
|
10644
10681
|
return todayKey(cell.startTs);
|
|
10645
10682
|
}
|
|
10646
10683
|
async function extractForesights(cell, ownerId, provider, model) {
|
|
@@ -10669,7 +10706,7 @@ async function extractForesights(cell, ownerId, provider, model) {
|
|
|
10669
10706
|
}
|
|
10670
10707
|
}
|
|
10671
10708
|
if (!raw) return [];
|
|
10672
|
-
const startFallback =
|
|
10709
|
+
const startFallback = cellDateKey(cell);
|
|
10673
10710
|
const out = [];
|
|
10674
10711
|
for (const r of raw) {
|
|
10675
10712
|
if (!r || typeof r !== "object") continue;
|
|
@@ -10710,7 +10747,7 @@ var init_foresight = __esm({
|
|
|
10710
10747
|
});
|
|
10711
10748
|
|
|
10712
10749
|
// src/mem7/core/pipeline.ts
|
|
10713
|
-
function
|
|
10750
|
+
function dateKey(tsMs) {
|
|
10714
10751
|
return todayKey(tsMs);
|
|
10715
10752
|
}
|
|
10716
10753
|
async function runMem7Pipeline(args2) {
|
|
@@ -10745,7 +10782,7 @@ async function runMem7Pipeline(args2) {
|
|
|
10745
10782
|
store.saveTail(sessionId, tail);
|
|
10746
10783
|
for (const cell of cells) {
|
|
10747
10784
|
const userSenders = [...new Set(cell.messages.filter((m) => m.role === "user").map((m) => m.senderId))];
|
|
10748
|
-
const dateStr =
|
|
10785
|
+
const dateStr = dateKey(cell.startTs);
|
|
10749
10786
|
const cellTimeIso = new Date(cell.startTs).toISOString();
|
|
10750
10787
|
let episodeOut = null;
|
|
10751
10788
|
let factsOut = [];
|
|
@@ -10918,11 +10955,18 @@ function parseProactiveConfig(raw) {
|
|
|
10918
10955
|
console.warn(`[proactive] model "${modelRef}" \u975E\u6CD5\uFF08${err.message}\uFF09\u2014\u2014 \u6539\u7528\u4E3B\u6A21\u578B`);
|
|
10919
10956
|
}
|
|
10920
10957
|
}
|
|
10958
|
+
const num = (v, dflt) => {
|
|
10959
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
10960
|
+
return Number.isFinite(n) && n >= 0 ? n : dflt;
|
|
10961
|
+
};
|
|
10921
10962
|
return {
|
|
10922
10963
|
enabled: obj.enabled === true || obj.enabled === "yes" || obj.enabled === 1,
|
|
10923
10964
|
modelRef: parsed ? `${parsed.providerId}/${parsed.modelId}` : "",
|
|
10924
10965
|
providerId: parsed?.providerId ?? "",
|
|
10925
|
-
modelId: parsed?.modelId ?? ""
|
|
10966
|
+
modelId: parsed?.modelId ?? "",
|
|
10967
|
+
// 默认 3 天 = 翀哥的"以周为单位";staleHours 默认 36 = 与旧行为一致
|
|
10968
|
+
leadGateDays: num(obj.leadGateDays, 3),
|
|
10969
|
+
staleHours: num(obj.staleHours, 36)
|
|
10926
10970
|
};
|
|
10927
10971
|
}
|
|
10928
10972
|
function proactiveDbPath(workspace) {
|
|
@@ -11286,6 +11330,8 @@ function rowToAction(r) {
|
|
|
11286
11330
|
createdAt: r.created_at,
|
|
11287
11331
|
completedAt: r.completed_at ?? void 0,
|
|
11288
11332
|
cancelReason: r.cancel_reason ?? void 0,
|
|
11333
|
+
injectedAt: r.injected_at ?? void 0,
|
|
11334
|
+
note: r.note ?? void 0,
|
|
11289
11335
|
provenance: r.provenance ? JSON.parse(r.provenance) : void 0
|
|
11290
11336
|
};
|
|
11291
11337
|
}
|
|
@@ -11335,6 +11381,8 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
11335
11381
|
created_at TEXT NOT NULL,
|
|
11336
11382
|
completed_at TEXT,
|
|
11337
11383
|
cancel_reason TEXT,
|
|
11384
|
+
injected_at TEXT,
|
|
11385
|
+
note TEXT,
|
|
11338
11386
|
provenance TEXT
|
|
11339
11387
|
);
|
|
11340
11388
|
CREATE INDEX IF NOT EXISTS idx_actions_due ON actions(status, scheduled_time);
|
|
@@ -11374,6 +11422,8 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11374
11422
|
this.db.exec(SCHEMA2);
|
|
11375
11423
|
const actionCols = new Set(this.db.prepare("PRAGMA table_info(actions)").all().map((r) => r.name));
|
|
11376
11424
|
if (!actionCols.has("provenance")) this.db.exec("ALTER TABLE actions ADD COLUMN provenance TEXT");
|
|
11425
|
+
if (!actionCols.has("injected_at")) this.db.exec("ALTER TABLE actions ADD COLUMN injected_at TEXT");
|
|
11426
|
+
if (!actionCols.has("note")) this.db.exec("ALTER TABLE actions ADD COLUMN note TEXT");
|
|
11377
11427
|
const decisionCols = new Set(this.db.prepare("PRAGMA table_info(proactive_decisions)").all().map((r) => r.name));
|
|
11378
11428
|
if (!decisionCols.has("coverage_matched")) this.db.exec("ALTER TABLE proactive_decisions ADD COLUMN coverage_matched INTEGER NOT NULL DEFAULT 0");
|
|
11379
11429
|
this.migrateNakedLocalTimes();
|
|
@@ -11637,16 +11687,51 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11637
11687
|
).run(reason, nowIso2, ...intentIds);
|
|
11638
11688
|
return Number(res.changes);
|
|
11639
11689
|
}
|
|
11690
|
+
/**
|
|
11691
|
+
* 改状态。**只更新显式传入的列**——此前是「任一字段有值就把两列都写」,
|
|
11692
|
+
* 未传的那列被写成 NULL:sweep 的 R2 复检只传 cancelReason,会把已存在的
|
|
11693
|
+
* completed_at 清空(0925 修)。
|
|
11694
|
+
*/
|
|
11640
11695
|
updateActionStatus(id, status, extra) {
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11696
|
+
const sets = ["status=?"];
|
|
11697
|
+
const vals = [status];
|
|
11698
|
+
if (extra?.completedAt !== void 0) {
|
|
11699
|
+
sets.push("completed_at=?");
|
|
11700
|
+
vals.push(extra.completedAt);
|
|
11701
|
+
}
|
|
11702
|
+
if (extra?.cancelReason !== void 0) {
|
|
11703
|
+
sets.push("cancel_reason=?");
|
|
11704
|
+
vals.push(extra.cancelReason);
|
|
11645
11705
|
}
|
|
11706
|
+
if (extra?.note !== void 0) {
|
|
11707
|
+
sets.push("note=?");
|
|
11708
|
+
vals.push(extra.note);
|
|
11709
|
+
}
|
|
11710
|
+
vals.push(id);
|
|
11711
|
+
this.db.prepare(`UPDATE actions SET ${sets.join(", ")} WHERE id=?`).run(...vals);
|
|
11646
11712
|
}
|
|
11647
|
-
/** sweep 注入前标记(防重复注入;复刻 due
|
|
11648
|
-
|
|
11649
|
-
|
|
11713
|
+
/** sweep 注入前标记(防重复注入;复刻 due→推送的原子语义)。
|
|
11714
|
+
* 0925:同时记 injected_at —— 收割机判"超时"必须拿**注入时刻**,不能拿排期/创建时刻。 */
|
|
11715
|
+
markActionReminded(id, injectedAt) {
|
|
11716
|
+
this.db.prepare(`UPDATE actions SET status='executing', injected_at=? WHERE id=? AND status='queued'`).run(injectedAt, id);
|
|
11717
|
+
}
|
|
11718
|
+
/**
|
|
11719
|
+
* 注入失败回滚(0925):submitMessage 被 dispatcher guard 拒了、或主 session 不在——
|
|
11720
|
+
* 此时 action 已被标 executing 却从没进过 agent 的视野,就是一条"谁也没见过"的僵尸。
|
|
11721
|
+
* 退回去重排,下一轮 sweep 还能再试。返回回滚条数。
|
|
11722
|
+
*/
|
|
11723
|
+
rollbackReminded(ids) {
|
|
11724
|
+
if (ids.length === 0) return 0;
|
|
11725
|
+
const ph = ids.map(() => "?").join(",");
|
|
11726
|
+
const res = this.db.prepare(
|
|
11727
|
+
`UPDATE actions SET status='queued', injected_at=NULL WHERE status='executing' AND id IN (${ph})`
|
|
11728
|
+
).run(...ids);
|
|
11729
|
+
return Number(res.changes);
|
|
11730
|
+
}
|
|
11731
|
+
/** 某状态的条数(对账告警用:executing 只增不减 = 闭环断了) */
|
|
11732
|
+
countByStatus(status) {
|
|
11733
|
+
const r = this.db.prepare("SELECT COUNT(*) AS c FROM actions WHERE status=?").get(status);
|
|
11734
|
+
return Number(r?.c ?? 0);
|
|
11650
11735
|
}
|
|
11651
11736
|
/**
|
|
11652
11737
|
* 0915:executing 超时收割。sweep 注入时标 executing,agent 处理后标终态;
|
|
@@ -11655,10 +11740,12 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11655
11740
|
* scheduled_time 或 created_at 距今超 staleHours 的 executing → cancelled。返回收割条数。
|
|
11656
11741
|
*/
|
|
11657
11742
|
reapStaleExecuting(nowIso2, staleHours = 36) {
|
|
11743
|
+
if (!(staleHours > 0)) return 0;
|
|
11658
11744
|
const cutoff = new Date(Date.parse(nowIso2) - staleHours * 36e5).toISOString();
|
|
11659
11745
|
const res = this.db.prepare(
|
|
11660
|
-
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
11661
|
-
|
|
11746
|
+
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
11747
|
+
WHERE status='executing' AND COALESCE(injected_at, created_at) < ?`
|
|
11748
|
+
).run(nowIso2, `\u672A\u6536\u5230\u56DE\u6267\uFF08\u6CE8\u5165\u540E ${staleHours}h \u65E0\u95ED\u73AF\u4FE1\u53F7\uFF1B\u539F\u56E0\u672A\u77E5\uFF09`, cutoff);
|
|
11662
11749
|
return Number(res.changes);
|
|
11663
11750
|
}
|
|
11664
11751
|
/**
|
|
@@ -12228,12 +12315,17 @@ ${D.intent_node_example}
|
|
|
12228
12315
|
\`\`\`
|
|
12229
12316
|
|
|
12230
12317
|
**Intent fields:**
|
|
12231
|
-
- \`suggested_time\`: When to surface/execute this intent
|
|
12232
|
-
- \`expiry\`: When this intent is no longer relevant
|
|
12318
|
+
- \`suggested_time\`: When to surface/execute this intent\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
12319
|
+
- \`expiry\`: When this intent is no longer relevant\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
12233
12320
|
- \`priority\`: "low", "medium", "high", or "urgent"
|
|
12234
12321
|
- \`status\`: "pending", "action_scheduled", or "resolved"
|
|
12235
12322
|
- \`related_time_node\`: Link to a TIME node for urgency tracking
|
|
12236
|
-
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
12323
|
+
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
12324
|
+
|
|
12325
|
+
**Time field format (applies to every *_time field below):**
|
|
12326
|
+
- Write **local wall time with NO timezone suffix** \u2014 \`"2026-09-21T09:00:00"\` means 9am in ${tz()}. This is the only accepted form.
|
|
12327
|
+
- **Never append \`Z\` or \`+08:00\`.** A suffixed value is read as an absolute instant, so \`"2026-09-21T09:00:00Z"\` lands at 5pm ${tz()}, not 9am \u2014 and it is logged as a contract violation.
|
|
12328
|
+
- Anchor on the \`## Current Time\` line when there is one, and convert relative phrasing ("\u660E\u65E99\u70B9") into that local clock.`;
|
|
12237
12329
|
const SECTION_INTENTS_PATTERNS = `
|
|
12238
12330
|
## Pattern-Based Intents
|
|
12239
12331
|
|
|
@@ -12620,7 +12712,7 @@ ${contextText}
|
|
|
12620
12712
|
|
|
12621
12713
|
## Current Time
|
|
12622
12714
|
|
|
12623
|
-
${args2.now
|
|
12715
|
+
${formatForLlm(args2.now)} \u2190 use THIS as "now" (local wall time, NOT UTC)
|
|
12624
12716
|
|
|
12625
12717
|
## Existing Commitments (calendar / current work state)
|
|
12626
12718
|
|
|
@@ -12727,6 +12819,7 @@ var DEFAULT_DOMAIN, registryCache, DEFAULT_SECTION_ORDER, MODE_PROMPTS, OPPORTUN
|
|
|
12727
12819
|
var init_prompts = __esm({
|
|
12728
12820
|
"src/memory/proactive/prompts.ts"() {
|
|
12729
12821
|
"use strict";
|
|
12822
|
+
init_clock();
|
|
12730
12823
|
DEFAULT_DOMAIN = {
|
|
12731
12824
|
domain_description: "\u4F60\u4E0E TA \u5171\u540C\u7684\u751F\u6D3B\u2014\u2014TA \u7684\u5DE5\u4F5C\u4E0E\u9879\u76EE\u3001\u5BB6\u4EBA\u670B\u53CB\u7684\u5F80\u6765\u3001\u5065\u5EB7\u3001\u5B66\u4E60\u6210\u957F\u548C\u65E5\u5E38\u4E8B\u52A1",
|
|
12732
12825
|
event_type_desc: "\u5BF9\u8BDD\u4E2D\u53D1\u751F\u7684\u4E00\u4EF6\u4E8B\u2014\u2014TA \u8BF4\u7684\u8BDD\u3001\u4E0E\u4EBA\u7684\u4E92\u52A8\u3001\u63D0\u5230\u7684\u884C\u7A0B\u6216\u8BA1\u5212",
|
|
@@ -12748,12 +12841,12 @@ var init_prompts = __esm({
|
|
|
12748
12841
|
time_node_example: JSON.stringify({
|
|
12749
12842
|
op: "ADD_NODE",
|
|
12750
12843
|
node_type: "time",
|
|
12751
|
-
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:
|
|
12844
|
+
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:00", recurrence: null, urgency_window_hours: 24 }
|
|
12752
12845
|
}, null, 2),
|
|
12753
12846
|
intent_node_example: JSON.stringify({
|
|
12754
12847
|
op: "ADD_NODE",
|
|
12755
12848
|
node_type: "intent",
|
|
12756
|
-
data: { intent_id: "i-example", title: "\u63D0\u9192 TA \u786E\u8BA4\u4F5C\u54C1\u63D0\u4EA4\u72B6\u6001", description: "\u63D0\u4EA4 9/21 \u622A\u6B62\uFF0C\u63D0\u524D\u786E\u8BA4\u72B6\u6001", status: "pending", priority: "high", suggested_time: "2026-09-18T09:00:
|
|
12849
|
+
data: { intent_id: "i-example", title: "\u63D0\u9192 TA \u786E\u8BA4\u4F5C\u54C1\u63D0\u4EA4\u72B6\u6001", description: "\u63D0\u4EA4 9/21 \u622A\u6B62\uFF0C\u63D0\u524D\u786E\u8BA4\u72B6\u6001", status: "pending", priority: "high", suggested_time: "2026-09-18T09:00:00", expiry: "2026-09-21T23:59:00" }
|
|
12757
12850
|
}, null, 2),
|
|
12758
12851
|
pattern_intent_examples: [
|
|
12759
12852
|
"**\u6A21\u5F0F**\uFF1ATA \u6BCF\u5468\u4E09\u665A\u6709\u56FA\u5B9A\u5B89\u6392\uFF08\u6982\u5FF5 c-ta-schedule\uFF0Cstrength 0.7\uFF09",
|
|
@@ -12886,7 +12979,7 @@ Return valid JSON with this structure:
|
|
|
12886
12979
|
{
|
|
12887
12980
|
"title": "Short action title (max 50 chars)",
|
|
12888
12981
|
"description": "Detailed description of what to do",
|
|
12889
|
-
"scheduled_time": "
|
|
12982
|
+
"scheduled_time": "Local wall time, NO timezone suffix - '2026-09-21T09:00:00' means 9am ${tz()}",
|
|
12890
12983
|
"estimated_duration_minutes": 30,
|
|
12891
12984
|
"urgency": "low|medium|high|urgent"
|
|
12892
12985
|
}
|
|
@@ -13630,7 +13723,7 @@ var init_commands = __esm({
|
|
|
13630
13723
|
});
|
|
13631
13724
|
|
|
13632
13725
|
// src/memory/proactive/calendar-bridge.ts
|
|
13633
|
-
function
|
|
13726
|
+
function toCalendarWallParts(iso) {
|
|
13634
13727
|
const b = wallParts(iso);
|
|
13635
13728
|
const dateStr = `${b.mo}/${b.d}`;
|
|
13636
13729
|
const timeExact = `${String(b.hh).padStart(2, "0")}:${String(b.mm).padStart(2, "0")}`;
|
|
@@ -13643,13 +13736,19 @@ function parseCalendarDateMs(dateStr, now) {
|
|
|
13643
13736
|
const day = parseInt(parts[1], 10);
|
|
13644
13737
|
if (!Number.isFinite(month) || !Number.isFinite(day)) return null;
|
|
13645
13738
|
let year = wallParts(now).y;
|
|
13646
|
-
let candidate =
|
|
13739
|
+
let candidate = wallMidnightMs(year, month, day);
|
|
13740
|
+
if (candidate === null) return null;
|
|
13647
13741
|
if (candidate < now.getTime() - 7 * 864e5) {
|
|
13648
13742
|
year += 1;
|
|
13649
|
-
candidate =
|
|
13743
|
+
candidate = wallMidnightMs(year, month, day);
|
|
13650
13744
|
}
|
|
13651
13745
|
return candidate;
|
|
13652
13746
|
}
|
|
13747
|
+
function wallMidnightMs(year, month, day) {
|
|
13748
|
+
const p22 = (n) => String(n).padStart(2, "0");
|
|
13749
|
+
const ms = Date.parse(parseWall(`${year}-${p22(month)}-${p22(day)}T00:00:00`));
|
|
13750
|
+
return Number.isNaN(ms) ? null : ms;
|
|
13751
|
+
}
|
|
13653
13752
|
function injectCalendarEvents(store, calDb, days = 30, now = /* @__PURE__ */ new Date()) {
|
|
13654
13753
|
const cutoffMs = now.getTime() + days * 864e5;
|
|
13655
13754
|
const rows = listEventsForInjection(calDb, 50);
|
|
@@ -13856,6 +13955,16 @@ function enqueueOpportunity(persist, opp, anchor, planningContext, previousTitle
|
|
|
13856
13955
|
console.log(`[proactive] opportunity enqueued (${anchor.label}): ${opp.title}`);
|
|
13857
13956
|
return true;
|
|
13858
13957
|
}
|
|
13958
|
+
function isProfilePrediction(text) {
|
|
13959
|
+
return PROFILE_RE.test(text);
|
|
13960
|
+
}
|
|
13961
|
+
function capForesightEntries(entries2, today2, openCap = 12, upcomingCap = 8) {
|
|
13962
|
+
const isOpen = (e) => e.start <= today2;
|
|
13963
|
+
const sorted = entries2.slice().sort((a, b) => Number(isOpen(b)) - Number(isOpen(a)) || (isOpen(a) ? a.end.localeCompare(b.end) : a.start.localeCompare(b.start)));
|
|
13964
|
+
const open = sorted.filter(isOpen).slice(0, openCap);
|
|
13965
|
+
const upcoming = sorted.filter((e) => !isOpen(e)).slice(0, upcomingCap);
|
|
13966
|
+
return [...open, ...upcoming];
|
|
13967
|
+
}
|
|
13859
13968
|
function foresightMatchKey(text) {
|
|
13860
13969
|
return text.replace(/\n+/g, " ").slice(0, FORESIGHT_TEXT_MAX).replace(/\s+/g, "");
|
|
13861
13970
|
}
|
|
@@ -13905,7 +14014,16 @@ function parseForesightEntries(everosRoot, now, userId) {
|
|
|
13905
14014
|
const key = text.replace(/\s+/g, "");
|
|
13906
14015
|
if (seen.has(key)) continue;
|
|
13907
14016
|
seen.add(key);
|
|
13908
|
-
|
|
14017
|
+
const textFlat = text.replace(/\n+/g, " ");
|
|
14018
|
+
out.push({
|
|
14019
|
+
start,
|
|
14020
|
+
end,
|
|
14021
|
+
text: textFlat.slice(0, FORESIGHT_TEXT_MAX),
|
|
14022
|
+
evidence: evidence.replace(/\n+/g, " ").slice(0, 80),
|
|
14023
|
+
ownerId: blockOwner,
|
|
14024
|
+
// 画像型在这里就标出来,扫描路径据此剔除(见 isProfilePrediction 的注释)
|
|
14025
|
+
kind: isProfilePrediction(textFlat) ? "profile" : "event"
|
|
14026
|
+
});
|
|
13909
14027
|
}
|
|
13910
14028
|
}
|
|
13911
14029
|
return out;
|
|
@@ -13959,7 +14077,10 @@ async function runOpportunityScan(deps) {
|
|
|
13959
14077
|
return ms !== null && ms >= now.getTime() - 864e5 && ms <= now.getTime() + 31 * 864e5;
|
|
13960
14078
|
}).sort((a, b) => (parseCalendarDateMs(a.date, now) ?? 0) - (parseCalendarDateMs(b.date, now) ?? 0)).slice(0, 30).map((c) => `- [calendar #${c.id}] ${c.date} ${c.time} ${c.title}`);
|
|
13961
14079
|
const userPrompt = [
|
|
13962
|
-
|
|
14080
|
+
// 这一行必须与上面的 calendar 行同坐标:calendar 的 date/time 是**配置时区**墙上时间,
|
|
14081
|
+
// 而这里此前给的是 UTC Z —— 同一个 prompt 两套坐标、且都没标注,模型无从判断"现在"。
|
|
14082
|
+
`\u5F53\u524D\u65F6\u95F4\uFF1A${formatForLlm(now)}`,
|
|
14083
|
+
`\uFF08\u4E0B\u9762 calendar \u884C\u7684 date/time \u4E5F\u662F ${tz()} \u672C\u5730\u65F6\u523B\uFF0C\u4E0E\u4E4B\u540C\u4E00\u5750\u6807\uFF09`,
|
|
13963
14084
|
"",
|
|
13964
14085
|
"## \u6302\u7740\u7684 intent\uFF08\u672A\u5B8C\u7ED3\uFF09",
|
|
13965
14086
|
activeIntents.length > 0 ? activeIntents.join("\n") : "\uFF08\u65E0\uFF09",
|
|
@@ -13967,7 +14088,8 @@ async function runOpportunityScan(deps) {
|
|
|
13967
14088
|
"## \u672A\u6765\u65E5\u7A0B\uFF08\u672A\u6765 30 \u5929 calendar\uFF09",
|
|
13968
14089
|
calendarLines.length > 0 ? calendarLines.join("\n") : "\uFF08\u65E0\uFF09",
|
|
13969
14090
|
"",
|
|
13970
|
-
|
|
14091
|
+
// 0919 删轨 B 后源头已是 mem7(945be93c 漏改的措辞)
|
|
14092
|
+
"## \u9884\u6D4B\uFF08mem7 foresight\u2014\u2014\u6B64\u524D\u5BF9\u8BDD\u4E2D LLM \u9884\u6D4B\u7684\u672A\u6765\u4E8B\u4EF6\uFF0C\u5E26\u65F6\u95F4\u7A97\u53E3\uFF1B\u662F\u9884\u6D4B\u4E0D\u662F\u4E8B\u5B9E\uFF09",
|
|
13971
14093
|
(deps.foresightLines ?? []).length > 0 ? (deps.foresightLines ?? []).join("\n") : "\uFF08\u65E0\uFF09",
|
|
13972
14094
|
"",
|
|
13973
14095
|
"## \u8FD1\u671F\u4E8B\u4EF6\uFF08\u6700\u65B0 15 \u6761\uFF09",
|
|
@@ -14047,7 +14169,7 @@ async function runOpportunityScan(deps) {
|
|
|
14047
14169
|
}
|
|
14048
14170
|
return enqueued;
|
|
14049
14171
|
}
|
|
14050
|
-
var OPPORTUNITY_SCAN_INTERVAL_MS, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
14172
|
+
var OPPORTUNITY_SCAN_INTERVAL_MS, PROFILE_RE, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
14051
14173
|
var init_opportunity = __esm({
|
|
14052
14174
|
"src/memory/proactive/opportunity.ts"() {
|
|
14053
14175
|
"use strict";
|
|
@@ -14057,6 +14179,7 @@ var init_opportunity = __esm({
|
|
|
14057
14179
|
init_recall_score();
|
|
14058
14180
|
init_clock();
|
|
14059
14181
|
OPPORTUNITY_SCAN_INTERVAL_MS = 6 * 60 * 6e4;
|
|
14182
|
+
PROFILE_RE = /今后|以后|将来|往后|再次遇到|下次遇到|会一直|会习惯|会继续|会优先|会避免|会主动|会定期|会倾向于|会按|会先|时会|倾向于|更愿意|养成了?.*习惯|将养成|将优先|将避免|习惯性地|will always|will prefer|will avoid|will continue|will keep|will strictly|tends? to|is likely to (?:always|keep|continue)/i;
|
|
14060
14183
|
foresightCache = /* @__PURE__ */ new Map();
|
|
14061
14184
|
FORESIGHT_CACHE_TTL_MS = 30 * 6e4;
|
|
14062
14185
|
FORESIGHT_TEXT_MAX = 150;
|
|
@@ -15065,48 +15188,91 @@ var init_intent = __esm({
|
|
|
15065
15188
|
|
|
15066
15189
|
// src/memory/proactive/sweep.ts
|
|
15067
15190
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
15191
|
+
function sharesTerm(a, b) {
|
|
15192
|
+
const x = normalizeTitle(a);
|
|
15193
|
+
const y = normalizeTitle(b);
|
|
15194
|
+
if (x.length < 2 || y.length < 2) return false;
|
|
15195
|
+
const grams = new Set(Array.from({ length: x.length - 1 }, (_, i) => x.slice(i, i + 2)));
|
|
15196
|
+
for (let i = 0; i < y.length - 1; i++) if (grams.has(y.slice(i, i + 2))) return true;
|
|
15197
|
+
return false;
|
|
15198
|
+
}
|
|
15199
|
+
function isSameCommitment(a, b) {
|
|
15200
|
+
const x = normalizeTitle(a);
|
|
15201
|
+
const y = normalizeTitle(b);
|
|
15202
|
+
if (!x || !y) return false;
|
|
15203
|
+
if (x === y) return true;
|
|
15204
|
+
if (x.length >= 4 && y.includes(x)) return true;
|
|
15205
|
+
if (y.length >= 4 && x.includes(y)) return true;
|
|
15206
|
+
return false;
|
|
15207
|
+
}
|
|
15068
15208
|
function actionPayload(a, now, recentEvidence) {
|
|
15069
15209
|
let suggestedTask = null;
|
|
15210
|
+
let scheduledLocal = null;
|
|
15070
15211
|
try {
|
|
15071
|
-
const { dateStr, timeExact } =
|
|
15212
|
+
const { dateStr, timeExact } = toCalendarWallParts(a.scheduledTime);
|
|
15072
15213
|
suggestedTask = { date: dateStr, time: timeExact };
|
|
15214
|
+
scheduledLocal = formatForLlm(a.scheduledTime);
|
|
15215
|
+
} catch {
|
|
15216
|
+
}
|
|
15217
|
+
let generatedLocal = null;
|
|
15218
|
+
try {
|
|
15219
|
+
generatedLocal = a.createdAt ? formatForLlm(a.createdAt) : null;
|
|
15073
15220
|
} catch {
|
|
15074
15221
|
}
|
|
15075
15222
|
const ageHours = (now.getTime() - Date.parse(a.scheduledTime)) / 36e5;
|
|
15076
|
-
const staleHint = ageHours >
|
|
15223
|
+
const staleHint = ageHours > STALE_HINT_HOURS ? `\u8FD9\u6761\u5DF2\u8FC7\u6392\u671F ${Math.round(ageHours)} \u5C0F\u65F6\u2014\u2014\u6B63\u6587\u91CC\u7684"\u73B0\u5728/\u4ECA\u5929"\u6307\u7684\u662F**\u751F\u6210\u65F6\u523B**\uFF0C\u4E0D\u662F\u6B64\u523B\uFF0C\u5148\u6838\u5BF9\u518D\u5224` : void 0;
|
|
15077
15224
|
return {
|
|
15078
15225
|
action_id: a.id,
|
|
15079
15226
|
intent_id: a.intentId,
|
|
15080
15227
|
title: a.title,
|
|
15081
15228
|
description: a.description,
|
|
15082
15229
|
scheduled_time: a.scheduledTime,
|
|
15230
|
+
// UTC 绝对时刻(机器坐标)
|
|
15231
|
+
scheduled_time_local: scheduledLocal,
|
|
15232
|
+
// 本地可读 —— 与上一行是同一个时刻,别当两件事
|
|
15233
|
+
suggested_task: suggestedTask,
|
|
15234
|
+
// { date, time } 本地墙上时间,calendar add-task 参数
|
|
15083
15235
|
urgency: a.urgency,
|
|
15084
15236
|
duration_minutes: a.durationMinutes,
|
|
15085
15237
|
calendar_id: a.calendarId,
|
|
15086
15238
|
status: a.status,
|
|
15087
|
-
|
|
15239
|
+
generated_at_local: generatedLocal,
|
|
15240
|
+
// 这条 action 是**几点生成的**(正文里的「现在」指这个点)
|
|
15088
15241
|
stale_hint: staleHint,
|
|
15089
|
-
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"
|
|
15090
|
-
|
|
15242
|
+
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"的马后炮当场现形。
|
|
15243
|
+
// 时间戳走 formatShort(本地);此前是 createdAt.slice(5,16) —— 那是 **UTC**,
|
|
15244
|
+
// 混在 suggested_task(本地)旁边且无标注。
|
|
15245
|
+
recent_evidence: recentEvidence && recentEvidence.length > 0 ? recentEvidence.map((e) => `[${formatShort(e.createdAt)}] ${e.title}${e.description ? `\uFF1A${e.description}` : ""}`) : void 0,
|
|
15091
15246
|
// 机械可判的过期事实标记(不靠 LLM):action 创建之后 graph 又来了新证据 →
|
|
15092
15247
|
// action 基于过期事实生成,先核对新证据再决定(生成侧 Time-Evidence Gate 的兜底)
|
|
15093
15248
|
stale_facts: recentEvidence && recentEvidence.length > 0 && recentEvidence.some((e) => Date.parse(e.createdAt) > Date.parse(a.createdAt)) ? "\u6B64 action \u751F\u6210\u540E\u53C8\u6709\u65B0\u8BC1\u636E\uFF08\u89C1 recent_evidence \u6700\u65B0\u4E00\u6761\uFF09\u2014\u2014\u5148\u6838\u5BF9\u518D\u51B3\u5B9A\uFF0C\u591A\u534A\u8BE5 cancelled" : void 0,
|
|
15094
15249
|
_received_at: now.toISOString()
|
|
15095
15250
|
};
|
|
15096
15251
|
}
|
|
15097
|
-
var DEFAULT_PROMPT, Sweep;
|
|
15252
|
+
var BACKLOG_ALERT_RUNS, STALE_HINT_HOURS, DEFAULT_PROMPT, Sweep;
|
|
15098
15253
|
var init_sweep = __esm({
|
|
15099
15254
|
"src/memory/proactive/sweep.ts"() {
|
|
15100
15255
|
"use strict";
|
|
15101
15256
|
init_calendar_bridge();
|
|
15257
|
+
init_intent();
|
|
15258
|
+
init_clock();
|
|
15259
|
+
BACKLOG_ALERT_RUNS = 12;
|
|
15260
|
+
STALE_HINT_HOURS = 8;
|
|
15102
15261
|
DEFAULT_PROMPT = `[proactive] \u6709 action \u5230\u671F\u4E86\u3002\u5904\u7406\u89C4\u5219\uFF1A
|
|
15103
15262
|
|
|
15104
|
-
1. **\
|
|
15105
|
-
|
|
15263
|
+
1. **\u5FC5\u987B\u8C03\u5DE5\u5177\u843D\u72B6\u6001**\uFF08\u786C\u8981\u6C42\uFF0C\u4E0D\u662F\u5EFA\u8BAE\uFF09\uFF1A
|
|
15264
|
+
- \u5904\u7406\u5B8C \u2192 \`proactive_done\`\uFF08action_id\uFF0C\u843D\u8FDB\u65E5\u5386\u65F6\u5E26 calendar_id\uFF09
|
|
15265
|
+
- \u8DF3\u8FC7 \u2192 \`proactive_cancel\`\uFF08action_id + reason\uFF09
|
|
15266
|
+
\u26A0\uFE0F **\u5728\u56DE\u590D\u6B63\u6587\u91CC\u5199 \`act-xxx: cancelled\` \u4E0D\u7B97\u6807\u8BB0** \u2014\u2014 \u90A3\u662F\u7559\u75D5\uFF0C\u4E0D\u843D\u5E93\u3002
|
|
15267
|
+
\u7CFB\u7EDF\u6709\u4E00\u9053\u515C\u5E95\u89E3\u6790\u4F1A\u635E\u6B63\u6587\uFF0C\u4F46\u5DE5\u5177\u8DEF\u5F84\u624D\u662F\u6B63\u8DEF\uFF08\u7406\u7531\u66F4\u6743\u5A01\u3001\u80FD\u56DE\u586B calendar\uFF09\u3002
|
|
15268
|
+
2. **\u6279\u91CF\u5904\u7406**\uFF1A\u5148\u5168\u90E8\u5224\u65AD\u5B8C\uFF0C\u518D\u7EDF\u4E00\u8C03\u5DE5\u5177\uFF08\u4E0D\u8981\u6BCF\u6761\u8C03\u4E00\u6B21\uFF09
|
|
15269
|
+
3. **status \u53EA\u6709**\uFF1Acompleted\uFF08\u5DF2\u5904\u7406\uFF0C\u542B\u5DF2\u544A\u8BC9 TA\uFF09/ cancelled\uFF08\u8DF3\u8FC7\uFF1A\u6280\u672F\u7C7B/\u5DF2\u8FC7\u65F6/\u91CD\u590D\uFF09/ failed\uFF08\u6267\u884C\u5931\u8D25\uFF09
|
|
15106
15270
|
\u26A0\uFE0F \u6CA1\u6709 notified\u3001\u6CA1\u6709 skipped
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15271
|
+
4. **\u91CD\u590D\u68C0\u6D4B**\uFF1A\u591A\u6761 title \u9AD8\u5EA6\u76F8\u4F3C\u65F6\u53EA\u5904\u7406\u4E00\u6761\uFF0C\u5176\u4F59 cancelled
|
|
15272
|
+
5. **\u5148\u6838\u5BF9\u518D\u5224**\uFF1A\u6B63\u6587\u91CC\u7684"\u73B0\u5728/\u4ECA\u5929"\u662F**\u751F\u6210\u65F6\u523B**\u7684\uFF0C\u4E0D\u662F\u6B64\u523B\u2014\u2014
|
|
15273
|
+
\u62FF \`generated_at_local\` \u8DDF \`_received_at\` \u5BF9\u4E00\u4E0B\uFF1B\u518D\u8DDF calendar/goal \u91CC\u5DF2\u6709\u7684\u5BF9\u4E00\u904D\u3002
|
|
15274
|
+
6. **urgency \u662F\u53C2\u8003**\uFF08urgent=\u51E0\u5C0F\u65F6/high=24h/medium=48h/low=\u4E00\u5468\uFF09\uFF0C\u6309\u5B9E\u9645\u60C5\u51B5\u5224\u65AD
|
|
15275
|
+
7. \u51B3\u5B9A\u544A\u8BC9 TA \u65F6\u7528\u4F60\u81EA\u5DF1\u7684\u8BED\u6C14\u2014\u2014\u8FD9\u662F\u4F60\u7684\u4E3B\u52A8\u5173\u6000\uFF0C\u4E0D\u662F\u7CFB\u7EDF\u901A\u77E5`;
|
|
15110
15276
|
Sweep = class {
|
|
15111
15277
|
constructor(deps, intervalMs = 3e5) {
|
|
15112
15278
|
this.deps = deps;
|
|
@@ -15115,6 +15281,9 @@ var init_sweep = __esm({
|
|
|
15115
15281
|
deps;
|
|
15116
15282
|
intervalMs;
|
|
15117
15283
|
timer = null;
|
|
15284
|
+
/** executing 连续增长的轮数(对账告警用) */
|
|
15285
|
+
backlogStreak = 0;
|
|
15286
|
+
lastExecuting = 0;
|
|
15118
15287
|
start() {
|
|
15119
15288
|
if (this.timer) return;
|
|
15120
15289
|
this.timer = setInterval(() => {
|
|
@@ -15137,8 +15306,20 @@ var init_sweep = __esm({
|
|
|
15137
15306
|
}
|
|
15138
15307
|
/** 单轮:收割超时 executing → due → 标记 → 合并注入一次。返回注入条数 */
|
|
15139
15308
|
runOnce(now) {
|
|
15140
|
-
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString());
|
|
15309
|
+
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString(), this.deps.staleHours ?? 36);
|
|
15141
15310
|
if (reaped > 0) console.log(`[proactive] reaped ${reaped} stale executing action(s)`);
|
|
15311
|
+
const execNow = this.deps.persist.countByStatus("executing");
|
|
15312
|
+
if (execNow > 0 && execNow >= this.lastExecuting) this.backlogStreak++;
|
|
15313
|
+
else this.backlogStreak = 0;
|
|
15314
|
+
this.lastExecuting = execNow;
|
|
15315
|
+
if (this.backlogStreak >= BACKLOG_ALERT_RUNS) {
|
|
15316
|
+
this.backlogStreak = 0;
|
|
15317
|
+
try {
|
|
15318
|
+
this.deps.onUnclosedBacklog?.(execNow);
|
|
15319
|
+
} catch (err) {
|
|
15320
|
+
console.warn(`[proactive] backlog alert failed: ${err.message}`);
|
|
15321
|
+
}
|
|
15322
|
+
}
|
|
15142
15323
|
let due = this.deps.persist.dueActions(now.toISOString());
|
|
15143
15324
|
if (due.length === 0) return 0;
|
|
15144
15325
|
const resolvedActions = [];
|
|
@@ -15152,13 +15333,58 @@ var init_sweep = __esm({
|
|
|
15152
15333
|
});
|
|
15153
15334
|
if (resolvedActions.length > 0) {
|
|
15154
15335
|
for (const { id, intentStatus } of resolvedActions) {
|
|
15155
|
-
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
15336
|
+
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
15337
|
+
completedAt: now.toISOString(),
|
|
15338
|
+
cancelReason: `intent ${intentStatus}\u2014\u2014R2 sweep \u518D\u9A8C\u8BC1`
|
|
15339
|
+
});
|
|
15156
15340
|
}
|
|
15157
15341
|
console.log(`[proactive] R2: ${resolvedActions.length} due action(s) cancelled (intent resolved)`);
|
|
15158
15342
|
}
|
|
15343
|
+
const gatedItems = [];
|
|
15344
|
+
const gateOff = (a, reason) => {
|
|
15345
|
+
gatedItems.push({ id: a.id, title: a.title, reason });
|
|
15346
|
+
return false;
|
|
15347
|
+
};
|
|
15348
|
+
const coveredTitles = this.deps.coveredTitles ?? [];
|
|
15349
|
+
const coveredEvents = this.deps.coveredEvents ?? [];
|
|
15350
|
+
if (coveredTitles.length > 0 || coveredEvents.length > 0) {
|
|
15351
|
+
due = due.filter((a) => {
|
|
15352
|
+
if (coveredTitles.some((c) => isSameCommitment(a.title, c))) {
|
|
15353
|
+
return gateOff(a, "calendar/goal \u91CC\u5DF2\u7ECF\u6709\u540C\u4E00\u4EF6\u4E8B\u2014\u2014\u4E0D\u91CD\u590D\u63D0\u9192");
|
|
15354
|
+
}
|
|
15355
|
+
const parts = toCalendarWallParts(a.scheduledTime);
|
|
15356
|
+
const sameMoment = coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact);
|
|
15357
|
+
if (sameMoment && coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact && sharesTerm(a.title, ev.title))) {
|
|
15358
|
+
return gateOff(a, "\u540C\u4E00\u65F6\u523B calendar \u91CC\u5DF2\u6709\u540C\u4E00\u4EF6\u4E8B\uFF08\u65F6\u95F4\u951A\u70B9\u5224\u91CD\uFF09\u2014\u2014\u4E0D\u91CD\u590D\u63D0\u9192");
|
|
15359
|
+
}
|
|
15360
|
+
return true;
|
|
15361
|
+
});
|
|
15362
|
+
}
|
|
15363
|
+
const minLeadDays = this.deps.minLeadDays ?? 0;
|
|
15364
|
+
if (minLeadDays > 0) {
|
|
15365
|
+
due = due.filter((a) => {
|
|
15366
|
+
const leadMs = Date.parse(a.scheduledTime) - Date.parse(a.createdAt);
|
|
15367
|
+
if (leadMs >= minLeadDays * 864e5) return true;
|
|
15368
|
+
return gateOff(a, `\u63D0\u524D\u91CF\u4E0D\u8DB3 ${minLeadDays} \u5929\uFF08\u751F\u6210\u65F6\u79BB\u4E8B\u53D1\u592A\u8FD1\uFF09\u2014\u2014proactive \u53EA\u505A\u5468\u7EA7\u63D0\u524D\u91CF`);
|
|
15369
|
+
});
|
|
15370
|
+
}
|
|
15371
|
+
for (const g of gatedItems) {
|
|
15372
|
+
this.deps.persist.updateActionStatus(g.id, "cancelled", {
|
|
15373
|
+
completedAt: now.toISOString(),
|
|
15374
|
+
cancelReason: g.reason
|
|
15375
|
+
});
|
|
15376
|
+
}
|
|
15377
|
+
if (gatedItems.length > 0) {
|
|
15378
|
+
console.log(`[proactive] gated ${gatedItems.length} action(s): ${gatedItems.map((g) => g.reason.slice(0, 12)).join(" / ")}`);
|
|
15379
|
+
try {
|
|
15380
|
+
this.deps.onGated?.(gatedItems);
|
|
15381
|
+
} catch (err) {
|
|
15382
|
+
console.warn(`[proactive] onGated failed: ${err.message}`);
|
|
15383
|
+
}
|
|
15384
|
+
}
|
|
15159
15385
|
if (due.length === 0) return 0;
|
|
15160
15386
|
for (const act of due) {
|
|
15161
|
-
this.deps.persist.markActionReminded(act.id);
|
|
15387
|
+
this.deps.persist.markActionReminded(act.id, now.toISOString());
|
|
15162
15388
|
}
|
|
15163
15389
|
const payload = due.map((a) => actionPayload(a, now, this.deps.persist.getIntentRecentEvents(a.intentId)));
|
|
15164
15390
|
const blocks = [
|
|
@@ -15169,7 +15395,12 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
15169
15395
|
\`\`\`` },
|
|
15170
15396
|
{ type: "text", text: this.loadPrompt() }
|
|
15171
15397
|
];
|
|
15172
|
-
this.deps.submit(blocks);
|
|
15398
|
+
const delivered = this.deps.submit(blocks);
|
|
15399
|
+
if (!delivered) {
|
|
15400
|
+
const back = this.deps.persist.rollbackReminded(due.map((a) => a.id));
|
|
15401
|
+
console.warn(`[proactive] injection dropped (no main session / guard busy) \u2014 rolled back ${back} action(s) to queued`);
|
|
15402
|
+
return 0;
|
|
15403
|
+
}
|
|
15173
15404
|
console.log(`[proactive] injected ${payload.length} due action(s) to main session`);
|
|
15174
15405
|
return payload.length;
|
|
15175
15406
|
}
|
|
@@ -15250,7 +15481,7 @@ function registerProactiveTools() {
|
|
|
15250
15481
|
completedAt: nowUtc(),
|
|
15251
15482
|
cancelReason: String(args2.reason || "\u672A\u6CE8\u660E")
|
|
15252
15483
|
});
|
|
15253
|
-
return { content: `\u2713 action ${a.id} \
|
|
15484
|
+
return { content: `\u2713 action ${a.id} \u6807\u8BB0\u4E3A cancelled\uFF08${String(args2.reason || "\u672A\u6CE8\u660E")}\uFF09` };
|
|
15254
15485
|
});
|
|
15255
15486
|
return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
|
|
15256
15487
|
}
|
|
@@ -15371,6 +15602,78 @@ var init_completion_evidence = __esm({
|
|
|
15371
15602
|
}
|
|
15372
15603
|
});
|
|
15373
15604
|
|
|
15605
|
+
// src/memory/proactive/verdict-parse.ts
|
|
15606
|
+
function parseVerdicts(text) {
|
|
15607
|
+
if (!text) return [];
|
|
15608
|
+
const hits = [];
|
|
15609
|
+
VERDICT_RE.lastIndex = 0;
|
|
15610
|
+
let m;
|
|
15611
|
+
while ((m = VERDICT_RE.exec(text)) !== null) {
|
|
15612
|
+
hits.push({
|
|
15613
|
+
id: m[1],
|
|
15614
|
+
status: m[2],
|
|
15615
|
+
start: m.index,
|
|
15616
|
+
end: m.index + m[0].length
|
|
15617
|
+
});
|
|
15618
|
+
}
|
|
15619
|
+
const byId = /* @__PURE__ */ new Map();
|
|
15620
|
+
for (let i = 0; i < hits.length; i++) {
|
|
15621
|
+
const h = hits[i];
|
|
15622
|
+
const segEnd = Math.min(hits[i + 1]?.start ?? text.length, h.end + SEGMENT_MAX);
|
|
15623
|
+
const seg = text.slice(h.end, segEnd);
|
|
15624
|
+
let reason = "";
|
|
15625
|
+
const inline = seg.match(INLINE_REASON_RE);
|
|
15626
|
+
if (inline) reason = inline[1] ?? inline[2] ?? inline[3] ?? "";
|
|
15627
|
+
if (!reason) {
|
|
15628
|
+
const r = seg.match(LABELED_REASON_RE);
|
|
15629
|
+
if (r) reason = (r[1] ?? r[2] ?? r[3] ?? r[4] ?? "").trim();
|
|
15630
|
+
}
|
|
15631
|
+
byId.set(h.id, { id: h.id, status: h.status, reason: reason.trim().slice(0, SEGMENT_MAX) });
|
|
15632
|
+
}
|
|
15633
|
+
return [...byId.values()];
|
|
15634
|
+
}
|
|
15635
|
+
function applyProseVerdicts(persist, text) {
|
|
15636
|
+
const verdicts = parseVerdicts(text);
|
|
15637
|
+
const skipped = [];
|
|
15638
|
+
let applied = 0;
|
|
15639
|
+
for (const v of verdicts) {
|
|
15640
|
+
const a = persist.getAction(v.id);
|
|
15641
|
+
if (!a) {
|
|
15642
|
+
skipped.push(`${v.id}(\u4E0D\u5B58\u5728)`);
|
|
15643
|
+
continue;
|
|
15644
|
+
}
|
|
15645
|
+
if (a.status !== "executing") {
|
|
15646
|
+
skipped.push(`${v.id}(${a.status})`);
|
|
15647
|
+
continue;
|
|
15648
|
+
}
|
|
15649
|
+
const at = nowUtc();
|
|
15650
|
+
if (v.status === "completed") {
|
|
15651
|
+
persist.updateActionStatus(v.id, "completed", {
|
|
15652
|
+
completedAt: at,
|
|
15653
|
+
...v.reason ? { note: v.reason } : {}
|
|
15654
|
+
});
|
|
15655
|
+
} else {
|
|
15656
|
+
persist.updateActionStatus(v.id, v.status, {
|
|
15657
|
+
completedAt: at,
|
|
15658
|
+
cancelReason: v.reason || "\u672A\u6CE8\u660E\uFF08\u6B63\u6587\u5224\u5B9A\uFF09"
|
|
15659
|
+
});
|
|
15660
|
+
}
|
|
15661
|
+
applied++;
|
|
15662
|
+
}
|
|
15663
|
+
return { applied, skipped };
|
|
15664
|
+
}
|
|
15665
|
+
var VERDICT_RE, LABELED_REASON_RE, INLINE_REASON_RE, SEGMENT_MAX;
|
|
15666
|
+
var init_verdict_parse = __esm({
|
|
15667
|
+
"src/memory/proactive/verdict-parse.ts"() {
|
|
15668
|
+
"use strict";
|
|
15669
|
+
init_clock();
|
|
15670
|
+
VERDICT_RE = /(act-[0-9a-f]{12})\s*[::]\s*(completed|cancelled|failed)\b/g;
|
|
15671
|
+
LABELED_REASON_RE = /(?:cancel_reason|note|reason)\s*[::]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”|([^\n]*))/i;
|
|
15672
|
+
INLINE_REASON_RE = /^\s*[,,]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”)/;
|
|
15673
|
+
SEGMENT_MAX = 500;
|
|
15674
|
+
}
|
|
15675
|
+
});
|
|
15676
|
+
|
|
15374
15677
|
// src/memory/proactive/plugin.ts
|
|
15375
15678
|
var plugin_exports = {};
|
|
15376
15679
|
__export(plugin_exports, {
|
|
@@ -15378,7 +15681,7 @@ __export(plugin_exports, {
|
|
|
15378
15681
|
feedMem7Cell: () => feedMem7Cell
|
|
15379
15682
|
});
|
|
15380
15683
|
import { join as join32 } from "node:path";
|
|
15381
|
-
import { existsSync as existsSync18, readFileSync as readFileSync24 } from "node:fs";
|
|
15684
|
+
import { existsSync as existsSync18, readFileSync as readFileSync24, appendFileSync as appendFileSync3 } from "node:fs";
|
|
15382
15685
|
function isFiller(text) {
|
|
15383
15686
|
const t = text.trim();
|
|
15384
15687
|
if (t.length >= 5) return false;
|
|
@@ -15451,7 +15754,10 @@ var init_plugin = __esm({
|
|
|
15451
15754
|
init_proactive_tools();
|
|
15452
15755
|
init_prompts();
|
|
15453
15756
|
init_completion_evidence();
|
|
15757
|
+
init_verdict_parse();
|
|
15758
|
+
init_task_manager();
|
|
15454
15759
|
init_opportunity();
|
|
15760
|
+
init_clock();
|
|
15455
15761
|
init_embedding();
|
|
15456
15762
|
init_filters();
|
|
15457
15763
|
init_store2();
|
|
@@ -15613,22 +15919,76 @@ ${String(input.tool_output ?? "")}`
|
|
|
15613
15919
|
return { continue: true };
|
|
15614
15920
|
}
|
|
15615
15921
|
});
|
|
15922
|
+
registerCallbackHook("Stop", {
|
|
15923
|
+
type: "callback",
|
|
15924
|
+
id: "proactive-verdict-parse",
|
|
15925
|
+
callback: async (input) => {
|
|
15926
|
+
if (input?.channel !== "proactive") return { continue: true };
|
|
15927
|
+
try {
|
|
15928
|
+
const r = applyProseVerdicts(this.persist, String(input?.last_assistant_message ?? ""));
|
|
15929
|
+
if (r.applied > 0) {
|
|
15930
|
+
console.log(`[proactive] \u6B63\u6587\u5224\u5B9A\u56DE\u6536 ${r.applied} \u6761\uFF08\u8DF3\u8FC7 ${r.skipped.length}\uFF09`);
|
|
15931
|
+
}
|
|
15932
|
+
} catch (err) {
|
|
15933
|
+
console.warn(`[proactive] verdict parse failed: ${err.message}`);
|
|
15934
|
+
}
|
|
15935
|
+
return { continue: true };
|
|
15936
|
+
}
|
|
15937
|
+
});
|
|
15616
15938
|
this.hookInstalled = true;
|
|
15617
15939
|
activeInstance = this;
|
|
15618
15940
|
console.log(`[proactive] mem7-fed ingest ready (model=${this.config.modelRef}, graphInput=${liveConfig.get("mem7.graphInput") ?? "memcell"})`);
|
|
15619
15941
|
registerProactiveTools();
|
|
15620
15942
|
setProactiveWorkspace(workspace);
|
|
15943
|
+
const pluginCalDb = this.calDb;
|
|
15621
15944
|
this.sweep = new Sweep({
|
|
15622
15945
|
persist: this.persist,
|
|
15623
15946
|
promptFile: join32(workspace, "prompts", "proactive.md"),
|
|
15947
|
+
// 三个都走 getter **每轮现读** —— 热加载规则(config 改了立刻生效)+
|
|
15948
|
+
// calendar 会变,都不能在 start() 时快照。
|
|
15949
|
+
get minLeadDays() {
|
|
15950
|
+
return parseProactiveConfig(liveConfig.get("proactive")).leadGateDays;
|
|
15951
|
+
},
|
|
15952
|
+
get staleHours() {
|
|
15953
|
+
return parseProactiveConfig(liveConfig.get("proactive")).staleHours;
|
|
15954
|
+
},
|
|
15955
|
+
get coveredTitles() {
|
|
15956
|
+
return buildPlanningContext(pluginCalDb, workspace).coveredTitles;
|
|
15957
|
+
},
|
|
15958
|
+
// 带日期时刻的版本 —— 供给"时间锚点判重"(张小媒 0925 review 补的维度)
|
|
15959
|
+
get coveredEvents() {
|
|
15960
|
+
return pluginCalDb ? getCalendarCoverage(pluginCalDb).map((c) => ({ title: c.title, date: c.date, time: c.time })) : [];
|
|
15961
|
+
},
|
|
15962
|
+
// 闸掉的必须留痕:她要能"扫一眼标题,真有冤案我喊"。
|
|
15963
|
+
onGated: (items) => {
|
|
15964
|
+
const gatedPath = join32(workspace, ".proactive", "gated.jsonl");
|
|
15965
|
+
try {
|
|
15966
|
+
appendFileSync3(
|
|
15967
|
+
gatedPath,
|
|
15968
|
+
items.map((i) => JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...i })).join("\n") + "\n",
|
|
15969
|
+
"utf8"
|
|
15970
|
+
);
|
|
15971
|
+
} catch (err) {
|
|
15972
|
+
console.warn(`[proactive] gated.jsonl \u5199\u5165\u5931\u8D25: ${err.message}`);
|
|
15973
|
+
}
|
|
15974
|
+
enqueueNotification(
|
|
15975
|
+
buildNudgeNotification(
|
|
15976
|
+
"stuck",
|
|
15977
|
+
`proactive \u4ECA\u5929\u95F8\u6389\u4E86 ${items.length} \u6761\uFF08\u6CA1\u8FDB\u6CE8\u5165\u3001\u5DF2\u6807 cancelled\uFF09\uFF1A` + items.map((i) => `
|
|
15978
|
+
\xB7 ${i.title.slice(0, 28)} \u2190 ${i.reason.slice(0, 22)}`).join("") + `
|
|
15979
|
+
\u5168\u91CF\u6E05\u5355\uFF1A${gatedPath}\u3002\u89C9\u5F97\u54EA\u6761\u662F\u51A4\u6848\u76F4\u63A5\u8BF4\uFF0C\u6211\u8C03\u9608\u503C\u6216\u6539\u5224\u636E\u3002`
|
|
15980
|
+
),
|
|
15981
|
+
void 0,
|
|
15982
|
+
`proactive-gated-${todayKey()}`
|
|
15983
|
+
);
|
|
15984
|
+
},
|
|
15624
15985
|
submit: (blocks) => {
|
|
15625
15986
|
const mainSessionId = ctx.sessions?.getSessionId?.("scope:main");
|
|
15626
15987
|
if (!mainSessionId) {
|
|
15627
|
-
console.warn("[proactive] no main session \u2014
|
|
15628
|
-
return;
|
|
15988
|
+
console.warn("[proactive] no main session \u2014 sweep injection dropped");
|
|
15989
|
+
return false;
|
|
15629
15990
|
}
|
|
15630
|
-
|
|
15631
|
-
ctx.dispatcher.submitMessage({
|
|
15991
|
+
const ok = ctx.dispatcher.submitMessage({
|
|
15632
15992
|
text: blocks,
|
|
15633
15993
|
sessionId: mainSessionId,
|
|
15634
15994
|
channelName: "proactive",
|
|
@@ -15637,6 +15997,21 @@ ${String(input.tool_output ?? "")}`
|
|
|
15637
15997
|
skipRecall: true,
|
|
15638
15998
|
deps: ctx.deps
|
|
15639
15999
|
});
|
|
16000
|
+
if (ok === false) console.warn("[proactive] dispatcher rejected sweep injection (guard busy) \u2014 rolling back");
|
|
16001
|
+
return ok !== false;
|
|
16002
|
+
},
|
|
16003
|
+
// 对账告警:注入的判定连续多轮不闭环 → 直接告诉她。她本人明确要过这条
|
|
16004
|
+
//("能发现只增不减的人是我,但前提是我收到这个信号")——躺日志里等于没有。
|
|
16005
|
+
onUnclosedBacklog: (count) => {
|
|
16006
|
+
enqueueNotification(
|
|
16007
|
+
buildNudgeNotification(
|
|
16008
|
+
"stuck",
|
|
16009
|
+
`proactive \u6709 ${count} \u6761 action \u6302\u7740 executing\u3001\u8FDE\u7EED 30 \u5206\u949F\u53EA\u589E\u4E0D\u51CF\u2014\u2014\u8BF4\u660E\u6CE8\u5165\u7684\u5224\u5B9A\u6CA1\u95ED\u73AF\u3002\u5904\u7406\u5B8C\u8BF7**\u8C03 proactive_done / proactive_cancel \u5DE5\u5177**\uFF08\u5728\u6B63\u6587\u91CC\u5199 act-<id>: cancelled \u53EA\u4F5C\u515C\u5E95\uFF0C\u5DE5\u5177\u8DEF\u5F84\u7684\u7406\u7531\u66F4\u6743\u5A01\uFF09\u3002\u7528 proactive_status \u770B\u5F53\u524D\u6302\u7740\u7684\u5217\u8868\u3002`
|
|
16010
|
+
),
|
|
16011
|
+
void 0,
|
|
16012
|
+
"proactive-unclosed-backlog"
|
|
16013
|
+
);
|
|
16014
|
+
console.warn(`[proactive] \u5BF9\u8D26\u544A\u8B66\u5DF2\u6CE8\u5165\uFF1Aexecuting=${count} \u8FDE\u7EED ${BACKLOG_ALERT_RUNS} \u8F6E\u53EA\u589E\u4E0D\u51CF`);
|
|
15640
16015
|
}
|
|
15641
16016
|
});
|
|
15642
16017
|
this.sweep.start();
|
|
@@ -15662,8 +16037,12 @@ ${String(input.tool_output ?? "")}`
|
|
|
15662
16037
|
const nowDate = /* @__PURE__ */ new Date();
|
|
15663
16038
|
const stateDir = String(liveConfig.get("stateDir") ?? "");
|
|
15664
16039
|
if (stateDir) {
|
|
15665
|
-
const
|
|
15666
|
-
|
|
16040
|
+
const all = loadForesightEntries(join32(stateDir, "mem7"), nowDate);
|
|
16041
|
+
const events = all.filter((e) => e.kind !== "profile");
|
|
16042
|
+
const mem7Entries = capForesightEntries(events, todayKey(nowDate));
|
|
16043
|
+
if (all.length > 0) {
|
|
16044
|
+
console.log(`[proactive] mem7 foresight: ${all.length} open-window \u2192 \u5254\u753B\u50CF ${all.length - events.length} \u2192 \u9650\u989D\u540E ${mem7Entries.length} \u8FDB prompt`);
|
|
16045
|
+
}
|
|
15667
16046
|
for (const e of mem7Entries) {
|
|
15668
16047
|
const key = e.text.replace(/\s+/g, "");
|
|
15669
16048
|
if (seen.has(key)) continue;
|
|
@@ -15671,7 +16050,6 @@ ${String(input.tool_output ?? "")}`
|
|
|
15671
16050
|
foresightLines.push(`[mem7] [\u7A97\u53E3 ${e.start}~${e.end}] ${e.text}${e.evidence ? `\uFF08\u4F9D\u636E\uFF1A${e.evidence}\uFF09` : ""}`);
|
|
15672
16051
|
}
|
|
15673
16052
|
}
|
|
15674
|
-
if (foresightLines.length > 0) console.log(`[proactive] mem7 foresight total: ${foresightLines.length}`);
|
|
15675
16053
|
} catch (err) {
|
|
15676
16054
|
console.warn(`[proactive] foresight load failed: ${err.message}`);
|
|
15677
16055
|
}
|
|
@@ -15695,6 +16073,7 @@ ${String(input.tool_output ?? "")}`
|
|
|
15695
16073
|
this.hookInstalled = false;
|
|
15696
16074
|
if (activeInstance === this) activeInstance = null;
|
|
15697
16075
|
unregisterCallbackHook("PostToolUse", "proactive-completion-evidence");
|
|
16076
|
+
unregisterCallbackHook("Stop", "proactive-verdict-parse");
|
|
15698
16077
|
await this.waitForIdle();
|
|
15699
16078
|
this.sweep?.stop();
|
|
15700
16079
|
this.sweep = null;
|
|
@@ -29131,7 +29510,7 @@ var NEGATIVE_PATTERNS = [
|
|
|
29131
29510
|
var COLD_PATTERN = /^[嗯哦额喔]+$/;
|
|
29132
29511
|
var INJECTED_MARKERS = ["\u3010\u5B9A\u65F6\u5FC3\u8DF3\u3011", "[\u5185\u5FC3\u5BF9\u8BDD\u6D4B\u8BD5]", "[inner-voice]"];
|
|
29133
29512
|
function initialState() {
|
|
29134
|
-
return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt:
|
|
29513
|
+
return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowLocalIso(), events: [] };
|
|
29135
29514
|
}
|
|
29136
29515
|
async function updateEmotionalState(workspace, sessions) {
|
|
29137
29516
|
const stateFile = path29.join(workspace, "inner-voice", "emotional-state.json");
|
|
@@ -29156,7 +29535,7 @@ async function updateEmotionalState(workspace, sessions) {
|
|
|
29156
29535
|
refreshHoursAgo(state2.events);
|
|
29157
29536
|
const recentV = state2.events.slice(-5).filter((e) => e.hoursAgo < 4).reduce((s, e) => s + e.valence, 0);
|
|
29158
29537
|
state2.trend = recentV > 0.2 ? "rising" : recentV < -0.2 ? "falling" : "stable";
|
|
29159
|
-
state2.updatedAt =
|
|
29538
|
+
state2.updatedAt = nowLocalIso();
|
|
29160
29539
|
saveJson(stateFile, state2);
|
|
29161
29540
|
const recent = state2.events.filter((e) => e.hoursAgo < 4).slice(-3);
|
|
29162
29541
|
const summary = recent.length ? recent.map((e) => `${e.summary}(${e.valence >= 0 ? "+" : ""}${e.valence.toFixed(1)})`).join(" | ") : "\u65E0\u8FD1\u671F\u60C5\u611F\u4E8B\u4EF6";
|
|
@@ -29286,7 +29665,7 @@ function refreshHoursAgo(events) {
|
|
|
29286
29665
|
function appendMoodLog(workspace, state2, summary) {
|
|
29287
29666
|
try {
|
|
29288
29667
|
const logPath = path29.join(workspace, "mood-history.log");
|
|
29289
|
-
const ts =
|
|
29668
|
+
const ts = formatLocal(/* @__PURE__ */ new Date(), false);
|
|
29290
29669
|
fs29.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
|
|
29291
29670
|
`);
|
|
29292
29671
|
} catch (err) {
|
|
@@ -29337,10 +29716,10 @@ function clamp(x, lo, hi) {
|
|
|
29337
29716
|
function clamp01(x) {
|
|
29338
29717
|
return clamp(x, 0, 1);
|
|
29339
29718
|
}
|
|
29340
|
-
function
|
|
29719
|
+
function nowLocalIso() {
|
|
29341
29720
|
return `${toWall().replace(" ", "T")}${localOffsetSuffix()}`;
|
|
29342
29721
|
}
|
|
29343
|
-
function
|
|
29722
|
+
function formatLocal(d, withSec) {
|
|
29344
29723
|
const p = (n) => String(n).padStart(2, "0");
|
|
29345
29724
|
const t = wallParts(d);
|
|
29346
29725
|
return `${t.y}-${p(t.mo)}-${p(t.d)} ${p(t.hh)}:${p(t.mm)}${withSec ? ":" + p(t.ss) : ""}`;
|
|
@@ -33529,6 +33908,9 @@ function buildReportPayload(opts) {
|
|
|
33529
33908
|
msgsToday: opts.counters.msgsToday,
|
|
33530
33909
|
msgsTotal: opts.counters.msgsTotal
|
|
33531
33910
|
};
|
|
33911
|
+
if (opts.counters.lastMsgAt > 0 || opts.counters.msgsTotal === 0) {
|
|
33912
|
+
p.lastMsgAt = opts.counters.lastMsgAt;
|
|
33913
|
+
}
|
|
33532
33914
|
if (opts.identity.contactEmail) p.contactEmail = opts.identity.contactEmail;
|
|
33533
33915
|
return p;
|
|
33534
33916
|
}
|
|
@@ -33540,6 +33922,7 @@ var nowFn = () => /* @__PURE__ */ new Date();
|
|
|
33540
33922
|
var today = "";
|
|
33541
33923
|
var msgsToday = 0;
|
|
33542
33924
|
var msgsTotal = 0;
|
|
33925
|
+
var lastMsgAt = 0;
|
|
33543
33926
|
function rolloverIfNeeded(now) {
|
|
33544
33927
|
const d = todayKey(now);
|
|
33545
33928
|
if (d !== today) {
|
|
@@ -33553,15 +33936,17 @@ function noteUserMessage(source) {
|
|
|
33553
33936
|
rolloverIfNeeded(now);
|
|
33554
33937
|
msgsToday++;
|
|
33555
33938
|
msgsTotal++;
|
|
33939
|
+
lastMsgAt = Math.floor(now.getTime() / 1e3);
|
|
33556
33940
|
}
|
|
33557
33941
|
function snapshotCounters() {
|
|
33558
33942
|
rolloverIfNeeded(nowFn());
|
|
33559
|
-
return { day: today, msgsToday, msgsTotal };
|
|
33943
|
+
return { day: today, msgsToday, msgsTotal, lastMsgAt };
|
|
33560
33944
|
}
|
|
33561
33945
|
function restoreCounters(s) {
|
|
33562
33946
|
today = todayKey(nowFn());
|
|
33563
33947
|
msgsToday = s.day === today && typeof s.msgsToday === "number" && s.msgsToday > 0 ? s.msgsToday : 0;
|
|
33564
33948
|
msgsTotal = typeof s.msgsTotal === "number" && s.msgsTotal > 0 ? s.msgsTotal : 0;
|
|
33949
|
+
lastMsgAt = typeof s.lastMsgAt === "number" && Number.isFinite(s.lastMsgAt) && s.lastMsgAt > 0 ? s.lastMsgAt : 0;
|
|
33565
33950
|
}
|
|
33566
33951
|
|
|
33567
33952
|
// src/optional/reporting/reporter.ts
|