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/engine-startup.mjs
CHANGED
|
@@ -7419,6 +7419,10 @@ function formatLog(i) {
|
|
|
7419
7419
|
function formatWallLabel(i) {
|
|
7420
7420
|
return formatForLlm(i).replace(/\s*(.*)$/, "");
|
|
7421
7421
|
}
|
|
7422
|
+
function formatShort(i) {
|
|
7423
|
+
const p = wallParts(i ?? /* @__PURE__ */ new Date());
|
|
7424
|
+
return `${p.mo}/${p.d} ${p2(p.hh)}:${p2(p.mm)}`;
|
|
7425
|
+
}
|
|
7422
7426
|
function warnOnce(callSite, shape, sample) {
|
|
7423
7427
|
const key = `${callSite}:${shape}`;
|
|
7424
7428
|
if (warned.has(key)) return;
|
|
@@ -9510,17 +9514,46 @@ var init_filters = __esm({
|
|
|
9510
9514
|
});
|
|
9511
9515
|
|
|
9512
9516
|
// src/mem7/core/vector-store.ts
|
|
9513
|
-
import * as lancedb from "@lancedb/lancedb";
|
|
9514
9517
|
import { Schema, Field, Utf8, Float32, Int64, FixedSizeList, TimestampMicrosecond, List } from "apache-arrow";
|
|
9515
9518
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
9516
9519
|
import { join as join22 } from "node:path";
|
|
9517
|
-
|
|
9520
|
+
function lanceSpecifierFor(platform2, arch) {
|
|
9521
|
+
return platform2 === "darwin" && arch === "x64" ? "@engine7/lancedb-intel" : "@lancedb/lancedb";
|
|
9522
|
+
}
|
|
9523
|
+
function loadLance() {
|
|
9524
|
+
if (!lancePromise) {
|
|
9525
|
+
const spec = lanceSpecifierFor(process.platform, process.arch);
|
|
9526
|
+
lancePromise = import(spec).then(
|
|
9527
|
+
// 转发包是 CJS(module.exports = require(...)),命名导出可能落在 default 上
|
|
9528
|
+
(m) => m?.connect ? m : m?.default,
|
|
9529
|
+
(err) => {
|
|
9530
|
+
mem7VectorWarning = err?.message ? String(err.message) : String(err);
|
|
9531
|
+
console.warn(
|
|
9532
|
+
`[mem7] \u5411\u91CF\u7D22\u5F15\u4E0D\u53EF\u7528\uFF1A\u52A0\u8F7D ${spec} \u5931\u8D25 \u2014\u2014 ${mem7VectorWarning}
|
|
9533
|
+
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" : "")
|
|
9534
|
+
);
|
|
9535
|
+
return null;
|
|
9536
|
+
}
|
|
9537
|
+
);
|
|
9538
|
+
}
|
|
9539
|
+
return lancePromise;
|
|
9540
|
+
}
|
|
9541
|
+
var isIntelMac, lancePromise, mem7VectorWarning, Mem7VectorUnavailableError, VECTOR_DIM, BM25_FIELDS, RRF_K, warnedFts, vecField, commonFields, SCHEMAS, Mem7VectorStore;
|
|
9518
9542
|
var init_vector_store = __esm({
|
|
9519
9543
|
"src/mem7/core/vector-store.ts"() {
|
|
9520
9544
|
"use strict";
|
|
9521
9545
|
init_store2();
|
|
9522
9546
|
init_tokenizer();
|
|
9523
9547
|
init_filters();
|
|
9548
|
+
isIntelMac = process.platform === "darwin" && process.arch === "x64";
|
|
9549
|
+
lancePromise = null;
|
|
9550
|
+
mem7VectorWarning = null;
|
|
9551
|
+
Mem7VectorUnavailableError = class extends Error {
|
|
9552
|
+
constructor() {
|
|
9553
|
+
super(`mem7 \u5411\u91CF\u7D22\u5F15\u4E0D\u53EF\u7528\uFF08lancedb \u672A\u80FD\u52A0\u8F7D\uFF09${mem7VectorWarning ? `\uFF1A${mem7VectorWarning}` : ""}`);
|
|
9554
|
+
this.name = "Mem7VectorUnavailableError";
|
|
9555
|
+
}
|
|
9556
|
+
};
|
|
9524
9557
|
VECTOR_DIM = 1024;
|
|
9525
9558
|
BM25_FIELDS = {
|
|
9526
9559
|
foresight: ["foresight_tokens", "evidence_tokens"],
|
|
@@ -9583,8 +9616,10 @@ var init_vector_store = __esm({
|
|
|
9583
9616
|
}
|
|
9584
9617
|
async conn() {
|
|
9585
9618
|
if (!this.db) {
|
|
9619
|
+
const lance = await loadLance();
|
|
9620
|
+
if (!lance) throw new Mem7VectorUnavailableError();
|
|
9586
9621
|
mkdirSync7(this.dir, { recursive: true });
|
|
9587
|
-
this.db = await
|
|
9622
|
+
this.db = await lance.connect(this.dir);
|
|
9588
9623
|
}
|
|
9589
9624
|
return this.db;
|
|
9590
9625
|
}
|
|
@@ -9606,10 +9641,12 @@ var init_vector_store = __esm({
|
|
|
9606
9641
|
* 幂等:已建则跳过(索引缺失才建)。
|
|
9607
9642
|
*/
|
|
9608
9643
|
async ensureFtsIndexes(kind, tbl) {
|
|
9644
|
+
const lance = await loadLance();
|
|
9645
|
+
if (!lance) return;
|
|
9609
9646
|
for (const col of BM25_FIELDS[kind]) {
|
|
9610
9647
|
try {
|
|
9611
9648
|
await tbl.createIndex(col, {
|
|
9612
|
-
config:
|
|
9649
|
+
config: lance.Index.fts({
|
|
9613
9650
|
baseTokenizer: "whitespace",
|
|
9614
9651
|
removeStopWords: false,
|
|
9615
9652
|
lowercase: true,
|
|
@@ -10082,7 +10119,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10082
10119
|
blocks.push(block);
|
|
10083
10120
|
count++;
|
|
10084
10121
|
}
|
|
10085
|
-
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${(
|
|
10122
|
+
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${formatMdTimestamp()}'`);
|
|
10086
10123
|
writeFileSync6(file2, head + "\n\n" + blocks.join("\n\n") + "\n", "utf8");
|
|
10087
10124
|
}
|
|
10088
10125
|
/**
|
|
@@ -10502,7 +10539,7 @@ function calcDurationDays(startDate, endDate) {
|
|
|
10502
10539
|
const ms = Date.parse(endDate + "T00:00:00Z") - Date.parse(startDate + "T00:00:00Z");
|
|
10503
10540
|
return Number.isNaN(ms) ? null : Math.round(ms / 864e5);
|
|
10504
10541
|
}
|
|
10505
|
-
function
|
|
10542
|
+
function cellDateKey(cell) {
|
|
10506
10543
|
return todayKey(cell.startTs);
|
|
10507
10544
|
}
|
|
10508
10545
|
async function extractForesights(cell, ownerId, provider, model) {
|
|
@@ -10531,7 +10568,7 @@ async function extractForesights(cell, ownerId, provider, model) {
|
|
|
10531
10568
|
}
|
|
10532
10569
|
}
|
|
10533
10570
|
if (!raw) return [];
|
|
10534
|
-
const startFallback =
|
|
10571
|
+
const startFallback = cellDateKey(cell);
|
|
10535
10572
|
const out = [];
|
|
10536
10573
|
for (const r of raw) {
|
|
10537
10574
|
if (!r || typeof r !== "object") continue;
|
|
@@ -10572,7 +10609,7 @@ var init_foresight = __esm({
|
|
|
10572
10609
|
});
|
|
10573
10610
|
|
|
10574
10611
|
// src/mem7/core/pipeline.ts
|
|
10575
|
-
function
|
|
10612
|
+
function dateKey(tsMs) {
|
|
10576
10613
|
return todayKey(tsMs);
|
|
10577
10614
|
}
|
|
10578
10615
|
async function runMem7Pipeline(args) {
|
|
@@ -10607,7 +10644,7 @@ async function runMem7Pipeline(args) {
|
|
|
10607
10644
|
store.saveTail(sessionId, tail);
|
|
10608
10645
|
for (const cell of cells) {
|
|
10609
10646
|
const userSenders = [...new Set(cell.messages.filter((m) => m.role === "user").map((m) => m.senderId))];
|
|
10610
|
-
const dateStr =
|
|
10647
|
+
const dateStr = dateKey(cell.startTs);
|
|
10611
10648
|
const cellTimeIso = new Date(cell.startTs).toISOString();
|
|
10612
10649
|
let episodeOut = null;
|
|
10613
10650
|
let factsOut = [];
|
|
@@ -10780,11 +10817,18 @@ function parseProactiveConfig(raw) {
|
|
|
10780
10817
|
console.warn(`[proactive] model "${modelRef}" \u975E\u6CD5\uFF08${err.message}\uFF09\u2014\u2014 \u6539\u7528\u4E3B\u6A21\u578B`);
|
|
10781
10818
|
}
|
|
10782
10819
|
}
|
|
10820
|
+
const num = (v, dflt) => {
|
|
10821
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
10822
|
+
return Number.isFinite(n) && n >= 0 ? n : dflt;
|
|
10823
|
+
};
|
|
10783
10824
|
return {
|
|
10784
10825
|
enabled: obj.enabled === true || obj.enabled === "yes" || obj.enabled === 1,
|
|
10785
10826
|
modelRef: parsed ? `${parsed.providerId}/${parsed.modelId}` : "",
|
|
10786
10827
|
providerId: parsed?.providerId ?? "",
|
|
10787
|
-
modelId: parsed?.modelId ?? ""
|
|
10828
|
+
modelId: parsed?.modelId ?? "",
|
|
10829
|
+
// 默认 3 天 = 翀哥的"以周为单位";staleHours 默认 36 = 与旧行为一致
|
|
10830
|
+
leadGateDays: num(obj.leadGateDays, 3),
|
|
10831
|
+
staleHours: num(obj.staleHours, 36)
|
|
10788
10832
|
};
|
|
10789
10833
|
}
|
|
10790
10834
|
function proactiveDbPath(workspace) {
|
|
@@ -11148,6 +11192,8 @@ function rowToAction(r) {
|
|
|
11148
11192
|
createdAt: r.created_at,
|
|
11149
11193
|
completedAt: r.completed_at ?? void 0,
|
|
11150
11194
|
cancelReason: r.cancel_reason ?? void 0,
|
|
11195
|
+
injectedAt: r.injected_at ?? void 0,
|
|
11196
|
+
note: r.note ?? void 0,
|
|
11151
11197
|
provenance: r.provenance ? JSON.parse(r.provenance) : void 0
|
|
11152
11198
|
};
|
|
11153
11199
|
}
|
|
@@ -11197,6 +11243,8 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
11197
11243
|
created_at TEXT NOT NULL,
|
|
11198
11244
|
completed_at TEXT,
|
|
11199
11245
|
cancel_reason TEXT,
|
|
11246
|
+
injected_at TEXT,
|
|
11247
|
+
note TEXT,
|
|
11200
11248
|
provenance TEXT
|
|
11201
11249
|
);
|
|
11202
11250
|
CREATE INDEX IF NOT EXISTS idx_actions_due ON actions(status, scheduled_time);
|
|
@@ -11236,6 +11284,8 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11236
11284
|
this.db.exec(SCHEMA2);
|
|
11237
11285
|
const actionCols = new Set(this.db.prepare("PRAGMA table_info(actions)").all().map((r) => r.name));
|
|
11238
11286
|
if (!actionCols.has("provenance")) this.db.exec("ALTER TABLE actions ADD COLUMN provenance TEXT");
|
|
11287
|
+
if (!actionCols.has("injected_at")) this.db.exec("ALTER TABLE actions ADD COLUMN injected_at TEXT");
|
|
11288
|
+
if (!actionCols.has("note")) this.db.exec("ALTER TABLE actions ADD COLUMN note TEXT");
|
|
11239
11289
|
const decisionCols = new Set(this.db.prepare("PRAGMA table_info(proactive_decisions)").all().map((r) => r.name));
|
|
11240
11290
|
if (!decisionCols.has("coverage_matched")) this.db.exec("ALTER TABLE proactive_decisions ADD COLUMN coverage_matched INTEGER NOT NULL DEFAULT 0");
|
|
11241
11291
|
this.migrateNakedLocalTimes();
|
|
@@ -11499,16 +11549,51 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11499
11549
|
).run(reason, nowIso2, ...intentIds);
|
|
11500
11550
|
return Number(res.changes);
|
|
11501
11551
|
}
|
|
11552
|
+
/**
|
|
11553
|
+
* 改状态。**只更新显式传入的列**——此前是「任一字段有值就把两列都写」,
|
|
11554
|
+
* 未传的那列被写成 NULL:sweep 的 R2 复检只传 cancelReason,会把已存在的
|
|
11555
|
+
* completed_at 清空(0925 修)。
|
|
11556
|
+
*/
|
|
11502
11557
|
updateActionStatus(id, status, extra) {
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11558
|
+
const sets = ["status=?"];
|
|
11559
|
+
const vals = [status];
|
|
11560
|
+
if (extra?.completedAt !== void 0) {
|
|
11561
|
+
sets.push("completed_at=?");
|
|
11562
|
+
vals.push(extra.completedAt);
|
|
11563
|
+
}
|
|
11564
|
+
if (extra?.cancelReason !== void 0) {
|
|
11565
|
+
sets.push("cancel_reason=?");
|
|
11566
|
+
vals.push(extra.cancelReason);
|
|
11507
11567
|
}
|
|
11568
|
+
if (extra?.note !== void 0) {
|
|
11569
|
+
sets.push("note=?");
|
|
11570
|
+
vals.push(extra.note);
|
|
11571
|
+
}
|
|
11572
|
+
vals.push(id);
|
|
11573
|
+
this.db.prepare(`UPDATE actions SET ${sets.join(", ")} WHERE id=?`).run(...vals);
|
|
11508
11574
|
}
|
|
11509
|
-
/** sweep 注入前标记(防重复注入;复刻 due
|
|
11510
|
-
|
|
11511
|
-
|
|
11575
|
+
/** sweep 注入前标记(防重复注入;复刻 due→推送的原子语义)。
|
|
11576
|
+
* 0925:同时记 injected_at —— 收割机判"超时"必须拿**注入时刻**,不能拿排期/创建时刻。 */
|
|
11577
|
+
markActionReminded(id, injectedAt) {
|
|
11578
|
+
this.db.prepare(`UPDATE actions SET status='executing', injected_at=? WHERE id=? AND status='queued'`).run(injectedAt, id);
|
|
11579
|
+
}
|
|
11580
|
+
/**
|
|
11581
|
+
* 注入失败回滚(0925):submitMessage 被 dispatcher guard 拒了、或主 session 不在——
|
|
11582
|
+
* 此时 action 已被标 executing 却从没进过 agent 的视野,就是一条"谁也没见过"的僵尸。
|
|
11583
|
+
* 退回去重排,下一轮 sweep 还能再试。返回回滚条数。
|
|
11584
|
+
*/
|
|
11585
|
+
rollbackReminded(ids) {
|
|
11586
|
+
if (ids.length === 0) return 0;
|
|
11587
|
+
const ph = ids.map(() => "?").join(",");
|
|
11588
|
+
const res = this.db.prepare(
|
|
11589
|
+
`UPDATE actions SET status='queued', injected_at=NULL WHERE status='executing' AND id IN (${ph})`
|
|
11590
|
+
).run(...ids);
|
|
11591
|
+
return Number(res.changes);
|
|
11592
|
+
}
|
|
11593
|
+
/** 某状态的条数(对账告警用:executing 只增不减 = 闭环断了) */
|
|
11594
|
+
countByStatus(status) {
|
|
11595
|
+
const r = this.db.prepare("SELECT COUNT(*) AS c FROM actions WHERE status=?").get(status);
|
|
11596
|
+
return Number(r?.c ?? 0);
|
|
11512
11597
|
}
|
|
11513
11598
|
/**
|
|
11514
11599
|
* 0915:executing 超时收割。sweep 注入时标 executing,agent 处理后标终态;
|
|
@@ -11517,10 +11602,12 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11517
11602
|
* scheduled_time 或 created_at 距今超 staleHours 的 executing → cancelled。返回收割条数。
|
|
11518
11603
|
*/
|
|
11519
11604
|
reapStaleExecuting(nowIso2, staleHours = 36) {
|
|
11605
|
+
if (!(staleHours > 0)) return 0;
|
|
11520
11606
|
const cutoff = new Date(Date.parse(nowIso2) - staleHours * 36e5).toISOString();
|
|
11521
11607
|
const res = this.db.prepare(
|
|
11522
|
-
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
11523
|
-
|
|
11608
|
+
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
11609
|
+
WHERE status='executing' AND COALESCE(injected_at, created_at) < ?`
|
|
11610
|
+
).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);
|
|
11524
11611
|
return Number(res.changes);
|
|
11525
11612
|
}
|
|
11526
11613
|
/**
|
|
@@ -12090,12 +12177,17 @@ ${D.intent_node_example}
|
|
|
12090
12177
|
\`\`\`
|
|
12091
12178
|
|
|
12092
12179
|
**Intent fields:**
|
|
12093
|
-
- \`suggested_time\`: When to surface/execute this intent
|
|
12094
|
-
- \`expiry\`: When this intent is no longer relevant
|
|
12180
|
+
- \`suggested_time\`: When to surface/execute this intent\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
12181
|
+
- \`expiry\`: When this intent is no longer relevant\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
12095
12182
|
- \`priority\`: "low", "medium", "high", or "urgent"
|
|
12096
12183
|
- \`status\`: "pending", "action_scheduled", or "resolved"
|
|
12097
12184
|
- \`related_time_node\`: Link to a TIME node for urgency tracking
|
|
12098
|
-
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
12185
|
+
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
12186
|
+
|
|
12187
|
+
**Time field format (applies to every *_time field below):**
|
|
12188
|
+
- Write **local wall time with NO timezone suffix** \u2014 \`"2026-09-21T09:00:00"\` means 9am in ${tz()}. This is the only accepted form.
|
|
12189
|
+
- **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.
|
|
12190
|
+
- Anchor on the \`## Current Time\` line when there is one, and convert relative phrasing ("\u660E\u65E99\u70B9") into that local clock.`;
|
|
12099
12191
|
const SECTION_INTENTS_PATTERNS = `
|
|
12100
12192
|
## Pattern-Based Intents
|
|
12101
12193
|
|
|
@@ -12482,7 +12574,7 @@ ${contextText}
|
|
|
12482
12574
|
|
|
12483
12575
|
## Current Time
|
|
12484
12576
|
|
|
12485
|
-
${args.now
|
|
12577
|
+
${formatForLlm(args.now)} \u2190 use THIS as "now" (local wall time, NOT UTC)
|
|
12486
12578
|
|
|
12487
12579
|
## Existing Commitments (calendar / current work state)
|
|
12488
12580
|
|
|
@@ -12589,6 +12681,7 @@ var DEFAULT_DOMAIN, registryCache, DEFAULT_SECTION_ORDER, MODE_PROMPTS, OPPORTUN
|
|
|
12589
12681
|
var init_prompts = __esm({
|
|
12590
12682
|
"src/memory/proactive/prompts.ts"() {
|
|
12591
12683
|
"use strict";
|
|
12684
|
+
init_clock();
|
|
12592
12685
|
DEFAULT_DOMAIN = {
|
|
12593
12686
|
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",
|
|
12594
12687
|
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",
|
|
@@ -12610,12 +12703,12 @@ var init_prompts = __esm({
|
|
|
12610
12703
|
time_node_example: JSON.stringify({
|
|
12611
12704
|
op: "ADD_NODE",
|
|
12612
12705
|
node_type: "time",
|
|
12613
|
-
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:
|
|
12706
|
+
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:00", recurrence: null, urgency_window_hours: 24 }
|
|
12614
12707
|
}, null, 2),
|
|
12615
12708
|
intent_node_example: JSON.stringify({
|
|
12616
12709
|
op: "ADD_NODE",
|
|
12617
12710
|
node_type: "intent",
|
|
12618
|
-
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:
|
|
12711
|
+
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" }
|
|
12619
12712
|
}, null, 2),
|
|
12620
12713
|
pattern_intent_examples: [
|
|
12621
12714
|
"**\u6A21\u5F0F**\uFF1ATA \u6BCF\u5468\u4E09\u665A\u6709\u56FA\u5B9A\u5B89\u6392\uFF08\u6982\u5FF5 c-ta-schedule\uFF0Cstrength 0.7\uFF09",
|
|
@@ -12748,7 +12841,7 @@ Return valid JSON with this structure:
|
|
|
12748
12841
|
{
|
|
12749
12842
|
"title": "Short action title (max 50 chars)",
|
|
12750
12843
|
"description": "Detailed description of what to do",
|
|
12751
|
-
"scheduled_time": "
|
|
12844
|
+
"scheduled_time": "Local wall time, NO timezone suffix - '2026-09-21T09:00:00' means 9am ${tz()}",
|
|
12752
12845
|
"estimated_duration_minutes": 30,
|
|
12753
12846
|
"urgency": "low|medium|high|urgent"
|
|
12754
12847
|
}
|
|
@@ -13492,7 +13585,7 @@ var init_commands = __esm({
|
|
|
13492
13585
|
});
|
|
13493
13586
|
|
|
13494
13587
|
// src/memory/proactive/calendar-bridge.ts
|
|
13495
|
-
function
|
|
13588
|
+
function toCalendarWallParts(iso) {
|
|
13496
13589
|
const b = wallParts(iso);
|
|
13497
13590
|
const dateStr = `${b.mo}/${b.d}`;
|
|
13498
13591
|
const timeExact = `${String(b.hh).padStart(2, "0")}:${String(b.mm).padStart(2, "0")}`;
|
|
@@ -13505,13 +13598,19 @@ function parseCalendarDateMs(dateStr, now) {
|
|
|
13505
13598
|
const day = parseInt(parts[1], 10);
|
|
13506
13599
|
if (!Number.isFinite(month) || !Number.isFinite(day)) return null;
|
|
13507
13600
|
let year = wallParts(now).y;
|
|
13508
|
-
let candidate =
|
|
13601
|
+
let candidate = wallMidnightMs(year, month, day);
|
|
13602
|
+
if (candidate === null) return null;
|
|
13509
13603
|
if (candidate < now.getTime() - 7 * 864e5) {
|
|
13510
13604
|
year += 1;
|
|
13511
|
-
candidate =
|
|
13605
|
+
candidate = wallMidnightMs(year, month, day);
|
|
13512
13606
|
}
|
|
13513
13607
|
return candidate;
|
|
13514
13608
|
}
|
|
13609
|
+
function wallMidnightMs(year, month, day) {
|
|
13610
|
+
const p22 = (n) => String(n).padStart(2, "0");
|
|
13611
|
+
const ms = Date.parse(parseWall(`${year}-${p22(month)}-${p22(day)}T00:00:00`));
|
|
13612
|
+
return Number.isNaN(ms) ? null : ms;
|
|
13613
|
+
}
|
|
13515
13614
|
function injectCalendarEvents(store, calDb, days = 30, now = /* @__PURE__ */ new Date()) {
|
|
13516
13615
|
const cutoffMs = now.getTime() + days * 864e5;
|
|
13517
13616
|
const rows = listEventsForInjection(calDb, 50);
|
|
@@ -13718,6 +13817,16 @@ function enqueueOpportunity(persist, opp, anchor, planningContext, previousTitle
|
|
|
13718
13817
|
console.log(`[proactive] opportunity enqueued (${anchor.label}): ${opp.title}`);
|
|
13719
13818
|
return true;
|
|
13720
13819
|
}
|
|
13820
|
+
function isProfilePrediction(text) {
|
|
13821
|
+
return PROFILE_RE.test(text);
|
|
13822
|
+
}
|
|
13823
|
+
function capForesightEntries(entries2, today2, openCap = 12, upcomingCap = 8) {
|
|
13824
|
+
const isOpen = (e) => e.start <= today2;
|
|
13825
|
+
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)));
|
|
13826
|
+
const open = sorted.filter(isOpen).slice(0, openCap);
|
|
13827
|
+
const upcoming = sorted.filter((e) => !isOpen(e)).slice(0, upcomingCap);
|
|
13828
|
+
return [...open, ...upcoming];
|
|
13829
|
+
}
|
|
13721
13830
|
function foresightMatchKey(text) {
|
|
13722
13831
|
return text.replace(/\n+/g, " ").slice(0, FORESIGHT_TEXT_MAX).replace(/\s+/g, "");
|
|
13723
13832
|
}
|
|
@@ -13767,7 +13876,16 @@ function parseForesightEntries(everosRoot, now, userId) {
|
|
|
13767
13876
|
const key = text.replace(/\s+/g, "");
|
|
13768
13877
|
if (seen.has(key)) continue;
|
|
13769
13878
|
seen.add(key);
|
|
13770
|
-
|
|
13879
|
+
const textFlat = text.replace(/\n+/g, " ");
|
|
13880
|
+
out.push({
|
|
13881
|
+
start,
|
|
13882
|
+
end,
|
|
13883
|
+
text: textFlat.slice(0, FORESIGHT_TEXT_MAX),
|
|
13884
|
+
evidence: evidence.replace(/\n+/g, " ").slice(0, 80),
|
|
13885
|
+
ownerId: blockOwner,
|
|
13886
|
+
// 画像型在这里就标出来,扫描路径据此剔除(见 isProfilePrediction 的注释)
|
|
13887
|
+
kind: isProfilePrediction(textFlat) ? "profile" : "event"
|
|
13888
|
+
});
|
|
13771
13889
|
}
|
|
13772
13890
|
}
|
|
13773
13891
|
return out;
|
|
@@ -13821,7 +13939,10 @@ async function runOpportunityScan(deps) {
|
|
|
13821
13939
|
return ms !== null && ms >= now.getTime() - 864e5 && ms <= now.getTime() + 31 * 864e5;
|
|
13822
13940
|
}).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}`);
|
|
13823
13941
|
const userPrompt = [
|
|
13824
|
-
|
|
13942
|
+
// 这一行必须与上面的 calendar 行同坐标:calendar 的 date/time 是**配置时区**墙上时间,
|
|
13943
|
+
// 而这里此前给的是 UTC Z —— 同一个 prompt 两套坐标、且都没标注,模型无从判断"现在"。
|
|
13944
|
+
`\u5F53\u524D\u65F6\u95F4\uFF1A${formatForLlm(now)}`,
|
|
13945
|
+
`\uFF08\u4E0B\u9762 calendar \u884C\u7684 date/time \u4E5F\u662F ${tz()} \u672C\u5730\u65F6\u523B\uFF0C\u4E0E\u4E4B\u540C\u4E00\u5750\u6807\uFF09`,
|
|
13825
13946
|
"",
|
|
13826
13947
|
"## \u6302\u7740\u7684 intent\uFF08\u672A\u5B8C\u7ED3\uFF09",
|
|
13827
13948
|
activeIntents.length > 0 ? activeIntents.join("\n") : "\uFF08\u65E0\uFF09",
|
|
@@ -13829,7 +13950,8 @@ async function runOpportunityScan(deps) {
|
|
|
13829
13950
|
"## \u672A\u6765\u65E5\u7A0B\uFF08\u672A\u6765 30 \u5929 calendar\uFF09",
|
|
13830
13951
|
calendarLines.length > 0 ? calendarLines.join("\n") : "\uFF08\u65E0\uFF09",
|
|
13831
13952
|
"",
|
|
13832
|
-
|
|
13953
|
+
// 0919 删轨 B 后源头已是 mem7(945be93c 漏改的措辞)
|
|
13954
|
+
"## \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",
|
|
13833
13955
|
(deps.foresightLines ?? []).length > 0 ? (deps.foresightLines ?? []).join("\n") : "\uFF08\u65E0\uFF09",
|
|
13834
13956
|
"",
|
|
13835
13957
|
"## \u8FD1\u671F\u4E8B\u4EF6\uFF08\u6700\u65B0 15 \u6761\uFF09",
|
|
@@ -13909,7 +14031,7 @@ async function runOpportunityScan(deps) {
|
|
|
13909
14031
|
}
|
|
13910
14032
|
return enqueued;
|
|
13911
14033
|
}
|
|
13912
|
-
var OPPORTUNITY_SCAN_INTERVAL_MS, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
14034
|
+
var OPPORTUNITY_SCAN_INTERVAL_MS, PROFILE_RE, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
13913
14035
|
var init_opportunity = __esm({
|
|
13914
14036
|
"src/memory/proactive/opportunity.ts"() {
|
|
13915
14037
|
"use strict";
|
|
@@ -13919,6 +14041,7 @@ var init_opportunity = __esm({
|
|
|
13919
14041
|
init_recall_score();
|
|
13920
14042
|
init_clock();
|
|
13921
14043
|
OPPORTUNITY_SCAN_INTERVAL_MS = 6 * 60 * 6e4;
|
|
14044
|
+
PROFILE_RE = /今后|以后|将来|往后|再次遇到|下次遇到|会一直|会习惯|会继续|会优先|会避免|会主动|会定期|会倾向于|会按|会先|时会|倾向于|更愿意|养成了?.*习惯|将养成|将优先|将避免|习惯性地|will always|will prefer|will avoid|will continue|will keep|will strictly|tends? to|is likely to (?:always|keep|continue)/i;
|
|
13922
14045
|
foresightCache = /* @__PURE__ */ new Map();
|
|
13923
14046
|
FORESIGHT_CACHE_TTL_MS = 30 * 6e4;
|
|
13924
14047
|
FORESIGHT_TEXT_MAX = 150;
|
|
@@ -14927,48 +15050,91 @@ var init_intent = __esm({
|
|
|
14927
15050
|
|
|
14928
15051
|
// src/memory/proactive/sweep.ts
|
|
14929
15052
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
15053
|
+
function sharesTerm(a, b) {
|
|
15054
|
+
const x = normalizeTitle(a);
|
|
15055
|
+
const y = normalizeTitle(b);
|
|
15056
|
+
if (x.length < 2 || y.length < 2) return false;
|
|
15057
|
+
const grams = new Set(Array.from({ length: x.length - 1 }, (_, i) => x.slice(i, i + 2)));
|
|
15058
|
+
for (let i = 0; i < y.length - 1; i++) if (grams.has(y.slice(i, i + 2))) return true;
|
|
15059
|
+
return false;
|
|
15060
|
+
}
|
|
15061
|
+
function isSameCommitment(a, b) {
|
|
15062
|
+
const x = normalizeTitle(a);
|
|
15063
|
+
const y = normalizeTitle(b);
|
|
15064
|
+
if (!x || !y) return false;
|
|
15065
|
+
if (x === y) return true;
|
|
15066
|
+
if (x.length >= 4 && y.includes(x)) return true;
|
|
15067
|
+
if (y.length >= 4 && x.includes(y)) return true;
|
|
15068
|
+
return false;
|
|
15069
|
+
}
|
|
14930
15070
|
function actionPayload(a, now, recentEvidence) {
|
|
14931
15071
|
let suggestedTask = null;
|
|
15072
|
+
let scheduledLocal = null;
|
|
14932
15073
|
try {
|
|
14933
|
-
const { dateStr, timeExact } =
|
|
15074
|
+
const { dateStr, timeExact } = toCalendarWallParts(a.scheduledTime);
|
|
14934
15075
|
suggestedTask = { date: dateStr, time: timeExact };
|
|
15076
|
+
scheduledLocal = formatForLlm(a.scheduledTime);
|
|
15077
|
+
} catch {
|
|
15078
|
+
}
|
|
15079
|
+
let generatedLocal = null;
|
|
15080
|
+
try {
|
|
15081
|
+
generatedLocal = a.createdAt ? formatForLlm(a.createdAt) : null;
|
|
14935
15082
|
} catch {
|
|
14936
15083
|
}
|
|
14937
15084
|
const ageHours = (now.getTime() - Date.parse(a.scheduledTime)) / 36e5;
|
|
14938
|
-
const staleHint = ageHours >
|
|
15085
|
+
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;
|
|
14939
15086
|
return {
|
|
14940
15087
|
action_id: a.id,
|
|
14941
15088
|
intent_id: a.intentId,
|
|
14942
15089
|
title: a.title,
|
|
14943
15090
|
description: a.description,
|
|
14944
15091
|
scheduled_time: a.scheduledTime,
|
|
15092
|
+
// UTC 绝对时刻(机器坐标)
|
|
15093
|
+
scheduled_time_local: scheduledLocal,
|
|
15094
|
+
// 本地可读 —— 与上一行是同一个时刻,别当两件事
|
|
15095
|
+
suggested_task: suggestedTask,
|
|
15096
|
+
// { date, time } 本地墙上时间,calendar add-task 参数
|
|
14945
15097
|
urgency: a.urgency,
|
|
14946
15098
|
duration_minutes: a.durationMinutes,
|
|
14947
15099
|
calendar_id: a.calendarId,
|
|
14948
15100
|
status: a.status,
|
|
14949
|
-
|
|
15101
|
+
generated_at_local: generatedLocal,
|
|
15102
|
+
// 这条 action 是**几点生成的**(正文里的「现在」指这个点)
|
|
14950
15103
|
stale_hint: staleHint,
|
|
14951
|
-
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"
|
|
14952
|
-
|
|
15104
|
+
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"的马后炮当场现形。
|
|
15105
|
+
// 时间戳走 formatShort(本地);此前是 createdAt.slice(5,16) —— 那是 **UTC**,
|
|
15106
|
+
// 混在 suggested_task(本地)旁边且无标注。
|
|
15107
|
+
recent_evidence: recentEvidence && recentEvidence.length > 0 ? recentEvidence.map((e) => `[${formatShort(e.createdAt)}] ${e.title}${e.description ? `\uFF1A${e.description}` : ""}`) : void 0,
|
|
14953
15108
|
// 机械可判的过期事实标记(不靠 LLM):action 创建之后 graph 又来了新证据 →
|
|
14954
15109
|
// action 基于过期事实生成,先核对新证据再决定(生成侧 Time-Evidence Gate 的兜底)
|
|
14955
15110
|
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,
|
|
14956
15111
|
_received_at: now.toISOString()
|
|
14957
15112
|
};
|
|
14958
15113
|
}
|
|
14959
|
-
var DEFAULT_PROMPT, Sweep;
|
|
15114
|
+
var BACKLOG_ALERT_RUNS, STALE_HINT_HOURS, DEFAULT_PROMPT, Sweep;
|
|
14960
15115
|
var init_sweep = __esm({
|
|
14961
15116
|
"src/memory/proactive/sweep.ts"() {
|
|
14962
15117
|
"use strict";
|
|
14963
15118
|
init_calendar_bridge();
|
|
15119
|
+
init_intent();
|
|
15120
|
+
init_clock();
|
|
15121
|
+
BACKLOG_ALERT_RUNS = 12;
|
|
15122
|
+
STALE_HINT_HOURS = 8;
|
|
14964
15123
|
DEFAULT_PROMPT = `[proactive] \u6709 action \u5230\u671F\u4E86\u3002\u5904\u7406\u89C4\u5219\uFF1A
|
|
14965
15124
|
|
|
14966
|
-
1. **\
|
|
14967
|
-
|
|
15125
|
+
1. **\u5FC5\u987B\u8C03\u5DE5\u5177\u843D\u72B6\u6001**\uFF08\u786C\u8981\u6C42\uFF0C\u4E0D\u662F\u5EFA\u8BAE\uFF09\uFF1A
|
|
15126
|
+
- \u5904\u7406\u5B8C \u2192 \`proactive_done\`\uFF08action_id\uFF0C\u843D\u8FDB\u65E5\u5386\u65F6\u5E26 calendar_id\uFF09
|
|
15127
|
+
- \u8DF3\u8FC7 \u2192 \`proactive_cancel\`\uFF08action_id + reason\uFF09
|
|
15128
|
+
\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
|
|
15129
|
+
\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
|
|
15130
|
+
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
|
|
15131
|
+
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
|
|
14968
15132
|
\u26A0\uFE0F \u6CA1\u6709 notified\u3001\u6CA1\u6709 skipped
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
15133
|
+
4. **\u91CD\u590D\u68C0\u6D4B**\uFF1A\u591A\u6761 title \u9AD8\u5EA6\u76F8\u4F3C\u65F6\u53EA\u5904\u7406\u4E00\u6761\uFF0C\u5176\u4F59 cancelled
|
|
15134
|
+
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
|
|
15135
|
+
\u62FF \`generated_at_local\` \u8DDF \`_received_at\` \u5BF9\u4E00\u4E0B\uFF1B\u518D\u8DDF calendar/goal \u91CC\u5DF2\u6709\u7684\u5BF9\u4E00\u904D\u3002
|
|
15136
|
+
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
|
|
15137
|
+
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`;
|
|
14972
15138
|
Sweep = class {
|
|
14973
15139
|
constructor(deps, intervalMs = 3e5) {
|
|
14974
15140
|
this.deps = deps;
|
|
@@ -14977,6 +15143,9 @@ var init_sweep = __esm({
|
|
|
14977
15143
|
deps;
|
|
14978
15144
|
intervalMs;
|
|
14979
15145
|
timer = null;
|
|
15146
|
+
/** executing 连续增长的轮数(对账告警用) */
|
|
15147
|
+
backlogStreak = 0;
|
|
15148
|
+
lastExecuting = 0;
|
|
14980
15149
|
start() {
|
|
14981
15150
|
if (this.timer) return;
|
|
14982
15151
|
this.timer = setInterval(() => {
|
|
@@ -14999,8 +15168,20 @@ var init_sweep = __esm({
|
|
|
14999
15168
|
}
|
|
15000
15169
|
/** 单轮:收割超时 executing → due → 标记 → 合并注入一次。返回注入条数 */
|
|
15001
15170
|
runOnce(now) {
|
|
15002
|
-
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString());
|
|
15171
|
+
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString(), this.deps.staleHours ?? 36);
|
|
15003
15172
|
if (reaped > 0) console.log(`[proactive] reaped ${reaped} stale executing action(s)`);
|
|
15173
|
+
const execNow = this.deps.persist.countByStatus("executing");
|
|
15174
|
+
if (execNow > 0 && execNow >= this.lastExecuting) this.backlogStreak++;
|
|
15175
|
+
else this.backlogStreak = 0;
|
|
15176
|
+
this.lastExecuting = execNow;
|
|
15177
|
+
if (this.backlogStreak >= BACKLOG_ALERT_RUNS) {
|
|
15178
|
+
this.backlogStreak = 0;
|
|
15179
|
+
try {
|
|
15180
|
+
this.deps.onUnclosedBacklog?.(execNow);
|
|
15181
|
+
} catch (err) {
|
|
15182
|
+
console.warn(`[proactive] backlog alert failed: ${err.message}`);
|
|
15183
|
+
}
|
|
15184
|
+
}
|
|
15004
15185
|
let due = this.deps.persist.dueActions(now.toISOString());
|
|
15005
15186
|
if (due.length === 0) return 0;
|
|
15006
15187
|
const resolvedActions = [];
|
|
@@ -15014,13 +15195,58 @@ var init_sweep = __esm({
|
|
|
15014
15195
|
});
|
|
15015
15196
|
if (resolvedActions.length > 0) {
|
|
15016
15197
|
for (const { id, intentStatus } of resolvedActions) {
|
|
15017
|
-
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
15198
|
+
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
15199
|
+
completedAt: now.toISOString(),
|
|
15200
|
+
cancelReason: `intent ${intentStatus}\u2014\u2014R2 sweep \u518D\u9A8C\u8BC1`
|
|
15201
|
+
});
|
|
15018
15202
|
}
|
|
15019
15203
|
console.log(`[proactive] R2: ${resolvedActions.length} due action(s) cancelled (intent resolved)`);
|
|
15020
15204
|
}
|
|
15205
|
+
const gatedItems = [];
|
|
15206
|
+
const gateOff = (a, reason) => {
|
|
15207
|
+
gatedItems.push({ id: a.id, title: a.title, reason });
|
|
15208
|
+
return false;
|
|
15209
|
+
};
|
|
15210
|
+
const coveredTitles = this.deps.coveredTitles ?? [];
|
|
15211
|
+
const coveredEvents = this.deps.coveredEvents ?? [];
|
|
15212
|
+
if (coveredTitles.length > 0 || coveredEvents.length > 0) {
|
|
15213
|
+
due = due.filter((a) => {
|
|
15214
|
+
if (coveredTitles.some((c) => isSameCommitment(a.title, c))) {
|
|
15215
|
+
return gateOff(a, "calendar/goal \u91CC\u5DF2\u7ECF\u6709\u540C\u4E00\u4EF6\u4E8B\u2014\u2014\u4E0D\u91CD\u590D\u63D0\u9192");
|
|
15216
|
+
}
|
|
15217
|
+
const parts = toCalendarWallParts(a.scheduledTime);
|
|
15218
|
+
const sameMoment = coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact);
|
|
15219
|
+
if (sameMoment && coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact && sharesTerm(a.title, ev.title))) {
|
|
15220
|
+
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");
|
|
15221
|
+
}
|
|
15222
|
+
return true;
|
|
15223
|
+
});
|
|
15224
|
+
}
|
|
15225
|
+
const minLeadDays = this.deps.minLeadDays ?? 0;
|
|
15226
|
+
if (minLeadDays > 0) {
|
|
15227
|
+
due = due.filter((a) => {
|
|
15228
|
+
const leadMs = Date.parse(a.scheduledTime) - Date.parse(a.createdAt);
|
|
15229
|
+
if (leadMs >= minLeadDays * 864e5) return true;
|
|
15230
|
+
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`);
|
|
15231
|
+
});
|
|
15232
|
+
}
|
|
15233
|
+
for (const g of gatedItems) {
|
|
15234
|
+
this.deps.persist.updateActionStatus(g.id, "cancelled", {
|
|
15235
|
+
completedAt: now.toISOString(),
|
|
15236
|
+
cancelReason: g.reason
|
|
15237
|
+
});
|
|
15238
|
+
}
|
|
15239
|
+
if (gatedItems.length > 0) {
|
|
15240
|
+
console.log(`[proactive] gated ${gatedItems.length} action(s): ${gatedItems.map((g) => g.reason.slice(0, 12)).join(" / ")}`);
|
|
15241
|
+
try {
|
|
15242
|
+
this.deps.onGated?.(gatedItems);
|
|
15243
|
+
} catch (err) {
|
|
15244
|
+
console.warn(`[proactive] onGated failed: ${err.message}`);
|
|
15245
|
+
}
|
|
15246
|
+
}
|
|
15021
15247
|
if (due.length === 0) return 0;
|
|
15022
15248
|
for (const act of due) {
|
|
15023
|
-
this.deps.persist.markActionReminded(act.id);
|
|
15249
|
+
this.deps.persist.markActionReminded(act.id, now.toISOString());
|
|
15024
15250
|
}
|
|
15025
15251
|
const payload = due.map((a) => actionPayload(a, now, this.deps.persist.getIntentRecentEvents(a.intentId)));
|
|
15026
15252
|
const blocks = [
|
|
@@ -15031,7 +15257,12 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
15031
15257
|
\`\`\`` },
|
|
15032
15258
|
{ type: "text", text: this.loadPrompt() }
|
|
15033
15259
|
];
|
|
15034
|
-
this.deps.submit(blocks);
|
|
15260
|
+
const delivered = this.deps.submit(blocks);
|
|
15261
|
+
if (!delivered) {
|
|
15262
|
+
const back = this.deps.persist.rollbackReminded(due.map((a) => a.id));
|
|
15263
|
+
console.warn(`[proactive] injection dropped (no main session / guard busy) \u2014 rolled back ${back} action(s) to queued`);
|
|
15264
|
+
return 0;
|
|
15265
|
+
}
|
|
15035
15266
|
console.log(`[proactive] injected ${payload.length} due action(s) to main session`);
|
|
15036
15267
|
return payload.length;
|
|
15037
15268
|
}
|
|
@@ -15112,7 +15343,7 @@ function registerProactiveTools() {
|
|
|
15112
15343
|
completedAt: nowUtc(),
|
|
15113
15344
|
cancelReason: String(args.reason || "\u672A\u6CE8\u660E")
|
|
15114
15345
|
});
|
|
15115
|
-
return { content: `\u2713 action ${a.id} \
|
|
15346
|
+
return { content: `\u2713 action ${a.id} \u6807\u8BB0\u4E3A cancelled\uFF08${String(args.reason || "\u672A\u6CE8\u660E")}\uFF09` };
|
|
15116
15347
|
});
|
|
15117
15348
|
return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
|
|
15118
15349
|
}
|
|
@@ -15233,6 +15464,78 @@ var init_completion_evidence = __esm({
|
|
|
15233
15464
|
}
|
|
15234
15465
|
});
|
|
15235
15466
|
|
|
15467
|
+
// src/memory/proactive/verdict-parse.ts
|
|
15468
|
+
function parseVerdicts(text) {
|
|
15469
|
+
if (!text) return [];
|
|
15470
|
+
const hits = [];
|
|
15471
|
+
VERDICT_RE.lastIndex = 0;
|
|
15472
|
+
let m;
|
|
15473
|
+
while ((m = VERDICT_RE.exec(text)) !== null) {
|
|
15474
|
+
hits.push({
|
|
15475
|
+
id: m[1],
|
|
15476
|
+
status: m[2],
|
|
15477
|
+
start: m.index,
|
|
15478
|
+
end: m.index + m[0].length
|
|
15479
|
+
});
|
|
15480
|
+
}
|
|
15481
|
+
const byId = /* @__PURE__ */ new Map();
|
|
15482
|
+
for (let i = 0; i < hits.length; i++) {
|
|
15483
|
+
const h = hits[i];
|
|
15484
|
+
const segEnd = Math.min(hits[i + 1]?.start ?? text.length, h.end + SEGMENT_MAX);
|
|
15485
|
+
const seg = text.slice(h.end, segEnd);
|
|
15486
|
+
let reason = "";
|
|
15487
|
+
const inline = seg.match(INLINE_REASON_RE);
|
|
15488
|
+
if (inline) reason = inline[1] ?? inline[2] ?? inline[3] ?? "";
|
|
15489
|
+
if (!reason) {
|
|
15490
|
+
const r = seg.match(LABELED_REASON_RE);
|
|
15491
|
+
if (r) reason = (r[1] ?? r[2] ?? r[3] ?? r[4] ?? "").trim();
|
|
15492
|
+
}
|
|
15493
|
+
byId.set(h.id, { id: h.id, status: h.status, reason: reason.trim().slice(0, SEGMENT_MAX) });
|
|
15494
|
+
}
|
|
15495
|
+
return [...byId.values()];
|
|
15496
|
+
}
|
|
15497
|
+
function applyProseVerdicts(persist, text) {
|
|
15498
|
+
const verdicts = parseVerdicts(text);
|
|
15499
|
+
const skipped = [];
|
|
15500
|
+
let applied = 0;
|
|
15501
|
+
for (const v of verdicts) {
|
|
15502
|
+
const a = persist.getAction(v.id);
|
|
15503
|
+
if (!a) {
|
|
15504
|
+
skipped.push(`${v.id}(\u4E0D\u5B58\u5728)`);
|
|
15505
|
+
continue;
|
|
15506
|
+
}
|
|
15507
|
+
if (a.status !== "executing") {
|
|
15508
|
+
skipped.push(`${v.id}(${a.status})`);
|
|
15509
|
+
continue;
|
|
15510
|
+
}
|
|
15511
|
+
const at = nowUtc();
|
|
15512
|
+
if (v.status === "completed") {
|
|
15513
|
+
persist.updateActionStatus(v.id, "completed", {
|
|
15514
|
+
completedAt: at,
|
|
15515
|
+
...v.reason ? { note: v.reason } : {}
|
|
15516
|
+
});
|
|
15517
|
+
} else {
|
|
15518
|
+
persist.updateActionStatus(v.id, v.status, {
|
|
15519
|
+
completedAt: at,
|
|
15520
|
+
cancelReason: v.reason || "\u672A\u6CE8\u660E\uFF08\u6B63\u6587\u5224\u5B9A\uFF09"
|
|
15521
|
+
});
|
|
15522
|
+
}
|
|
15523
|
+
applied++;
|
|
15524
|
+
}
|
|
15525
|
+
return { applied, skipped };
|
|
15526
|
+
}
|
|
15527
|
+
var VERDICT_RE, LABELED_REASON_RE, INLINE_REASON_RE, SEGMENT_MAX;
|
|
15528
|
+
var init_verdict_parse = __esm({
|
|
15529
|
+
"src/memory/proactive/verdict-parse.ts"() {
|
|
15530
|
+
"use strict";
|
|
15531
|
+
init_clock();
|
|
15532
|
+
VERDICT_RE = /(act-[0-9a-f]{12})\s*[::]\s*(completed|cancelled|failed)\b/g;
|
|
15533
|
+
LABELED_REASON_RE = /(?:cancel_reason|note|reason)\s*[::]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”|([^\n]*))/i;
|
|
15534
|
+
INLINE_REASON_RE = /^\s*[,,]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”)/;
|
|
15535
|
+
SEGMENT_MAX = 500;
|
|
15536
|
+
}
|
|
15537
|
+
});
|
|
15538
|
+
|
|
15236
15539
|
// src/memory/proactive/plugin.ts
|
|
15237
15540
|
var plugin_exports = {};
|
|
15238
15541
|
__export(plugin_exports, {
|
|
@@ -15240,7 +15543,7 @@ __export(plugin_exports, {
|
|
|
15240
15543
|
feedMem7Cell: () => feedMem7Cell
|
|
15241
15544
|
});
|
|
15242
15545
|
import { join as join31 } from "node:path";
|
|
15243
|
-
import { existsSync as existsSync17, readFileSync as readFileSync23 } from "node:fs";
|
|
15546
|
+
import { existsSync as existsSync17, readFileSync as readFileSync23, appendFileSync as appendFileSync3 } from "node:fs";
|
|
15244
15547
|
function isFiller(text) {
|
|
15245
15548
|
const t = text.trim();
|
|
15246
15549
|
if (t.length >= 5) return false;
|
|
@@ -15313,7 +15616,10 @@ var init_plugin = __esm({
|
|
|
15313
15616
|
init_proactive_tools();
|
|
15314
15617
|
init_prompts();
|
|
15315
15618
|
init_completion_evidence();
|
|
15619
|
+
init_verdict_parse();
|
|
15620
|
+
init_task_manager();
|
|
15316
15621
|
init_opportunity();
|
|
15622
|
+
init_clock();
|
|
15317
15623
|
init_embedding();
|
|
15318
15624
|
init_filters();
|
|
15319
15625
|
init_store2();
|
|
@@ -15475,22 +15781,76 @@ ${String(input.tool_output ?? "")}`
|
|
|
15475
15781
|
return { continue: true };
|
|
15476
15782
|
}
|
|
15477
15783
|
});
|
|
15784
|
+
registerCallbackHook("Stop", {
|
|
15785
|
+
type: "callback",
|
|
15786
|
+
id: "proactive-verdict-parse",
|
|
15787
|
+
callback: async (input) => {
|
|
15788
|
+
if (input?.channel !== "proactive") return { continue: true };
|
|
15789
|
+
try {
|
|
15790
|
+
const r = applyProseVerdicts(this.persist, String(input?.last_assistant_message ?? ""));
|
|
15791
|
+
if (r.applied > 0) {
|
|
15792
|
+
console.log(`[proactive] \u6B63\u6587\u5224\u5B9A\u56DE\u6536 ${r.applied} \u6761\uFF08\u8DF3\u8FC7 ${r.skipped.length}\uFF09`);
|
|
15793
|
+
}
|
|
15794
|
+
} catch (err) {
|
|
15795
|
+
console.warn(`[proactive] verdict parse failed: ${err.message}`);
|
|
15796
|
+
}
|
|
15797
|
+
return { continue: true };
|
|
15798
|
+
}
|
|
15799
|
+
});
|
|
15478
15800
|
this.hookInstalled = true;
|
|
15479
15801
|
activeInstance = this;
|
|
15480
15802
|
console.log(`[proactive] mem7-fed ingest ready (model=${this.config.modelRef}, graphInput=${liveConfig.get("mem7.graphInput") ?? "memcell"})`);
|
|
15481
15803
|
registerProactiveTools();
|
|
15482
15804
|
setProactiveWorkspace(workspace);
|
|
15805
|
+
const pluginCalDb = this.calDb;
|
|
15483
15806
|
this.sweep = new Sweep({
|
|
15484
15807
|
persist: this.persist,
|
|
15485
15808
|
promptFile: join31(workspace, "prompts", "proactive.md"),
|
|
15809
|
+
// 三个都走 getter **每轮现读** —— 热加载规则(config 改了立刻生效)+
|
|
15810
|
+
// calendar 会变,都不能在 start() 时快照。
|
|
15811
|
+
get minLeadDays() {
|
|
15812
|
+
return parseProactiveConfig(liveConfig.get("proactive")).leadGateDays;
|
|
15813
|
+
},
|
|
15814
|
+
get staleHours() {
|
|
15815
|
+
return parseProactiveConfig(liveConfig.get("proactive")).staleHours;
|
|
15816
|
+
},
|
|
15817
|
+
get coveredTitles() {
|
|
15818
|
+
return buildPlanningContext(pluginCalDb, workspace).coveredTitles;
|
|
15819
|
+
},
|
|
15820
|
+
// 带日期时刻的版本 —— 供给"时间锚点判重"(张小媒 0925 review 补的维度)
|
|
15821
|
+
get coveredEvents() {
|
|
15822
|
+
return pluginCalDb ? getCalendarCoverage(pluginCalDb).map((c) => ({ title: c.title, date: c.date, time: c.time })) : [];
|
|
15823
|
+
},
|
|
15824
|
+
// 闸掉的必须留痕:她要能"扫一眼标题,真有冤案我喊"。
|
|
15825
|
+
onGated: (items) => {
|
|
15826
|
+
const gatedPath = join31(workspace, ".proactive", "gated.jsonl");
|
|
15827
|
+
try {
|
|
15828
|
+
appendFileSync3(
|
|
15829
|
+
gatedPath,
|
|
15830
|
+
items.map((i) => JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...i })).join("\n") + "\n",
|
|
15831
|
+
"utf8"
|
|
15832
|
+
);
|
|
15833
|
+
} catch (err) {
|
|
15834
|
+
console.warn(`[proactive] gated.jsonl \u5199\u5165\u5931\u8D25: ${err.message}`);
|
|
15835
|
+
}
|
|
15836
|
+
enqueueNotification(
|
|
15837
|
+
buildNudgeNotification(
|
|
15838
|
+
"stuck",
|
|
15839
|
+
`proactive \u4ECA\u5929\u95F8\u6389\u4E86 ${items.length} \u6761\uFF08\u6CA1\u8FDB\u6CE8\u5165\u3001\u5DF2\u6807 cancelled\uFF09\uFF1A` + items.map((i) => `
|
|
15840
|
+
\xB7 ${i.title.slice(0, 28)} \u2190 ${i.reason.slice(0, 22)}`).join("") + `
|
|
15841
|
+
\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`
|
|
15842
|
+
),
|
|
15843
|
+
void 0,
|
|
15844
|
+
`proactive-gated-${todayKey()}`
|
|
15845
|
+
);
|
|
15846
|
+
},
|
|
15486
15847
|
submit: (blocks) => {
|
|
15487
15848
|
const mainSessionId = ctx.sessions?.getSessionId?.("scope:main");
|
|
15488
15849
|
if (!mainSessionId) {
|
|
15489
|
-
console.warn("[proactive] no main session \u2014
|
|
15490
|
-
return;
|
|
15850
|
+
console.warn("[proactive] no main session \u2014 sweep injection dropped");
|
|
15851
|
+
return false;
|
|
15491
15852
|
}
|
|
15492
|
-
|
|
15493
|
-
ctx.dispatcher.submitMessage({
|
|
15853
|
+
const ok = ctx.dispatcher.submitMessage({
|
|
15494
15854
|
text: blocks,
|
|
15495
15855
|
sessionId: mainSessionId,
|
|
15496
15856
|
channelName: "proactive",
|
|
@@ -15499,6 +15859,21 @@ ${String(input.tool_output ?? "")}`
|
|
|
15499
15859
|
skipRecall: true,
|
|
15500
15860
|
deps: ctx.deps
|
|
15501
15861
|
});
|
|
15862
|
+
if (ok === false) console.warn("[proactive] dispatcher rejected sweep injection (guard busy) \u2014 rolling back");
|
|
15863
|
+
return ok !== false;
|
|
15864
|
+
},
|
|
15865
|
+
// 对账告警:注入的判定连续多轮不闭环 → 直接告诉她。她本人明确要过这条
|
|
15866
|
+
//("能发现只增不减的人是我,但前提是我收到这个信号")——躺日志里等于没有。
|
|
15867
|
+
onUnclosedBacklog: (count) => {
|
|
15868
|
+
enqueueNotification(
|
|
15869
|
+
buildNudgeNotification(
|
|
15870
|
+
"stuck",
|
|
15871
|
+
`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`
|
|
15872
|
+
),
|
|
15873
|
+
void 0,
|
|
15874
|
+
"proactive-unclosed-backlog"
|
|
15875
|
+
);
|
|
15876
|
+
console.warn(`[proactive] \u5BF9\u8D26\u544A\u8B66\u5DF2\u6CE8\u5165\uFF1Aexecuting=${count} \u8FDE\u7EED ${BACKLOG_ALERT_RUNS} \u8F6E\u53EA\u589E\u4E0D\u51CF`);
|
|
15502
15877
|
}
|
|
15503
15878
|
});
|
|
15504
15879
|
this.sweep.start();
|
|
@@ -15524,8 +15899,12 @@ ${String(input.tool_output ?? "")}`
|
|
|
15524
15899
|
const nowDate = /* @__PURE__ */ new Date();
|
|
15525
15900
|
const stateDir = String(liveConfig.get("stateDir") ?? "");
|
|
15526
15901
|
if (stateDir) {
|
|
15527
|
-
const
|
|
15528
|
-
|
|
15902
|
+
const all = loadForesightEntries(join31(stateDir, "mem7"), nowDate);
|
|
15903
|
+
const events = all.filter((e) => e.kind !== "profile");
|
|
15904
|
+
const mem7Entries = capForesightEntries(events, todayKey(nowDate));
|
|
15905
|
+
if (all.length > 0) {
|
|
15906
|
+
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`);
|
|
15907
|
+
}
|
|
15529
15908
|
for (const e of mem7Entries) {
|
|
15530
15909
|
const key = e.text.replace(/\s+/g, "");
|
|
15531
15910
|
if (seen.has(key)) continue;
|
|
@@ -15533,7 +15912,6 @@ ${String(input.tool_output ?? "")}`
|
|
|
15533
15912
|
foresightLines.push(`[mem7] [\u7A97\u53E3 ${e.start}~${e.end}] ${e.text}${e.evidence ? `\uFF08\u4F9D\u636E\uFF1A${e.evidence}\uFF09` : ""}`);
|
|
15534
15913
|
}
|
|
15535
15914
|
}
|
|
15536
|
-
if (foresightLines.length > 0) console.log(`[proactive] mem7 foresight total: ${foresightLines.length}`);
|
|
15537
15915
|
} catch (err) {
|
|
15538
15916
|
console.warn(`[proactive] foresight load failed: ${err.message}`);
|
|
15539
15917
|
}
|
|
@@ -15557,6 +15935,7 @@ ${String(input.tool_output ?? "")}`
|
|
|
15557
15935
|
this.hookInstalled = false;
|
|
15558
15936
|
if (activeInstance === this) activeInstance = null;
|
|
15559
15937
|
unregisterCallbackHook("PostToolUse", "proactive-completion-evidence");
|
|
15938
|
+
unregisterCallbackHook("Stop", "proactive-verdict-parse");
|
|
15560
15939
|
await this.waitForIdle();
|
|
15561
15940
|
this.sweep?.stop();
|
|
15562
15941
|
this.sweep = null;
|
|
@@ -28834,7 +29213,7 @@ var NEGATIVE_PATTERNS = [
|
|
|
28834
29213
|
var COLD_PATTERN = /^[嗯哦额喔]+$/;
|
|
28835
29214
|
var INJECTED_MARKERS = ["\u3010\u5B9A\u65F6\u5FC3\u8DF3\u3011", "[\u5185\u5FC3\u5BF9\u8BDD\u6D4B\u8BD5]", "[inner-voice]"];
|
|
28836
29215
|
function initialState() {
|
|
28837
|
-
return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt:
|
|
29216
|
+
return { version: 1, mood: NEUTRAL, trend: "stable", updatedAt: nowLocalIso(), events: [] };
|
|
28838
29217
|
}
|
|
28839
29218
|
async function updateEmotionalState(workspace, sessions) {
|
|
28840
29219
|
const stateFile = path29.join(workspace, "inner-voice", "emotional-state.json");
|
|
@@ -28859,7 +29238,7 @@ async function updateEmotionalState(workspace, sessions) {
|
|
|
28859
29238
|
refreshHoursAgo(state2.events);
|
|
28860
29239
|
const recentV = state2.events.slice(-5).filter((e) => e.hoursAgo < 4).reduce((s, e) => s + e.valence, 0);
|
|
28861
29240
|
state2.trend = recentV > 0.2 ? "rising" : recentV < -0.2 ? "falling" : "stable";
|
|
28862
|
-
state2.updatedAt =
|
|
29241
|
+
state2.updatedAt = nowLocalIso();
|
|
28863
29242
|
saveJson(stateFile, state2);
|
|
28864
29243
|
const recent = state2.events.filter((e) => e.hoursAgo < 4).slice(-3);
|
|
28865
29244
|
const summary = recent.length ? recent.map((e) => `${e.summary}(${e.valence >= 0 ? "+" : ""}${e.valence.toFixed(1)})`).join(" | ") : "\u65E0\u8FD1\u671F\u60C5\u611F\u4E8B\u4EF6";
|
|
@@ -28989,7 +29368,7 @@ function refreshHoursAgo(events) {
|
|
|
28989
29368
|
function appendMoodLog(workspace, state2, summary) {
|
|
28990
29369
|
try {
|
|
28991
29370
|
const logPath = path29.join(workspace, "mood-history.log");
|
|
28992
|
-
const ts =
|
|
29371
|
+
const ts = formatLocal(/* @__PURE__ */ new Date(), false);
|
|
28993
29372
|
fs29.appendFileSync(logPath, `${ts} mood=${state2.mood.toFixed(2)} trend=${state2.trend} ${summary}
|
|
28994
29373
|
`);
|
|
28995
29374
|
} catch (err) {
|
|
@@ -29040,10 +29419,10 @@ function clamp(x, lo, hi) {
|
|
|
29040
29419
|
function clamp01(x) {
|
|
29041
29420
|
return clamp(x, 0, 1);
|
|
29042
29421
|
}
|
|
29043
|
-
function
|
|
29422
|
+
function nowLocalIso() {
|
|
29044
29423
|
return `${toWall().replace(" ", "T")}${localOffsetSuffix()}`;
|
|
29045
29424
|
}
|
|
29046
|
-
function
|
|
29425
|
+
function formatLocal(d, withSec) {
|
|
29047
29426
|
const p = (n) => String(n).padStart(2, "0");
|
|
29048
29427
|
const t = wallParts(d);
|
|
29049
29428
|
return `${t.y}-${p(t.mo)}-${p(t.d)} ${p(t.hh)}:${p(t.mm)}${withSec ? ":" + p(t.ss) : ""}`;
|
|
@@ -33232,6 +33611,9 @@ function buildReportPayload(opts) {
|
|
|
33232
33611
|
msgsToday: opts.counters.msgsToday,
|
|
33233
33612
|
msgsTotal: opts.counters.msgsTotal
|
|
33234
33613
|
};
|
|
33614
|
+
if (opts.counters.lastMsgAt > 0 || opts.counters.msgsTotal === 0) {
|
|
33615
|
+
p.lastMsgAt = opts.counters.lastMsgAt;
|
|
33616
|
+
}
|
|
33235
33617
|
if (opts.identity.contactEmail) p.contactEmail = opts.identity.contactEmail;
|
|
33236
33618
|
return p;
|
|
33237
33619
|
}
|
|
@@ -33243,6 +33625,7 @@ var nowFn = () => /* @__PURE__ */ new Date();
|
|
|
33243
33625
|
var today = "";
|
|
33244
33626
|
var msgsToday = 0;
|
|
33245
33627
|
var msgsTotal = 0;
|
|
33628
|
+
var lastMsgAt = 0;
|
|
33246
33629
|
function rolloverIfNeeded(now) {
|
|
33247
33630
|
const d = todayKey(now);
|
|
33248
33631
|
if (d !== today) {
|
|
@@ -33256,15 +33639,17 @@ function noteUserMessage(source) {
|
|
|
33256
33639
|
rolloverIfNeeded(now);
|
|
33257
33640
|
msgsToday++;
|
|
33258
33641
|
msgsTotal++;
|
|
33642
|
+
lastMsgAt = Math.floor(now.getTime() / 1e3);
|
|
33259
33643
|
}
|
|
33260
33644
|
function snapshotCounters() {
|
|
33261
33645
|
rolloverIfNeeded(nowFn());
|
|
33262
|
-
return { day: today, msgsToday, msgsTotal };
|
|
33646
|
+
return { day: today, msgsToday, msgsTotal, lastMsgAt };
|
|
33263
33647
|
}
|
|
33264
33648
|
function restoreCounters(s) {
|
|
33265
33649
|
today = todayKey(nowFn());
|
|
33266
33650
|
msgsToday = s.day === today && typeof s.msgsToday === "number" && s.msgsToday > 0 ? s.msgsToday : 0;
|
|
33267
33651
|
msgsTotal = typeof s.msgsTotal === "number" && s.msgsTotal > 0 ? s.msgsTotal : 0;
|
|
33652
|
+
lastMsgAt = typeof s.lastMsgAt === "number" && Number.isFinite(s.lastMsgAt) && s.lastMsgAt > 0 ? s.lastMsgAt : 0;
|
|
33268
33653
|
}
|
|
33269
33654
|
|
|
33270
33655
|
// src/optional/reporting/reporter.ts
|