engine7 7.1.106 → 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 +408 -53
- package/dist/engine-startup.mjs +411 -59
- package/dist/main.mjs +411 -59
- package/package.json +3 -2
- package/templates/workspace/prompts/proactive.md +40 -0
package/dist/cli.mjs
CHANGED
|
@@ -530,6 +530,10 @@ function formatForLlm(i) {
|
|
|
530
530
|
function formatLog(i) {
|
|
531
531
|
return toWall(i ?? /* @__PURE__ */ new Date());
|
|
532
532
|
}
|
|
533
|
+
function formatShort(i) {
|
|
534
|
+
const p = wallParts(i ?? /* @__PURE__ */ new Date());
|
|
535
|
+
return `${p.mo}/${p.d} ${p2(p.hh)}:${p2(p.mm)}`;
|
|
536
|
+
}
|
|
533
537
|
function warnOnce(callSite, shape, sample) {
|
|
534
538
|
const key = `${callSite}:${shape}`;
|
|
535
539
|
if (warned.has(key)) return;
|
|
@@ -20570,7 +20574,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
20570
20574
|
blocks.push(block);
|
|
20571
20575
|
count++;
|
|
20572
20576
|
}
|
|
20573
|
-
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${(
|
|
20577
|
+
head = head.replace(/entry_count: \d+/, `entry_count: ${count}`).replace(/last_appended_at: '[^']*'/, `last_appended_at: '${formatMdTimestamp()}'`);
|
|
20574
20578
|
writeFileSync17(file2, head + "\n\n" + blocks.join("\n\n") + "\n", "utf8");
|
|
20575
20579
|
}
|
|
20576
20580
|
/**
|
|
@@ -20990,7 +20994,7 @@ function calcDurationDays(startDate, endDate) {
|
|
|
20990
20994
|
const ms = Date.parse(endDate + "T00:00:00Z") - Date.parse(startDate + "T00:00:00Z");
|
|
20991
20995
|
return Number.isNaN(ms) ? null : Math.round(ms / 864e5);
|
|
20992
20996
|
}
|
|
20993
|
-
function
|
|
20997
|
+
function cellDateKey(cell) {
|
|
20994
20998
|
return todayKey(cell.startTs);
|
|
20995
20999
|
}
|
|
20996
21000
|
async function extractForesights(cell, ownerId, provider, model) {
|
|
@@ -21019,7 +21023,7 @@ async function extractForesights(cell, ownerId, provider, model) {
|
|
|
21019
21023
|
}
|
|
21020
21024
|
}
|
|
21021
21025
|
if (!raw) return [];
|
|
21022
|
-
const startFallback =
|
|
21026
|
+
const startFallback = cellDateKey(cell);
|
|
21023
21027
|
const out = [];
|
|
21024
21028
|
for (const r of raw) {
|
|
21025
21029
|
if (!r || typeof r !== "object") continue;
|
|
@@ -21060,7 +21064,7 @@ var init_foresight = __esm({
|
|
|
21060
21064
|
});
|
|
21061
21065
|
|
|
21062
21066
|
// src/mem7/core/pipeline.ts
|
|
21063
|
-
function
|
|
21067
|
+
function dateKey(tsMs) {
|
|
21064
21068
|
return todayKey(tsMs);
|
|
21065
21069
|
}
|
|
21066
21070
|
async function runMem7Pipeline(args) {
|
|
@@ -21095,7 +21099,7 @@ async function runMem7Pipeline(args) {
|
|
|
21095
21099
|
store.saveTail(sessionId, tail);
|
|
21096
21100
|
for (const cell of cells) {
|
|
21097
21101
|
const userSenders = [...new Set(cell.messages.filter((m) => m.role === "user").map((m) => m.senderId))];
|
|
21098
|
-
const dateStr =
|
|
21102
|
+
const dateStr = dateKey(cell.startTs);
|
|
21099
21103
|
const cellTimeIso = new Date(cell.startTs).toISOString();
|
|
21100
21104
|
let episodeOut = null;
|
|
21101
21105
|
let factsOut = [];
|
|
@@ -21268,11 +21272,18 @@ function parseProactiveConfig(raw) {
|
|
|
21268
21272
|
console.warn(`[proactive] model "${modelRef}" \u975E\u6CD5\uFF08${err.message}\uFF09\u2014\u2014 \u6539\u7528\u4E3B\u6A21\u578B`);
|
|
21269
21273
|
}
|
|
21270
21274
|
}
|
|
21275
|
+
const num = (v, dflt) => {
|
|
21276
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
21277
|
+
return Number.isFinite(n) && n >= 0 ? n : dflt;
|
|
21278
|
+
};
|
|
21271
21279
|
return {
|
|
21272
21280
|
enabled: obj.enabled === true || obj.enabled === "yes" || obj.enabled === 1,
|
|
21273
21281
|
modelRef: parsed ? `${parsed.providerId}/${parsed.modelId}` : "",
|
|
21274
21282
|
providerId: parsed?.providerId ?? "",
|
|
21275
|
-
modelId: parsed?.modelId ?? ""
|
|
21283
|
+
modelId: parsed?.modelId ?? "",
|
|
21284
|
+
// 默认 3 天 = 翀哥的"以周为单位";staleHours 默认 36 = 与旧行为一致
|
|
21285
|
+
leadGateDays: num(obj.leadGateDays, 3),
|
|
21286
|
+
staleHours: num(obj.staleHours, 36)
|
|
21276
21287
|
};
|
|
21277
21288
|
}
|
|
21278
21289
|
function proactiveDbPath(workspace) {
|
|
@@ -21636,6 +21647,8 @@ function rowToAction(r) {
|
|
|
21636
21647
|
createdAt: r.created_at,
|
|
21637
21648
|
completedAt: r.completed_at ?? void 0,
|
|
21638
21649
|
cancelReason: r.cancel_reason ?? void 0,
|
|
21650
|
+
injectedAt: r.injected_at ?? void 0,
|
|
21651
|
+
note: r.note ?? void 0,
|
|
21639
21652
|
provenance: r.provenance ? JSON.parse(r.provenance) : void 0
|
|
21640
21653
|
};
|
|
21641
21654
|
}
|
|
@@ -21685,6 +21698,8 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
21685
21698
|
created_at TEXT NOT NULL,
|
|
21686
21699
|
completed_at TEXT,
|
|
21687
21700
|
cancel_reason TEXT,
|
|
21701
|
+
injected_at TEXT,
|
|
21702
|
+
note TEXT,
|
|
21688
21703
|
provenance TEXT
|
|
21689
21704
|
);
|
|
21690
21705
|
CREATE INDEX IF NOT EXISTS idx_actions_due ON actions(status, scheduled_time);
|
|
@@ -21724,6 +21739,8 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
21724
21739
|
this.db.exec(SCHEMA10);
|
|
21725
21740
|
const actionCols = new Set(this.db.prepare("PRAGMA table_info(actions)").all().map((r) => r.name));
|
|
21726
21741
|
if (!actionCols.has("provenance")) this.db.exec("ALTER TABLE actions ADD COLUMN provenance TEXT");
|
|
21742
|
+
if (!actionCols.has("injected_at")) this.db.exec("ALTER TABLE actions ADD COLUMN injected_at TEXT");
|
|
21743
|
+
if (!actionCols.has("note")) this.db.exec("ALTER TABLE actions ADD COLUMN note TEXT");
|
|
21727
21744
|
const decisionCols = new Set(this.db.prepare("PRAGMA table_info(proactive_decisions)").all().map((r) => r.name));
|
|
21728
21745
|
if (!decisionCols.has("coverage_matched")) this.db.exec("ALTER TABLE proactive_decisions ADD COLUMN coverage_matched INTEGER NOT NULL DEFAULT 0");
|
|
21729
21746
|
this.migrateNakedLocalTimes();
|
|
@@ -21987,16 +22004,51 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
21987
22004
|
).run(reason, nowIso2, ...intentIds);
|
|
21988
22005
|
return Number(res.changes);
|
|
21989
22006
|
}
|
|
22007
|
+
/**
|
|
22008
|
+
* 改状态。**只更新显式传入的列**——此前是「任一字段有值就把两列都写」,
|
|
22009
|
+
* 未传的那列被写成 NULL:sweep 的 R2 复检只传 cancelReason,会把已存在的
|
|
22010
|
+
* completed_at 清空(0925 修)。
|
|
22011
|
+
*/
|
|
21990
22012
|
updateActionStatus(id, status, extra) {
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
22013
|
+
const sets = ["status=?"];
|
|
22014
|
+
const vals = [status];
|
|
22015
|
+
if (extra?.completedAt !== void 0) {
|
|
22016
|
+
sets.push("completed_at=?");
|
|
22017
|
+
vals.push(extra.completedAt);
|
|
22018
|
+
}
|
|
22019
|
+
if (extra?.cancelReason !== void 0) {
|
|
22020
|
+
sets.push("cancel_reason=?");
|
|
22021
|
+
vals.push(extra.cancelReason);
|
|
21995
22022
|
}
|
|
22023
|
+
if (extra?.note !== void 0) {
|
|
22024
|
+
sets.push("note=?");
|
|
22025
|
+
vals.push(extra.note);
|
|
22026
|
+
}
|
|
22027
|
+
vals.push(id);
|
|
22028
|
+
this.db.prepare(`UPDATE actions SET ${sets.join(", ")} WHERE id=?`).run(...vals);
|
|
21996
22029
|
}
|
|
21997
|
-
/** sweep 注入前标记(防重复注入;复刻 due
|
|
21998
|
-
|
|
21999
|
-
|
|
22030
|
+
/** sweep 注入前标记(防重复注入;复刻 due→推送的原子语义)。
|
|
22031
|
+
* 0925:同时记 injected_at —— 收割机判"超时"必须拿**注入时刻**,不能拿排期/创建时刻。 */
|
|
22032
|
+
markActionReminded(id, injectedAt) {
|
|
22033
|
+
this.db.prepare(`UPDATE actions SET status='executing', injected_at=? WHERE id=? AND status='queued'`).run(injectedAt, id);
|
|
22034
|
+
}
|
|
22035
|
+
/**
|
|
22036
|
+
* 注入失败回滚(0925):submitMessage 被 dispatcher guard 拒了、或主 session 不在——
|
|
22037
|
+
* 此时 action 已被标 executing 却从没进过 agent 的视野,就是一条"谁也没见过"的僵尸。
|
|
22038
|
+
* 退回去重排,下一轮 sweep 还能再试。返回回滚条数。
|
|
22039
|
+
*/
|
|
22040
|
+
rollbackReminded(ids) {
|
|
22041
|
+
if (ids.length === 0) return 0;
|
|
22042
|
+
const ph = ids.map(() => "?").join(",");
|
|
22043
|
+
const res = this.db.prepare(
|
|
22044
|
+
`UPDATE actions SET status='queued', injected_at=NULL WHERE status='executing' AND id IN (${ph})`
|
|
22045
|
+
).run(...ids);
|
|
22046
|
+
return Number(res.changes);
|
|
22047
|
+
}
|
|
22048
|
+
/** 某状态的条数(对账告警用:executing 只增不减 = 闭环断了) */
|
|
22049
|
+
countByStatus(status) {
|
|
22050
|
+
const r = this.db.prepare("SELECT COUNT(*) AS c FROM actions WHERE status=?").get(status);
|
|
22051
|
+
return Number(r?.c ?? 0);
|
|
22000
22052
|
}
|
|
22001
22053
|
/**
|
|
22002
22054
|
* 0915:executing 超时收割。sweep 注入时标 executing,agent 处理后标终态;
|
|
@@ -22005,10 +22057,12 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
22005
22057
|
* scheduled_time 或 created_at 距今超 staleHours 的 executing → cancelled。返回收割条数。
|
|
22006
22058
|
*/
|
|
22007
22059
|
reapStaleExecuting(nowIso2, staleHours = 36) {
|
|
22060
|
+
if (!(staleHours > 0)) return 0;
|
|
22008
22061
|
const cutoff = new Date(Date.parse(nowIso2) - staleHours * 36e5).toISOString();
|
|
22009
22062
|
const res = this.db.prepare(
|
|
22010
|
-
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
22011
|
-
|
|
22063
|
+
`UPDATE actions SET status='cancelled', completed_at=?, cancel_reason=?
|
|
22064
|
+
WHERE status='executing' AND COALESCE(injected_at, created_at) < ?`
|
|
22065
|
+
).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);
|
|
22012
22066
|
return Number(res.changes);
|
|
22013
22067
|
}
|
|
22014
22068
|
/**
|
|
@@ -22578,12 +22632,17 @@ ${D.intent_node_example}
|
|
|
22578
22632
|
\`\`\`
|
|
22579
22633
|
|
|
22580
22634
|
**Intent fields:**
|
|
22581
|
-
- \`suggested_time\`: When to surface/execute this intent
|
|
22582
|
-
- \`expiry\`: When this intent is no longer relevant
|
|
22635
|
+
- \`suggested_time\`: When to surface/execute this intent\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
22636
|
+
- \`expiry\`: When this intent is no longer relevant\uFF08\u89C1\u4E0B\u65B9\u65F6\u95F4\u683C\u5F0F\uFF09
|
|
22583
22637
|
- \`priority\`: "low", "medium", "high", or "urgent"
|
|
22584
22638
|
- \`status\`: "pending", "action_scheduled", or "resolved"
|
|
22585
22639
|
- \`related_time_node\`: Link to a TIME node for urgency tracking
|
|
22586
|
-
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
22640
|
+
- \`pattern_source\`: The concept ID that triggered this intent (for pattern-based intents)
|
|
22641
|
+
|
|
22642
|
+
**Time field format (applies to every *_time field below):**
|
|
22643
|
+
- Write **local wall time with NO timezone suffix** \u2014 \`"2026-09-21T09:00:00"\` means 9am in ${tz()}. This is the only accepted form.
|
|
22644
|
+
- **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.
|
|
22645
|
+
- Anchor on the \`## Current Time\` line when there is one, and convert relative phrasing ("\u660E\u65E99\u70B9") into that local clock.`;
|
|
22587
22646
|
const SECTION_INTENTS_PATTERNS = `
|
|
22588
22647
|
## Pattern-Based Intents
|
|
22589
22648
|
|
|
@@ -22970,7 +23029,7 @@ ${contextText}
|
|
|
22970
23029
|
|
|
22971
23030
|
## Current Time
|
|
22972
23031
|
|
|
22973
|
-
${args.now
|
|
23032
|
+
${formatForLlm(args.now)} \u2190 use THIS as "now" (local wall time, NOT UTC)
|
|
22974
23033
|
|
|
22975
23034
|
## Existing Commitments (calendar / current work state)
|
|
22976
23035
|
|
|
@@ -23077,6 +23136,7 @@ var DEFAULT_DOMAIN, registryCache, DEFAULT_SECTION_ORDER, MODE_PROMPTS2, OPPORTU
|
|
|
23077
23136
|
var init_prompts = __esm({
|
|
23078
23137
|
"src/memory/proactive/prompts.ts"() {
|
|
23079
23138
|
"use strict";
|
|
23139
|
+
init_clock();
|
|
23080
23140
|
DEFAULT_DOMAIN = {
|
|
23081
23141
|
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",
|
|
23082
23142
|
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",
|
|
@@ -23098,12 +23158,12 @@ var init_prompts = __esm({
|
|
|
23098
23158
|
time_node_example: JSON.stringify({
|
|
23099
23159
|
op: "ADD_NODE",
|
|
23100
23160
|
node_type: "time",
|
|
23101
|
-
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:
|
|
23161
|
+
data: { id: "t-example", title: "\u4F5C\u54C1\u63D0\u4EA4\u622A\u6B62", scheduled_time: "2026-09-21T23:59:00", recurrence: null, urgency_window_hours: 24 }
|
|
23102
23162
|
}, null, 2),
|
|
23103
23163
|
intent_node_example: JSON.stringify({
|
|
23104
23164
|
op: "ADD_NODE",
|
|
23105
23165
|
node_type: "intent",
|
|
23106
|
-
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:
|
|
23166
|
+
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" }
|
|
23107
23167
|
}, null, 2),
|
|
23108
23168
|
pattern_intent_examples: [
|
|
23109
23169
|
"**\u6A21\u5F0F**\uFF1ATA \u6BCF\u5468\u4E09\u665A\u6709\u56FA\u5B9A\u5B89\u6392\uFF08\u6982\u5FF5 c-ta-schedule\uFF0Cstrength 0.7\uFF09",
|
|
@@ -23236,7 +23296,7 @@ Return valid JSON with this structure:
|
|
|
23236
23296
|
{
|
|
23237
23297
|
"title": "Short action title (max 50 chars)",
|
|
23238
23298
|
"description": "Detailed description of what to do",
|
|
23239
|
-
"scheduled_time": "
|
|
23299
|
+
"scheduled_time": "Local wall time, NO timezone suffix - '2026-09-21T09:00:00' means 9am ${tz()}",
|
|
23240
23300
|
"estimated_duration_minutes": 30,
|
|
23241
23301
|
"urgency": "low|medium|high|urgent"
|
|
23242
23302
|
}
|
|
@@ -23312,7 +23372,7 @@ If deadline has no time context, fall back to normal timing rules.
|
|
|
23312
23372
|
});
|
|
23313
23373
|
|
|
23314
23374
|
// src/memory/proactive/calendar-bridge.ts
|
|
23315
|
-
function
|
|
23375
|
+
function toCalendarWallParts(iso) {
|
|
23316
23376
|
const b = wallParts(iso);
|
|
23317
23377
|
const dateStr = `${b.mo}/${b.d}`;
|
|
23318
23378
|
const timeExact = `${String(b.hh).padStart(2, "0")}:${String(b.mm).padStart(2, "0")}`;
|
|
@@ -23325,13 +23385,19 @@ function parseCalendarDateMs(dateStr, now) {
|
|
|
23325
23385
|
const day = parseInt(parts[1], 10);
|
|
23326
23386
|
if (!Number.isFinite(month) || !Number.isFinite(day)) return null;
|
|
23327
23387
|
let year = wallParts(now).y;
|
|
23328
|
-
let candidate =
|
|
23388
|
+
let candidate = wallMidnightMs(year, month, day);
|
|
23389
|
+
if (candidate === null) return null;
|
|
23329
23390
|
if (candidate < now.getTime() - 7 * 864e5) {
|
|
23330
23391
|
year += 1;
|
|
23331
|
-
candidate =
|
|
23392
|
+
candidate = wallMidnightMs(year, month, day);
|
|
23332
23393
|
}
|
|
23333
23394
|
return candidate;
|
|
23334
23395
|
}
|
|
23396
|
+
function wallMidnightMs(year, month, day) {
|
|
23397
|
+
const p22 = (n) => String(n).padStart(2, "0");
|
|
23398
|
+
const ms = Date.parse(parseWall(`${year}-${p22(month)}-${p22(day)}T00:00:00`));
|
|
23399
|
+
return Number.isNaN(ms) ? null : ms;
|
|
23400
|
+
}
|
|
23335
23401
|
function injectCalendarEvents(store, calDb, days = 30, now = /* @__PURE__ */ new Date()) {
|
|
23336
23402
|
const cutoffMs = now.getTime() + days * 864e5;
|
|
23337
23403
|
const rows = listEventsForInjection(calDb, 50);
|
|
@@ -23538,6 +23604,16 @@ function enqueueOpportunity(persist, opp, anchor, planningContext, previousTitle
|
|
|
23538
23604
|
console.log(`[proactive] opportunity enqueued (${anchor.label}): ${opp.title}`);
|
|
23539
23605
|
return true;
|
|
23540
23606
|
}
|
|
23607
|
+
function isProfilePrediction(text) {
|
|
23608
|
+
return PROFILE_RE.test(text);
|
|
23609
|
+
}
|
|
23610
|
+
function capForesightEntries(entries2, today, openCap = 12, upcomingCap = 8) {
|
|
23611
|
+
const isOpen = (e) => e.start <= today;
|
|
23612
|
+
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)));
|
|
23613
|
+
const open = sorted.filter(isOpen).slice(0, openCap);
|
|
23614
|
+
const upcoming = sorted.filter((e) => !isOpen(e)).slice(0, upcomingCap);
|
|
23615
|
+
return [...open, ...upcoming];
|
|
23616
|
+
}
|
|
23541
23617
|
function foresightMatchKey(text) {
|
|
23542
23618
|
return text.replace(/\n+/g, " ").slice(0, FORESIGHT_TEXT_MAX).replace(/\s+/g, "");
|
|
23543
23619
|
}
|
|
@@ -23587,7 +23663,16 @@ function parseForesightEntries(everosRoot, now, userId) {
|
|
|
23587
23663
|
const key = text.replace(/\s+/g, "");
|
|
23588
23664
|
if (seen.has(key)) continue;
|
|
23589
23665
|
seen.add(key);
|
|
23590
|
-
|
|
23666
|
+
const textFlat = text.replace(/\n+/g, " ");
|
|
23667
|
+
out.push({
|
|
23668
|
+
start,
|
|
23669
|
+
end,
|
|
23670
|
+
text: textFlat.slice(0, FORESIGHT_TEXT_MAX),
|
|
23671
|
+
evidence: evidence.replace(/\n+/g, " ").slice(0, 80),
|
|
23672
|
+
ownerId: blockOwner,
|
|
23673
|
+
// 画像型在这里就标出来,扫描路径据此剔除(见 isProfilePrediction 的注释)
|
|
23674
|
+
kind: isProfilePrediction(textFlat) ? "profile" : "event"
|
|
23675
|
+
});
|
|
23591
23676
|
}
|
|
23592
23677
|
}
|
|
23593
23678
|
return out;
|
|
@@ -23641,7 +23726,10 @@ async function runOpportunityScan(deps) {
|
|
|
23641
23726
|
return ms !== null && ms >= now.getTime() - 864e5 && ms <= now.getTime() + 31 * 864e5;
|
|
23642
23727
|
}).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}`);
|
|
23643
23728
|
const userPrompt = [
|
|
23644
|
-
|
|
23729
|
+
// 这一行必须与上面的 calendar 行同坐标:calendar 的 date/time 是**配置时区**墙上时间,
|
|
23730
|
+
// 而这里此前给的是 UTC Z —— 同一个 prompt 两套坐标、且都没标注,模型无从判断"现在"。
|
|
23731
|
+
`\u5F53\u524D\u65F6\u95F4\uFF1A${formatForLlm(now)}`,
|
|
23732
|
+
`\uFF08\u4E0B\u9762 calendar \u884C\u7684 date/time \u4E5F\u662F ${tz()} \u672C\u5730\u65F6\u523B\uFF0C\u4E0E\u4E4B\u540C\u4E00\u5750\u6807\uFF09`,
|
|
23645
23733
|
"",
|
|
23646
23734
|
"## \u6302\u7740\u7684 intent\uFF08\u672A\u5B8C\u7ED3\uFF09",
|
|
23647
23735
|
activeIntents.length > 0 ? activeIntents.join("\n") : "\uFF08\u65E0\uFF09",
|
|
@@ -23649,7 +23737,8 @@ async function runOpportunityScan(deps) {
|
|
|
23649
23737
|
"## \u672A\u6765\u65E5\u7A0B\uFF08\u672A\u6765 30 \u5929 calendar\uFF09",
|
|
23650
23738
|
calendarLines.length > 0 ? calendarLines.join("\n") : "\uFF08\u65E0\uFF09",
|
|
23651
23739
|
"",
|
|
23652
|
-
|
|
23740
|
+
// 0919 删轨 B 后源头已是 mem7(945be93c 漏改的措辞)
|
|
23741
|
+
"## \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",
|
|
23653
23742
|
(deps.foresightLines ?? []).length > 0 ? (deps.foresightLines ?? []).join("\n") : "\uFF08\u65E0\uFF09",
|
|
23654
23743
|
"",
|
|
23655
23744
|
"## \u8FD1\u671F\u4E8B\u4EF6\uFF08\u6700\u65B0 15 \u6761\uFF09",
|
|
@@ -23729,7 +23818,7 @@ async function runOpportunityScan(deps) {
|
|
|
23729
23818
|
}
|
|
23730
23819
|
return enqueued;
|
|
23731
23820
|
}
|
|
23732
|
-
var OPPORTUNITY_SCAN_INTERVAL_MS, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
23821
|
+
var OPPORTUNITY_SCAN_INTERVAL_MS, PROFILE_RE, foresightCache, FORESIGHT_CACHE_TTL_MS, FORESIGHT_TEXT_MAX, FS_MATCH_STOP;
|
|
23733
23822
|
var init_opportunity = __esm({
|
|
23734
23823
|
"src/memory/proactive/opportunity.ts"() {
|
|
23735
23824
|
"use strict";
|
|
@@ -23739,6 +23828,7 @@ var init_opportunity = __esm({
|
|
|
23739
23828
|
init_recall_score();
|
|
23740
23829
|
init_clock();
|
|
23741
23830
|
OPPORTUNITY_SCAN_INTERVAL_MS = 6 * 60 * 6e4;
|
|
23831
|
+
PROFILE_RE = /今后|以后|将来|往后|再次遇到|下次遇到|会一直|会习惯|会继续|会优先|会避免|会主动|会定期|会倾向于|会按|会先|时会|倾向于|更愿意|养成了?.*习惯|将养成|将优先|将避免|习惯性地|will always|will prefer|will avoid|will continue|will keep|will strictly|tends? to|is likely to (?:always|keep|continue)/i;
|
|
23742
23832
|
foresightCache = /* @__PURE__ */ new Map();
|
|
23743
23833
|
FORESIGHT_CACHE_TTL_MS = 30 * 6e4;
|
|
23744
23834
|
FORESIGHT_TEXT_MAX = 150;
|
|
@@ -24747,48 +24837,91 @@ var init_intent = __esm({
|
|
|
24747
24837
|
|
|
24748
24838
|
// src/memory/proactive/sweep.ts
|
|
24749
24839
|
import { readFileSync as readFileSync32 } from "node:fs";
|
|
24840
|
+
function sharesTerm(a, b) {
|
|
24841
|
+
const x = normalizeTitle(a);
|
|
24842
|
+
const y = normalizeTitle(b);
|
|
24843
|
+
if (x.length < 2 || y.length < 2) return false;
|
|
24844
|
+
const grams = new Set(Array.from({ length: x.length - 1 }, (_, i) => x.slice(i, i + 2)));
|
|
24845
|
+
for (let i = 0; i < y.length - 1; i++) if (grams.has(y.slice(i, i + 2))) return true;
|
|
24846
|
+
return false;
|
|
24847
|
+
}
|
|
24848
|
+
function isSameCommitment(a, b) {
|
|
24849
|
+
const x = normalizeTitle(a);
|
|
24850
|
+
const y = normalizeTitle(b);
|
|
24851
|
+
if (!x || !y) return false;
|
|
24852
|
+
if (x === y) return true;
|
|
24853
|
+
if (x.length >= 4 && y.includes(x)) return true;
|
|
24854
|
+
if (y.length >= 4 && x.includes(y)) return true;
|
|
24855
|
+
return false;
|
|
24856
|
+
}
|
|
24750
24857
|
function actionPayload(a, now, recentEvidence) {
|
|
24751
24858
|
let suggestedTask = null;
|
|
24859
|
+
let scheduledLocal = null;
|
|
24752
24860
|
try {
|
|
24753
|
-
const { dateStr, timeExact } =
|
|
24861
|
+
const { dateStr, timeExact } = toCalendarWallParts(a.scheduledTime);
|
|
24754
24862
|
suggestedTask = { date: dateStr, time: timeExact };
|
|
24863
|
+
scheduledLocal = formatForLlm(a.scheduledTime);
|
|
24864
|
+
} catch {
|
|
24865
|
+
}
|
|
24866
|
+
let generatedLocal = null;
|
|
24867
|
+
try {
|
|
24868
|
+
generatedLocal = a.createdAt ? formatForLlm(a.createdAt) : null;
|
|
24755
24869
|
} catch {
|
|
24756
24870
|
}
|
|
24757
24871
|
const ageHours = (now.getTime() - Date.parse(a.scheduledTime)) / 36e5;
|
|
24758
|
-
const staleHint = ageHours >
|
|
24872
|
+
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;
|
|
24759
24873
|
return {
|
|
24760
24874
|
action_id: a.id,
|
|
24761
24875
|
intent_id: a.intentId,
|
|
24762
24876
|
title: a.title,
|
|
24763
24877
|
description: a.description,
|
|
24764
24878
|
scheduled_time: a.scheduledTime,
|
|
24879
|
+
// UTC 绝对时刻(机器坐标)
|
|
24880
|
+
scheduled_time_local: scheduledLocal,
|
|
24881
|
+
// 本地可读 —— 与上一行是同一个时刻,别当两件事
|
|
24882
|
+
suggested_task: suggestedTask,
|
|
24883
|
+
// { date, time } 本地墙上时间,calendar add-task 参数
|
|
24765
24884
|
urgency: a.urgency,
|
|
24766
24885
|
duration_minutes: a.durationMinutes,
|
|
24767
24886
|
calendar_id: a.calendarId,
|
|
24768
24887
|
status: a.status,
|
|
24769
|
-
|
|
24888
|
+
generated_at_local: generatedLocal,
|
|
24889
|
+
// 这条 action 是**几点生成的**(正文里的「现在」指这个点)
|
|
24770
24890
|
stale_hint: staleHint,
|
|
24771
|
-
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"
|
|
24772
|
-
|
|
24891
|
+
// 0906 anti-hindsight:近期关联证据(最新在前)——"后续对话已做完"的马后炮当场现形。
|
|
24892
|
+
// 时间戳走 formatShort(本地);此前是 createdAt.slice(5,16) —— 那是 **UTC**,
|
|
24893
|
+
// 混在 suggested_task(本地)旁边且无标注。
|
|
24894
|
+
recent_evidence: recentEvidence && recentEvidence.length > 0 ? recentEvidence.map((e) => `[${formatShort(e.createdAt)}] ${e.title}${e.description ? `\uFF1A${e.description}` : ""}`) : void 0,
|
|
24773
24895
|
// 机械可判的过期事实标记(不靠 LLM):action 创建之后 graph 又来了新证据 →
|
|
24774
24896
|
// action 基于过期事实生成,先核对新证据再决定(生成侧 Time-Evidence Gate 的兜底)
|
|
24775
24897
|
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,
|
|
24776
24898
|
_received_at: now.toISOString()
|
|
24777
24899
|
};
|
|
24778
24900
|
}
|
|
24779
|
-
var DEFAULT_PROMPT, Sweep;
|
|
24901
|
+
var BACKLOG_ALERT_RUNS, STALE_HINT_HOURS, DEFAULT_PROMPT, Sweep;
|
|
24780
24902
|
var init_sweep = __esm({
|
|
24781
24903
|
"src/memory/proactive/sweep.ts"() {
|
|
24782
24904
|
"use strict";
|
|
24783
24905
|
init_calendar_bridge();
|
|
24906
|
+
init_intent();
|
|
24907
|
+
init_clock();
|
|
24908
|
+
BACKLOG_ALERT_RUNS = 12;
|
|
24909
|
+
STALE_HINT_HOURS = 8;
|
|
24784
24910
|
DEFAULT_PROMPT = `[proactive] \u6709 action \u5230\u671F\u4E86\u3002\u5904\u7406\u89C4\u5219\uFF1A
|
|
24785
24911
|
|
|
24786
|
-
1. **\
|
|
24787
|
-
|
|
24912
|
+
1. **\u5FC5\u987B\u8C03\u5DE5\u5177\u843D\u72B6\u6001**\uFF08\u786C\u8981\u6C42\uFF0C\u4E0D\u662F\u5EFA\u8BAE\uFF09\uFF1A
|
|
24913
|
+
- \u5904\u7406\u5B8C \u2192 \`proactive_done\`\uFF08action_id\uFF0C\u843D\u8FDB\u65E5\u5386\u65F6\u5E26 calendar_id\uFF09
|
|
24914
|
+
- \u8DF3\u8FC7 \u2192 \`proactive_cancel\`\uFF08action_id + reason\uFF09
|
|
24915
|
+
\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
|
|
24916
|
+
\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
|
|
24917
|
+
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
|
|
24918
|
+
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
|
|
24788
24919
|
\u26A0\uFE0F \u6CA1\u6709 notified\u3001\u6CA1\u6709 skipped
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
24920
|
+
4. **\u91CD\u590D\u68C0\u6D4B**\uFF1A\u591A\u6761 title \u9AD8\u5EA6\u76F8\u4F3C\u65F6\u53EA\u5904\u7406\u4E00\u6761\uFF0C\u5176\u4F59 cancelled
|
|
24921
|
+
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
|
|
24922
|
+
\u62FF \`generated_at_local\` \u8DDF \`_received_at\` \u5BF9\u4E00\u4E0B\uFF1B\u518D\u8DDF calendar/goal \u91CC\u5DF2\u6709\u7684\u5BF9\u4E00\u904D\u3002
|
|
24923
|
+
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
|
|
24924
|
+
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`;
|
|
24792
24925
|
Sweep = class {
|
|
24793
24926
|
constructor(deps, intervalMs = 3e5) {
|
|
24794
24927
|
this.deps = deps;
|
|
@@ -24797,6 +24930,9 @@ var init_sweep = __esm({
|
|
|
24797
24930
|
deps;
|
|
24798
24931
|
intervalMs;
|
|
24799
24932
|
timer = null;
|
|
24933
|
+
/** executing 连续增长的轮数(对账告警用) */
|
|
24934
|
+
backlogStreak = 0;
|
|
24935
|
+
lastExecuting = 0;
|
|
24800
24936
|
start() {
|
|
24801
24937
|
if (this.timer) return;
|
|
24802
24938
|
this.timer = setInterval(() => {
|
|
@@ -24819,8 +24955,20 @@ var init_sweep = __esm({
|
|
|
24819
24955
|
}
|
|
24820
24956
|
/** 单轮:收割超时 executing → due → 标记 → 合并注入一次。返回注入条数 */
|
|
24821
24957
|
runOnce(now) {
|
|
24822
|
-
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString());
|
|
24958
|
+
const reaped = this.deps.persist.reapStaleExecuting(now.toISOString(), this.deps.staleHours ?? 36);
|
|
24823
24959
|
if (reaped > 0) console.log(`[proactive] reaped ${reaped} stale executing action(s)`);
|
|
24960
|
+
const execNow = this.deps.persist.countByStatus("executing");
|
|
24961
|
+
if (execNow > 0 && execNow >= this.lastExecuting) this.backlogStreak++;
|
|
24962
|
+
else this.backlogStreak = 0;
|
|
24963
|
+
this.lastExecuting = execNow;
|
|
24964
|
+
if (this.backlogStreak >= BACKLOG_ALERT_RUNS) {
|
|
24965
|
+
this.backlogStreak = 0;
|
|
24966
|
+
try {
|
|
24967
|
+
this.deps.onUnclosedBacklog?.(execNow);
|
|
24968
|
+
} catch (err) {
|
|
24969
|
+
console.warn(`[proactive] backlog alert failed: ${err.message}`);
|
|
24970
|
+
}
|
|
24971
|
+
}
|
|
24824
24972
|
let due = this.deps.persist.dueActions(now.toISOString());
|
|
24825
24973
|
if (due.length === 0) return 0;
|
|
24826
24974
|
const resolvedActions = [];
|
|
@@ -24834,13 +24982,58 @@ var init_sweep = __esm({
|
|
|
24834
24982
|
});
|
|
24835
24983
|
if (resolvedActions.length > 0) {
|
|
24836
24984
|
for (const { id, intentStatus } of resolvedActions) {
|
|
24837
|
-
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
24985
|
+
this.deps.persist.updateActionStatus(id, "cancelled", {
|
|
24986
|
+
completedAt: now.toISOString(),
|
|
24987
|
+
cancelReason: `intent ${intentStatus}\u2014\u2014R2 sweep \u518D\u9A8C\u8BC1`
|
|
24988
|
+
});
|
|
24838
24989
|
}
|
|
24839
24990
|
console.log(`[proactive] R2: ${resolvedActions.length} due action(s) cancelled (intent resolved)`);
|
|
24840
24991
|
}
|
|
24992
|
+
const gatedItems = [];
|
|
24993
|
+
const gateOff = (a, reason) => {
|
|
24994
|
+
gatedItems.push({ id: a.id, title: a.title, reason });
|
|
24995
|
+
return false;
|
|
24996
|
+
};
|
|
24997
|
+
const coveredTitles = this.deps.coveredTitles ?? [];
|
|
24998
|
+
const coveredEvents = this.deps.coveredEvents ?? [];
|
|
24999
|
+
if (coveredTitles.length > 0 || coveredEvents.length > 0) {
|
|
25000
|
+
due = due.filter((a) => {
|
|
25001
|
+
if (coveredTitles.some((c) => isSameCommitment(a.title, c))) {
|
|
25002
|
+
return gateOff(a, "calendar/goal \u91CC\u5DF2\u7ECF\u6709\u540C\u4E00\u4EF6\u4E8B\u2014\u2014\u4E0D\u91CD\u590D\u63D0\u9192");
|
|
25003
|
+
}
|
|
25004
|
+
const parts = toCalendarWallParts(a.scheduledTime);
|
|
25005
|
+
const sameMoment = coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact);
|
|
25006
|
+
if (sameMoment && coveredEvents.some((ev) => ev.date === parts.dateStr && ev.time === parts.timeExact && sharesTerm(a.title, ev.title))) {
|
|
25007
|
+
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");
|
|
25008
|
+
}
|
|
25009
|
+
return true;
|
|
25010
|
+
});
|
|
25011
|
+
}
|
|
25012
|
+
const minLeadDays = this.deps.minLeadDays ?? 0;
|
|
25013
|
+
if (minLeadDays > 0) {
|
|
25014
|
+
due = due.filter((a) => {
|
|
25015
|
+
const leadMs = Date.parse(a.scheduledTime) - Date.parse(a.createdAt);
|
|
25016
|
+
if (leadMs >= minLeadDays * 864e5) return true;
|
|
25017
|
+
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`);
|
|
25018
|
+
});
|
|
25019
|
+
}
|
|
25020
|
+
for (const g of gatedItems) {
|
|
25021
|
+
this.deps.persist.updateActionStatus(g.id, "cancelled", {
|
|
25022
|
+
completedAt: now.toISOString(),
|
|
25023
|
+
cancelReason: g.reason
|
|
25024
|
+
});
|
|
25025
|
+
}
|
|
25026
|
+
if (gatedItems.length > 0) {
|
|
25027
|
+
console.log(`[proactive] gated ${gatedItems.length} action(s): ${gatedItems.map((g) => g.reason.slice(0, 12)).join(" / ")}`);
|
|
25028
|
+
try {
|
|
25029
|
+
this.deps.onGated?.(gatedItems);
|
|
25030
|
+
} catch (err) {
|
|
25031
|
+
console.warn(`[proactive] onGated failed: ${err.message}`);
|
|
25032
|
+
}
|
|
25033
|
+
}
|
|
24841
25034
|
if (due.length === 0) return 0;
|
|
24842
25035
|
for (const act of due) {
|
|
24843
|
-
this.deps.persist.markActionReminded(act.id);
|
|
25036
|
+
this.deps.persist.markActionReminded(act.id, now.toISOString());
|
|
24844
25037
|
}
|
|
24845
25038
|
const payload = due.map((a) => actionPayload(a, now, this.deps.persist.getIntentRecentEvents(a.intentId)));
|
|
24846
25039
|
const blocks = [
|
|
@@ -24851,7 +25044,12 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
24851
25044
|
\`\`\`` },
|
|
24852
25045
|
{ type: "text", text: this.loadPrompt() }
|
|
24853
25046
|
];
|
|
24854
|
-
this.deps.submit(blocks);
|
|
25047
|
+
const delivered = this.deps.submit(blocks);
|
|
25048
|
+
if (!delivered) {
|
|
25049
|
+
const back = this.deps.persist.rollbackReminded(due.map((a) => a.id));
|
|
25050
|
+
console.warn(`[proactive] injection dropped (no main session / guard busy) \u2014 rolled back ${back} action(s) to queued`);
|
|
25051
|
+
return 0;
|
|
25052
|
+
}
|
|
24855
25053
|
console.log(`[proactive] injected ${payload.length} due action(s) to main session`);
|
|
24856
25054
|
return payload.length;
|
|
24857
25055
|
}
|
|
@@ -24932,7 +25130,7 @@ function registerProactiveTools() {
|
|
|
24932
25130
|
completedAt: nowUtc(),
|
|
24933
25131
|
cancelReason: String(args.reason || "\u672A\u6CE8\u660E")
|
|
24934
25132
|
});
|
|
24935
|
-
return { content: `\u2713 action ${a.id} \
|
|
25133
|
+
return { content: `\u2713 action ${a.id} \u6807\u8BB0\u4E3A cancelled\uFF08${String(args.reason || "\u672A\u6CE8\u660E")}\uFF09` };
|
|
24936
25134
|
});
|
|
24937
25135
|
return out ?? { content: "proactive \u672A\u542F\u7528", isError: true };
|
|
24938
25136
|
}
|
|
@@ -25053,6 +25251,78 @@ var init_completion_evidence = __esm({
|
|
|
25053
25251
|
}
|
|
25054
25252
|
});
|
|
25055
25253
|
|
|
25254
|
+
// src/memory/proactive/verdict-parse.ts
|
|
25255
|
+
function parseVerdicts(text) {
|
|
25256
|
+
if (!text) return [];
|
|
25257
|
+
const hits = [];
|
|
25258
|
+
VERDICT_RE.lastIndex = 0;
|
|
25259
|
+
let m;
|
|
25260
|
+
while ((m = VERDICT_RE.exec(text)) !== null) {
|
|
25261
|
+
hits.push({
|
|
25262
|
+
id: m[1],
|
|
25263
|
+
status: m[2],
|
|
25264
|
+
start: m.index,
|
|
25265
|
+
end: m.index + m[0].length
|
|
25266
|
+
});
|
|
25267
|
+
}
|
|
25268
|
+
const byId = /* @__PURE__ */ new Map();
|
|
25269
|
+
for (let i = 0; i < hits.length; i++) {
|
|
25270
|
+
const h = hits[i];
|
|
25271
|
+
const segEnd = Math.min(hits[i + 1]?.start ?? text.length, h.end + SEGMENT_MAX);
|
|
25272
|
+
const seg = text.slice(h.end, segEnd);
|
|
25273
|
+
let reason = "";
|
|
25274
|
+
const inline = seg.match(INLINE_REASON_RE);
|
|
25275
|
+
if (inline) reason = inline[1] ?? inline[2] ?? inline[3] ?? "";
|
|
25276
|
+
if (!reason) {
|
|
25277
|
+
const r = seg.match(LABELED_REASON_RE);
|
|
25278
|
+
if (r) reason = (r[1] ?? r[2] ?? r[3] ?? r[4] ?? "").trim();
|
|
25279
|
+
}
|
|
25280
|
+
byId.set(h.id, { id: h.id, status: h.status, reason: reason.trim().slice(0, SEGMENT_MAX) });
|
|
25281
|
+
}
|
|
25282
|
+
return [...byId.values()];
|
|
25283
|
+
}
|
|
25284
|
+
function applyProseVerdicts(persist, text) {
|
|
25285
|
+
const verdicts = parseVerdicts(text);
|
|
25286
|
+
const skipped = [];
|
|
25287
|
+
let applied = 0;
|
|
25288
|
+
for (const v of verdicts) {
|
|
25289
|
+
const a = persist.getAction(v.id);
|
|
25290
|
+
if (!a) {
|
|
25291
|
+
skipped.push(`${v.id}(\u4E0D\u5B58\u5728)`);
|
|
25292
|
+
continue;
|
|
25293
|
+
}
|
|
25294
|
+
if (a.status !== "executing") {
|
|
25295
|
+
skipped.push(`${v.id}(${a.status})`);
|
|
25296
|
+
continue;
|
|
25297
|
+
}
|
|
25298
|
+
const at = nowUtc();
|
|
25299
|
+
if (v.status === "completed") {
|
|
25300
|
+
persist.updateActionStatus(v.id, "completed", {
|
|
25301
|
+
completedAt: at,
|
|
25302
|
+
...v.reason ? { note: v.reason } : {}
|
|
25303
|
+
});
|
|
25304
|
+
} else {
|
|
25305
|
+
persist.updateActionStatus(v.id, v.status, {
|
|
25306
|
+
completedAt: at,
|
|
25307
|
+
cancelReason: v.reason || "\u672A\u6CE8\u660E\uFF08\u6B63\u6587\u5224\u5B9A\uFF09"
|
|
25308
|
+
});
|
|
25309
|
+
}
|
|
25310
|
+
applied++;
|
|
25311
|
+
}
|
|
25312
|
+
return { applied, skipped };
|
|
25313
|
+
}
|
|
25314
|
+
var VERDICT_RE, LABELED_REASON_RE, INLINE_REASON_RE, SEGMENT_MAX;
|
|
25315
|
+
var init_verdict_parse = __esm({
|
|
25316
|
+
"src/memory/proactive/verdict-parse.ts"() {
|
|
25317
|
+
"use strict";
|
|
25318
|
+
init_clock();
|
|
25319
|
+
VERDICT_RE = /(act-[0-9a-f]{12})\s*[::]\s*(completed|cancelled|failed)\b/g;
|
|
25320
|
+
LABELED_REASON_RE = /(?:cancel_reason|note|reason)\s*[::]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”|([^\n]*))/i;
|
|
25321
|
+
INLINE_REASON_RE = /^\s*[,,]\s*(?:"([^"]*)"|'([^']*)'|“([^”]*)”)/;
|
|
25322
|
+
SEGMENT_MAX = 500;
|
|
25323
|
+
}
|
|
25324
|
+
});
|
|
25325
|
+
|
|
25056
25326
|
// src/memory/proactive/plugin.ts
|
|
25057
25327
|
var plugin_exports = {};
|
|
25058
25328
|
__export(plugin_exports, {
|
|
@@ -25060,7 +25330,7 @@ __export(plugin_exports, {
|
|
|
25060
25330
|
feedMem7Cell: () => feedMem7Cell
|
|
25061
25331
|
});
|
|
25062
25332
|
import { join as join41 } from "node:path";
|
|
25063
|
-
import { existsSync as existsSync27, readFileSync as readFileSync33 } from "node:fs";
|
|
25333
|
+
import { existsSync as existsSync27, readFileSync as readFileSync33, appendFileSync as appendFileSync2 } from "node:fs";
|
|
25064
25334
|
function isFiller(text) {
|
|
25065
25335
|
const t = text.trim();
|
|
25066
25336
|
if (t.length >= 5) return false;
|
|
@@ -25133,7 +25403,10 @@ var init_plugin = __esm({
|
|
|
25133
25403
|
init_proactive_tools();
|
|
25134
25404
|
init_prompts();
|
|
25135
25405
|
init_completion_evidence();
|
|
25406
|
+
init_verdict_parse();
|
|
25407
|
+
init_task_manager();
|
|
25136
25408
|
init_opportunity();
|
|
25409
|
+
init_clock();
|
|
25137
25410
|
init_embedding();
|
|
25138
25411
|
init_filters();
|
|
25139
25412
|
init_store2();
|
|
@@ -25295,22 +25568,76 @@ ${String(input.tool_output ?? "")}`
|
|
|
25295
25568
|
return { continue: true };
|
|
25296
25569
|
}
|
|
25297
25570
|
});
|
|
25571
|
+
registerCallbackHook("Stop", {
|
|
25572
|
+
type: "callback",
|
|
25573
|
+
id: "proactive-verdict-parse",
|
|
25574
|
+
callback: async (input) => {
|
|
25575
|
+
if (input?.channel !== "proactive") return { continue: true };
|
|
25576
|
+
try {
|
|
25577
|
+
const r = applyProseVerdicts(this.persist, String(input?.last_assistant_message ?? ""));
|
|
25578
|
+
if (r.applied > 0) {
|
|
25579
|
+
console.log(`[proactive] \u6B63\u6587\u5224\u5B9A\u56DE\u6536 ${r.applied} \u6761\uFF08\u8DF3\u8FC7 ${r.skipped.length}\uFF09`);
|
|
25580
|
+
}
|
|
25581
|
+
} catch (err) {
|
|
25582
|
+
console.warn(`[proactive] verdict parse failed: ${err.message}`);
|
|
25583
|
+
}
|
|
25584
|
+
return { continue: true };
|
|
25585
|
+
}
|
|
25586
|
+
});
|
|
25298
25587
|
this.hookInstalled = true;
|
|
25299
25588
|
activeInstance = this;
|
|
25300
25589
|
console.log(`[proactive] mem7-fed ingest ready (model=${this.config.modelRef}, graphInput=${liveConfig.get("mem7.graphInput") ?? "memcell"})`);
|
|
25301
25590
|
registerProactiveTools();
|
|
25302
25591
|
setProactiveWorkspace(workspace);
|
|
25592
|
+
const pluginCalDb = this.calDb;
|
|
25303
25593
|
this.sweep = new Sweep({
|
|
25304
25594
|
persist: this.persist,
|
|
25305
25595
|
promptFile: join41(workspace, "prompts", "proactive.md"),
|
|
25596
|
+
// 三个都走 getter **每轮现读** —— 热加载规则(config 改了立刻生效)+
|
|
25597
|
+
// calendar 会变,都不能在 start() 时快照。
|
|
25598
|
+
get minLeadDays() {
|
|
25599
|
+
return parseProactiveConfig(liveConfig.get("proactive")).leadGateDays;
|
|
25600
|
+
},
|
|
25601
|
+
get staleHours() {
|
|
25602
|
+
return parseProactiveConfig(liveConfig.get("proactive")).staleHours;
|
|
25603
|
+
},
|
|
25604
|
+
get coveredTitles() {
|
|
25605
|
+
return buildPlanningContext(pluginCalDb, workspace).coveredTitles;
|
|
25606
|
+
},
|
|
25607
|
+
// 带日期时刻的版本 —— 供给"时间锚点判重"(张小媒 0925 review 补的维度)
|
|
25608
|
+
get coveredEvents() {
|
|
25609
|
+
return pluginCalDb ? getCalendarCoverage(pluginCalDb).map((c) => ({ title: c.title, date: c.date, time: c.time })) : [];
|
|
25610
|
+
},
|
|
25611
|
+
// 闸掉的必须留痕:她要能"扫一眼标题,真有冤案我喊"。
|
|
25612
|
+
onGated: (items) => {
|
|
25613
|
+
const gatedPath = join41(workspace, ".proactive", "gated.jsonl");
|
|
25614
|
+
try {
|
|
25615
|
+
appendFileSync2(
|
|
25616
|
+
gatedPath,
|
|
25617
|
+
items.map((i) => JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...i })).join("\n") + "\n",
|
|
25618
|
+
"utf8"
|
|
25619
|
+
);
|
|
25620
|
+
} catch (err) {
|
|
25621
|
+
console.warn(`[proactive] gated.jsonl \u5199\u5165\u5931\u8D25: ${err.message}`);
|
|
25622
|
+
}
|
|
25623
|
+
enqueueNotification(
|
|
25624
|
+
buildNudgeNotification(
|
|
25625
|
+
"stuck",
|
|
25626
|
+
`proactive \u4ECA\u5929\u95F8\u6389\u4E86 ${items.length} \u6761\uFF08\u6CA1\u8FDB\u6CE8\u5165\u3001\u5DF2\u6807 cancelled\uFF09\uFF1A` + items.map((i) => `
|
|
25627
|
+
\xB7 ${i.title.slice(0, 28)} \u2190 ${i.reason.slice(0, 22)}`).join("") + `
|
|
25628
|
+
\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`
|
|
25629
|
+
),
|
|
25630
|
+
void 0,
|
|
25631
|
+
`proactive-gated-${todayKey()}`
|
|
25632
|
+
);
|
|
25633
|
+
},
|
|
25306
25634
|
submit: (blocks) => {
|
|
25307
25635
|
const mainSessionId = ctx.sessions?.getSessionId?.("scope:main");
|
|
25308
25636
|
if (!mainSessionId) {
|
|
25309
|
-
console.warn("[proactive] no main session \u2014
|
|
25310
|
-
return;
|
|
25637
|
+
console.warn("[proactive] no main session \u2014 sweep injection dropped");
|
|
25638
|
+
return false;
|
|
25311
25639
|
}
|
|
25312
|
-
|
|
25313
|
-
ctx.dispatcher.submitMessage({
|
|
25640
|
+
const ok = ctx.dispatcher.submitMessage({
|
|
25314
25641
|
text: blocks,
|
|
25315
25642
|
sessionId: mainSessionId,
|
|
25316
25643
|
channelName: "proactive",
|
|
@@ -25319,6 +25646,21 @@ ${String(input.tool_output ?? "")}`
|
|
|
25319
25646
|
skipRecall: true,
|
|
25320
25647
|
deps: ctx.deps
|
|
25321
25648
|
});
|
|
25649
|
+
if (ok === false) console.warn("[proactive] dispatcher rejected sweep injection (guard busy) \u2014 rolling back");
|
|
25650
|
+
return ok !== false;
|
|
25651
|
+
},
|
|
25652
|
+
// 对账告警:注入的判定连续多轮不闭环 → 直接告诉她。她本人明确要过这条
|
|
25653
|
+
//("能发现只增不减的人是我,但前提是我收到这个信号")——躺日志里等于没有。
|
|
25654
|
+
onUnclosedBacklog: (count) => {
|
|
25655
|
+
enqueueNotification(
|
|
25656
|
+
buildNudgeNotification(
|
|
25657
|
+
"stuck",
|
|
25658
|
+
`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`
|
|
25659
|
+
),
|
|
25660
|
+
void 0,
|
|
25661
|
+
"proactive-unclosed-backlog"
|
|
25662
|
+
);
|
|
25663
|
+
console.warn(`[proactive] \u5BF9\u8D26\u544A\u8B66\u5DF2\u6CE8\u5165\uFF1Aexecuting=${count} \u8FDE\u7EED ${BACKLOG_ALERT_RUNS} \u8F6E\u53EA\u589E\u4E0D\u51CF`);
|
|
25322
25664
|
}
|
|
25323
25665
|
});
|
|
25324
25666
|
this.sweep.start();
|
|
@@ -25344,8 +25686,12 @@ ${String(input.tool_output ?? "")}`
|
|
|
25344
25686
|
const nowDate = /* @__PURE__ */ new Date();
|
|
25345
25687
|
const stateDir = String(liveConfig.get("stateDir") ?? "");
|
|
25346
25688
|
if (stateDir) {
|
|
25347
|
-
const
|
|
25348
|
-
|
|
25689
|
+
const all = loadForesightEntries(join41(stateDir, "mem7"), nowDate);
|
|
25690
|
+
const events = all.filter((e) => e.kind !== "profile");
|
|
25691
|
+
const mem7Entries = capForesightEntries(events, todayKey(nowDate));
|
|
25692
|
+
if (all.length > 0) {
|
|
25693
|
+
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`);
|
|
25694
|
+
}
|
|
25349
25695
|
for (const e of mem7Entries) {
|
|
25350
25696
|
const key = e.text.replace(/\s+/g, "");
|
|
25351
25697
|
if (seen.has(key)) continue;
|
|
@@ -25353,7 +25699,6 @@ ${String(input.tool_output ?? "")}`
|
|
|
25353
25699
|
foresightLines.push(`[mem7] [\u7A97\u53E3 ${e.start}~${e.end}] ${e.text}${e.evidence ? `\uFF08\u4F9D\u636E\uFF1A${e.evidence}\uFF09` : ""}`);
|
|
25354
25700
|
}
|
|
25355
25701
|
}
|
|
25356
|
-
if (foresightLines.length > 0) console.log(`[proactive] mem7 foresight total: ${foresightLines.length}`);
|
|
25357
25702
|
} catch (err) {
|
|
25358
25703
|
console.warn(`[proactive] foresight load failed: ${err.message}`);
|
|
25359
25704
|
}
|
|
@@ -25377,6 +25722,7 @@ ${String(input.tool_output ?? "")}`
|
|
|
25377
25722
|
this.hookInstalled = false;
|
|
25378
25723
|
if (activeInstance === this) activeInstance = null;
|
|
25379
25724
|
unregisterCallbackHook("PostToolUse", "proactive-completion-evidence");
|
|
25725
|
+
unregisterCallbackHook("Stop", "proactive-verdict-parse");
|
|
25380
25726
|
await this.waitForIdle();
|
|
25381
25727
|
this.sweep?.stop();
|
|
25382
25728
|
this.sweep = null;
|
|
@@ -29676,6 +30022,15 @@ function buildDirTree(v) {
|
|
|
29676
30022
|
content: readTemplate("workspace/prompts/contacts.md").replace("{{DISCORD_USER_ID}}", v.discordUserId || "YOUR_DISCORD_ID").replace("{{FEISHU_OPEN_ID}}", v.feishuOpenId || "YOUR_FEISHU_OPEN_ID")
|
|
29677
30023
|
},
|
|
29678
30024
|
{ path: path34.join(w, "prompts", "auto-memory-instructions.md"), type: "file", content: readTemplate("workspace/prompts/auto-memory-instructions.md") },
|
|
30025
|
+
{
|
|
30026
|
+
// 0925:proactive 到期处理规则。此前仓内**零模板**——那份规则只在私有部署的
|
|
30027
|
+
// stateDir 里手工存在,装机用户吃的是引擎内置兜底文案(那份也没点名工具)。
|
|
30028
|
+
// 结果:agent 用正文写判定、不调工具,判定全部不落库。
|
|
30029
|
+
// 注意 readTemplate 找不到模板会 **throw**,所以本文件与 templates/ 下那份必须成对存在。
|
|
30030
|
+
path: path34.join(w, "prompts", "proactive.md"),
|
|
30031
|
+
type: "file",
|
|
30032
|
+
content: readTemplate("workspace/prompts/proactive.md")
|
|
30033
|
+
},
|
|
29679
30034
|
{
|
|
29680
30035
|
// 0917:inner-voice 定制 prompt 按人设装(templates/workspace/inner-voice/<persona>.md)
|
|
29681
30036
|
path: path34.join(w, "prompts", "my-inner-voice.md"),
|