@kanadego/dsh-heartbeat 1.9.0 → 1.9.1
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/README.md +33 -68
- package/assets/frontwin.ps1 +1 -0
- package/assets/idle.ps1 +1 -0
- package/assets/screenpulse.ps1 +4 -2
- package/config/profile-schema.json +306 -306
- package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
- package/dist/{chunk-IPOZFZPY.js → chunk-2NB4QILF.js} +48 -10
- package/dist/chunk-2NB4QILF.js.map +1 -0
- package/dist/{chunk-IFTFDHZX.js → chunk-7TW6DD6Q.js} +11 -6
- package/dist/chunk-7TW6DD6Q.js.map +1 -0
- package/dist/{chunk-63FUJLIF.js → chunk-BQCXYE7N.js} +17 -2
- package/dist/chunk-BQCXYE7N.js.map +1 -0
- package/dist/chunk-HT57FOEF.js +2454 -0
- package/dist/chunk-HT57FOEF.js.map +1 -0
- package/dist/chunk-IV2ZWQA3.js +56 -0
- package/dist/chunk-IV2ZWQA3.js.map +1 -0
- package/dist/{chunk-PQTSXW4F.js → chunk-KOOQOMQY.js} +97 -71
- package/dist/chunk-KOOQOMQY.js.map +1 -0
- package/dist/{chunk-VIZNIQLK.js → chunk-OYVNWB5G.js} +82 -74
- package/dist/chunk-OYVNWB5G.js.map +1 -0
- package/dist/{chunk-RB3NLLGF.js → chunk-QOQ7EPZZ.js} +3 -3
- package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
- package/dist/chunk-TY6UP74X.js.map +1 -0
- package/dist/chunk-WRJCINZ7.js +569 -0
- package/dist/chunk-WRJCINZ7.js.map +1 -0
- package/dist/chunk-WRUTATW4.js +47 -0
- package/dist/chunk-WRUTATW4.js.map +1 -0
- package/dist/chunk-ZLV4NMFX.js +333 -0
- package/dist/chunk-ZLV4NMFX.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +565 -497
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-57NHQHY3.js → inbox-JLT5G7TL.js} +5 -3
- package/dist/index.d.ts +517 -0
- package/dist/index.js +570 -2719
- package/dist/index.js.map +1 -1
- package/dist/{migrate-N6NXMZ3G.js → migrate-WDVFHUP7.js} +4 -3
- package/dist/orchestrator-2ZLU7RGE.js +60 -0
- package/dist/{pool-7I4VACG2.js → pool-B34NTH6D.js} +5 -3
- package/dist/{snapshot-ZBCSX67Y.js → snapshot-JQDJ346Z.js} +5 -4
- package/dist/snapshot-JQDJ346Z.js.map +1 -0
- package/package.json +10 -3
- package/config/watchlist.json +0 -9
- package/dist/chunk-2SYXKKZF.js +0 -837
- package/dist/chunk-2SYXKKZF.js.map +0 -1
- package/dist/chunk-63FUJLIF.js.map +0 -1
- package/dist/chunk-IFTFDHZX.js.map +0 -1
- package/dist/chunk-IPOZFZPY.js.map +0 -1
- package/dist/chunk-J6ZTRFFW.js.map +0 -1
- package/dist/chunk-PQTSXW4F.js.map +0 -1
- package/dist/chunk-VIZNIQLK.js.map +0 -1
- /package/dist/{bindings-XPPSKILN.js.map → bindings-225SI7KC.js.map} +0 -0
- /package/dist/{chunk-RB3NLLGF.js.map → chunk-QOQ7EPZZ.js.map} +0 -0
- /package/dist/{inbox-57NHQHY3.js.map → inbox-JLT5G7TL.js.map} +0 -0
- /package/dist/{migrate-N6NXMZ3G.js.map → migrate-WDVFHUP7.js.map} +0 -0
- /package/dist/{pool-7I4VACG2.js.map → orchestrator-2ZLU7RGE.js.map} +0 -0
- /package/dist/{snapshot-ZBCSX67Y.js.map → pool-B34NTH6D.js.map} +0 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import {
|
|
2
|
+
activeSeeds,
|
|
3
|
+
loadPool,
|
|
4
|
+
normalizeCategory,
|
|
5
|
+
seedsFilePath
|
|
6
|
+
} from "./chunk-KOOQOMQY.js";
|
|
7
|
+
import {
|
|
8
|
+
appendAuditLine
|
|
9
|
+
} from "./chunk-OYVNWB5G.js";
|
|
10
|
+
import {
|
|
11
|
+
loadEncryptedText,
|
|
12
|
+
loadJson,
|
|
13
|
+
readText,
|
|
14
|
+
saveEncryptedText,
|
|
15
|
+
saveJson,
|
|
16
|
+
workspace,
|
|
17
|
+
writeText
|
|
18
|
+
} from "./chunk-7TW6DD6Q.js";
|
|
19
|
+
import {
|
|
20
|
+
atomicWriteJsonSync
|
|
21
|
+
} from "./chunk-WRUTATW4.js";
|
|
22
|
+
|
|
23
|
+
// src/ledger/ledger.ts
|
|
24
|
+
import path from "path";
|
|
25
|
+
import { randomUUID } from "crypto";
|
|
26
|
+
var DAY_MS = 864e5;
|
|
27
|
+
function ledgerFilePath(dataDir) {
|
|
28
|
+
return path.join(dataDir, "ledger.md");
|
|
29
|
+
}
|
|
30
|
+
var LINE_RE = /^- \[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\]\[(open|done)\]\[#([0-9a-f]{6})\] (.*)$/;
|
|
31
|
+
function renderEntry(e) {
|
|
32
|
+
return `- [${e.date} ${e.time}][${e.status}][#${e.id}] ${e.text}`;
|
|
33
|
+
}
|
|
34
|
+
function readLedger(guard, file) {
|
|
35
|
+
const raw = readText(guard, file, "# \u8D26\u672C\n");
|
|
36
|
+
const lines = raw.split("\n");
|
|
37
|
+
const entries = [];
|
|
38
|
+
const rawLines = [];
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const m = LINE_RE.exec(line);
|
|
41
|
+
if (m) {
|
|
42
|
+
entries.push({ date: m[1], time: m[2], status: m[3], id: m[4], text: m[5] });
|
|
43
|
+
}
|
|
44
|
+
rawLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
return { header: lines[0] ?? "# \u8D26\u672C", entries, rawLines };
|
|
47
|
+
}
|
|
48
|
+
function appendEntry(guard, file, text, now = Date.now()) {
|
|
49
|
+
const textTrimmed = text.trim();
|
|
50
|
+
if (!textTrimmed) throw new Error("ledger entry must not be empty");
|
|
51
|
+
const d = new Date(now);
|
|
52
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
53
|
+
const entry = {
|
|
54
|
+
id: randomUUID().slice(0, 6),
|
|
55
|
+
date: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`,
|
|
56
|
+
time: `${pad(d.getHours())}:${pad(d.getMinutes())}`,
|
|
57
|
+
status: "open",
|
|
58
|
+
text: textTrimmed.replace(/\r?\n/g, " ")
|
|
59
|
+
};
|
|
60
|
+
const { rawLines } = readLedger(guard, file);
|
|
61
|
+
rawLines.push(renderEntry(entry));
|
|
62
|
+
writeText(guard, file, rawLines.join("\n").replace(/\n*$/, "\n"));
|
|
63
|
+
return entry;
|
|
64
|
+
}
|
|
65
|
+
function markDone(guard, file, key, now = Date.now()) {
|
|
66
|
+
const { rawLines, entries } = readLedger(guard, file);
|
|
67
|
+
const target = entries.find((e) => e.status === "open" && (e.id === key || e.text.includes(key)));
|
|
68
|
+
if (!target) return null;
|
|
69
|
+
const d = new Date(now);
|
|
70
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
71
|
+
const doneDate = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
72
|
+
const out = rawLines.map((line) => {
|
|
73
|
+
if (line.includes(`#${target.id}] `)) {
|
|
74
|
+
return `- [${doneDate} ${pad(d.getHours())}:${pad(d.getMinutes())}][done][#${target.id}] ${target.text}`;
|
|
75
|
+
}
|
|
76
|
+
return line;
|
|
77
|
+
});
|
|
78
|
+
writeText(guard, file, out.join("\n").replace(/\n*$/, "\n"));
|
|
79
|
+
return target;
|
|
80
|
+
}
|
|
81
|
+
function scanPending(guard, file, now = Date.now()) {
|
|
82
|
+
const { entries } = readLedger(guard, file);
|
|
83
|
+
return entries.filter((e) => e.status === "open").sort((a, b) => a.date < b.date ? -1 : 1);
|
|
84
|
+
}
|
|
85
|
+
function pendingOlderThan(guard, file, days, now = Date.now()) {
|
|
86
|
+
const cutoff = new Date(now - days * DAY_MS).toISOString().slice(0, 10);
|
|
87
|
+
return scanPending(guard, file, now).filter((e) => e.date <= cutoff);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/browse/preference.ts
|
|
91
|
+
import path2 from "path";
|
|
92
|
+
var DAY_MS2 = 864e5;
|
|
93
|
+
var W_MIN = 0.5;
|
|
94
|
+
var W_MAX = 2;
|
|
95
|
+
var SMOOTH_ADOPTED = 1;
|
|
96
|
+
var SMOOTH_DELIVERED = 4;
|
|
97
|
+
var DECAY_HALF_LIFE_DAYS = 30;
|
|
98
|
+
function preferenceFilePath(dataDir) {
|
|
99
|
+
return path2.join(dataDir, "preference.json");
|
|
100
|
+
}
|
|
101
|
+
function num(v) {
|
|
102
|
+
const n = Number(v);
|
|
103
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
104
|
+
}
|
|
105
|
+
function sanitizeTopicPref(raw) {
|
|
106
|
+
if (!raw || typeof raw !== "object") return null;
|
|
107
|
+
const t = raw;
|
|
108
|
+
if (!Number.isFinite(Number(t.delivered)) || !Number.isFinite(Number(t.adopted))) return null;
|
|
109
|
+
const delivered = Math.floor(num(t.delivered));
|
|
110
|
+
return {
|
|
111
|
+
delivered,
|
|
112
|
+
adopted: Math.min(delivered, Math.floor(num(t.adopted))),
|
|
113
|
+
lastDeliveredAt: num(t.lastDeliveredAt),
|
|
114
|
+
lastAdoptedAt: num(t.lastAdoptedAt)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function loadPreference(guard, file) {
|
|
118
|
+
const doc = loadJson(guard, file);
|
|
119
|
+
if (!doc || typeof doc !== "object" || !doc.topics || typeof doc.topics !== "object") {
|
|
120
|
+
return { version: 1, topics: {}, updatedAt: 0 };
|
|
121
|
+
}
|
|
122
|
+
const topics = {};
|
|
123
|
+
for (const [key, raw] of Object.entries(doc.topics)) {
|
|
124
|
+
const t = sanitizeTopicPref(raw);
|
|
125
|
+
if (key && t) topics[key] = t;
|
|
126
|
+
}
|
|
127
|
+
const updatedAt = Number(doc.updatedAt);
|
|
128
|
+
return { version: 1, topics, updatedAt: Number.isFinite(updatedAt) ? updatedAt : 0 };
|
|
129
|
+
}
|
|
130
|
+
function savePreference(guard, file, state) {
|
|
131
|
+
saveJson(guard, file, state);
|
|
132
|
+
}
|
|
133
|
+
function recordDelivery(guard, file, opts) {
|
|
134
|
+
const state = loadPreference(guard, file);
|
|
135
|
+
const adopted = new Set(opts.adoptedTopics);
|
|
136
|
+
for (const topic of opts.offeredTopics) {
|
|
137
|
+
if (!topic) continue;
|
|
138
|
+
const t = state.topics[topic] ?? { delivered: 0, adopted: 0, lastDeliveredAt: 0, lastAdoptedAt: 0 };
|
|
139
|
+
t.delivered += 1;
|
|
140
|
+
t.lastDeliveredAt = opts.now;
|
|
141
|
+
if (adopted.has(topic)) {
|
|
142
|
+
t.adopted += 1;
|
|
143
|
+
t.lastAdoptedAt = opts.now;
|
|
144
|
+
}
|
|
145
|
+
state.topics[topic] = t;
|
|
146
|
+
}
|
|
147
|
+
state.updatedAt = opts.now;
|
|
148
|
+
savePreference(guard, file, state);
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
function decayed(t, now) {
|
|
152
|
+
const dAgeDays = Math.max(0, now - t.lastDeliveredAt) / DAY_MS2;
|
|
153
|
+
const aAgeDays = Math.max(0, now - t.lastAdoptedAt) / DAY_MS2;
|
|
154
|
+
const dHalf = Math.pow(0.5, dAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
155
|
+
const aHalf = Math.pow(0.5, aAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
156
|
+
return { delivered: t.delivered * dHalf, adopted: t.adopted * aHalf };
|
|
157
|
+
}
|
|
158
|
+
function preferenceWeight(state, topic, now) {
|
|
159
|
+
const t = state.topics[topic];
|
|
160
|
+
const rate = t ? (() => {
|
|
161
|
+
const d = decayed(t, now);
|
|
162
|
+
return (d.adopted + SMOOTH_ADOPTED) / (d.delivered + SMOOTH_DELIVERED);
|
|
163
|
+
})() : SMOOTH_ADOPTED / SMOOTH_DELIVERED;
|
|
164
|
+
return Math.min(W_MAX, Math.max(W_MIN, rate / (SMOOTH_ADOPTED / SMOOTH_DELIVERED)));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/browse/browse.ts
|
|
168
|
+
import fs from "fs";
|
|
169
|
+
import path3 from "path";
|
|
170
|
+
|
|
171
|
+
// src/core/time-window.ts
|
|
172
|
+
var HH_MM = /^(\d{1,2}):(\d{2})$/;
|
|
173
|
+
function parseHhMm(text) {
|
|
174
|
+
const m = HH_MM.exec(String(text).trim());
|
|
175
|
+
if (!m) return null;
|
|
176
|
+
const h = Number(m[1]);
|
|
177
|
+
const min = Number(m[2]);
|
|
178
|
+
if (!Number.isFinite(h) || !Number.isFinite(min) || h > 23 || min > 59) return null;
|
|
179
|
+
return h * 60 + min;
|
|
180
|
+
}
|
|
181
|
+
function minutesOfDay(now) {
|
|
182
|
+
return now.getHours() * 60 + now.getMinutes();
|
|
183
|
+
}
|
|
184
|
+
function inHhMmWindow(start, end, mins) {
|
|
185
|
+
const s = parseHhMm(start);
|
|
186
|
+
const e = parseHhMm(end);
|
|
187
|
+
if (s === null || e === null) return false;
|
|
188
|
+
if (s === e) return false;
|
|
189
|
+
return s > e ? mins >= s || mins < e : mins >= s && mins < e;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/browse/browse.ts
|
|
193
|
+
function emptyBrowseState() {
|
|
194
|
+
return { wander: { focusHistory: {}, focusCount: {}, last_wander_at: 0, refillCount: {}, sessionCount: {} } };
|
|
195
|
+
}
|
|
196
|
+
function browseStatePath(paths) {
|
|
197
|
+
return path3.join(paths.dataDir, "browse.json");
|
|
198
|
+
}
|
|
199
|
+
function readJsonFile(file, fallback) {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
202
|
+
} catch {
|
|
203
|
+
return fallback;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function loadInterests(paths) {
|
|
207
|
+
const userPath = path3.join(paths.settingsDir, "interests.json");
|
|
208
|
+
if (fs.existsSync(userPath)) return readJsonFile(userPath, { interests: [], _schedule: {} });
|
|
209
|
+
return readJsonFile(path3.join(paths.configDir, "interests.json"), { interests: [], _schedule: {} });
|
|
210
|
+
}
|
|
211
|
+
function normalizeBrowseState(raw) {
|
|
212
|
+
const out = emptyBrowseState();
|
|
213
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
214
|
+
const r = raw;
|
|
215
|
+
const w = r.wander && typeof r.wander === "object" ? r.wander : {};
|
|
216
|
+
out.wander.focusHistory = numberRecord(w.focusHistory);
|
|
217
|
+
out.wander.focusCount = numberRecord(w.focusCount);
|
|
218
|
+
out.wander.refillCount = numberRecord(w.refillCount);
|
|
219
|
+
out.wander.sessionCount = numberRecord(w.sessionCount);
|
|
220
|
+
if (typeof w.last_wander_at === "number" && Number.isFinite(w.last_wander_at)) out.wander.last_wander_at = w.last_wander_at;
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
function numberRecord(v) {
|
|
224
|
+
const out = {};
|
|
225
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return out;
|
|
226
|
+
for (const [k, n] of Object.entries(v)) {
|
|
227
|
+
if (typeof n === "number" && Number.isFinite(n)) out[k] = n;
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
function loadState(guard, paths) {
|
|
232
|
+
return normalizeBrowseState(loadJson(guard, browseStatePath(paths)));
|
|
233
|
+
}
|
|
234
|
+
function resolveSchedule(interests, policy) {
|
|
235
|
+
const sc = interests._schedule ?? {};
|
|
236
|
+
return {
|
|
237
|
+
windows: sc.windows?.length ? sc.windows : policy.browse.windows,
|
|
238
|
+
minIntervalHours: sc.min_interval_hours ?? policy.browse.minIntervalHours,
|
|
239
|
+
maxSeedsPerFocus: sc.max_seeds_per_focus ?? policy.browse.maxSeedsPerVisit,
|
|
240
|
+
dailySessions: sc.daily_sessions ?? 0,
|
|
241
|
+
focusPerSession: Math.max(1, sc.focus_per_session ?? 1),
|
|
242
|
+
focusCooldownDays: sc.focus_cooldown_days ?? 3
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function inWanderWindow(now, windows) {
|
|
246
|
+
const mins = minutesOfDay(now);
|
|
247
|
+
for (const w of windows) {
|
|
248
|
+
if (inHhMmWindow(w.start, w.end, mins)) return `${w.start}-${w.end}`;
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
function onCooldown(state, focus, cooldownDays, now) {
|
|
253
|
+
const last = state.wander.focusHistory[focus] ?? 0;
|
|
254
|
+
return last > now - cooldownDays * 864e5;
|
|
255
|
+
}
|
|
256
|
+
var STARVATION_DAYS = 14;
|
|
257
|
+
function pickFocus(state, interests, now, pref) {
|
|
258
|
+
const sc = interests._schedule ?? {};
|
|
259
|
+
const cooldown = sc.focus_cooldown_days ?? 3;
|
|
260
|
+
const pool = (interests.interests ?? []).filter((t) => !onCooldown(state, t, cooldown, now));
|
|
261
|
+
if (pool.length === 0) return null;
|
|
262
|
+
if (!pref) {
|
|
263
|
+
pool.sort((a, b) => (state.wander.focusHistory[a] ?? 0) - (state.wander.focusHistory[b] ?? 0));
|
|
264
|
+
return pool[0];
|
|
265
|
+
}
|
|
266
|
+
const age = (t) => now - (state.wander.focusHistory[t] ?? 0);
|
|
267
|
+
const starved = pool.filter((t) => age(t) >= STARVATION_DAYS * 864e5);
|
|
268
|
+
if (starved.length > 0) {
|
|
269
|
+
starved.sort((a, b) => age(b) - age(a));
|
|
270
|
+
return starved[0];
|
|
271
|
+
}
|
|
272
|
+
const effAge = (t) => age(t) * preferenceWeight(pref, t, now);
|
|
273
|
+
pool.sort((a, b) => effAge(b) - effAge(a));
|
|
274
|
+
return pool[0];
|
|
275
|
+
}
|
|
276
|
+
function adviseWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
277
|
+
const state = loadState(guard, paths);
|
|
278
|
+
const interests = loadInterests(paths);
|
|
279
|
+
const sched = resolveSchedule(interests, policy);
|
|
280
|
+
const budget = { maxSeeds: sched.maxSeedsPerFocus };
|
|
281
|
+
const win = inWanderWindow(now, sched.windows);
|
|
282
|
+
if (!win) {
|
|
283
|
+
const hh = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
284
|
+
return { focus: null, query: null, skipped: `window(now=${hh})`, ...budget };
|
|
285
|
+
}
|
|
286
|
+
const today = localDayKey(now);
|
|
287
|
+
const sessionsToday = state.wander.sessionCount?.[today] ?? 0;
|
|
288
|
+
if (sched.dailySessions > 0 && sessionsToday >= sched.dailySessions) {
|
|
289
|
+
return { focus: null, query: null, skipped: `daily-sessions(${sessionsToday})`, ...budget };
|
|
290
|
+
}
|
|
291
|
+
const minGap = sched.minIntervalHours * 36e5;
|
|
292
|
+
if (now.getTime() - state.wander.last_wander_at < minGap) {
|
|
293
|
+
return { focus: null, query: null, skipped: "min-interval", ...budget };
|
|
294
|
+
}
|
|
295
|
+
const pref = loadPreference(guard, preferenceFilePath(paths.dataDir));
|
|
296
|
+
const pool = interests.interests ?? [];
|
|
297
|
+
const candidates = [];
|
|
298
|
+
for (let i = 0; i < sched.focusPerSession; i++) {
|
|
299
|
+
const rest = pool.filter((t) => !candidates.includes(t));
|
|
300
|
+
if (rest.length === 0) break;
|
|
301
|
+
const picked = pickFocus(state, { ...interests, interests: rest }, now.getTime(), pref);
|
|
302
|
+
if (!picked) break;
|
|
303
|
+
candidates.push(picked);
|
|
304
|
+
}
|
|
305
|
+
if (candidates.length === 0) return { focus: null, query: null, skipped: "no-focus", ...budget };
|
|
306
|
+
return { focus: candidates[0], query: `${candidates[0]} 2026 \u6700\u65B0`, skipped: null, candidates, ...budget };
|
|
307
|
+
}
|
|
308
|
+
function completeWander(guard, paths, focus, now = Date.now(), opts = {}) {
|
|
309
|
+
const state = loadState(guard, paths);
|
|
310
|
+
state.wander.focusHistory[focus] = now;
|
|
311
|
+
state.wander.focusCount[focus] = (state.wander.focusCount[focus] ?? 0) + 1;
|
|
312
|
+
state.wander.last_wander_at = now;
|
|
313
|
+
if (!opts.refill) {
|
|
314
|
+
const day = localDayKey(new Date(now));
|
|
315
|
+
state.wander.sessionCount = state.wander.sessionCount ?? {};
|
|
316
|
+
state.wander.sessionCount[day] = (state.wander.sessionCount[day] ?? 0) + 1;
|
|
317
|
+
}
|
|
318
|
+
let refillsToday;
|
|
319
|
+
if (opts.refill) {
|
|
320
|
+
const today = localDayKey(new Date(now));
|
|
321
|
+
state.wander.refillCount = state.wander.refillCount ?? {};
|
|
322
|
+
state.wander.refillCount[today] = (state.wander.refillCount[today] ?? 0) + 1;
|
|
323
|
+
refillsToday = state.wander.refillCount[today];
|
|
324
|
+
}
|
|
325
|
+
saveJson(guard, browseStatePath(paths), state);
|
|
326
|
+
return { focus, count: state.wander.focusCount[focus], ...refillsToday === void 0 ? {} : { refillsToday } };
|
|
327
|
+
}
|
|
328
|
+
function localDayKey(d) {
|
|
329
|
+
const y = d.getFullYear();
|
|
330
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
331
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
332
|
+
return `${y}-${m}-${day}`;
|
|
333
|
+
}
|
|
334
|
+
var REFILL_TOPIC_THRESHOLD = 4;
|
|
335
|
+
var REFILL_MAX_PER_DAY = 2;
|
|
336
|
+
function adviseRefillWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
337
|
+
const state = loadState(guard, paths);
|
|
338
|
+
const today = localDayKey(now);
|
|
339
|
+
const refillsToday = state.wander.refillCount?.[today] ?? 0;
|
|
340
|
+
const topicCount = activeSeeds(loadPool(guard, seedsFilePath(paths.dataDir))).filter((s) => normalizeCategory(s.category) === "topic").length;
|
|
341
|
+
if (refillsToday >= REFILL_MAX_PER_DAY) {
|
|
342
|
+
return { focus: null, query: null, skipped: `refill-daily-cap(${refillsToday})`, topicCount, refillsToday };
|
|
343
|
+
}
|
|
344
|
+
if (topicCount > REFILL_TOPIC_THRESHOLD) {
|
|
345
|
+
return { focus: null, query: null, skipped: `topic-stock-ok(${topicCount})`, topicCount, refillsToday };
|
|
346
|
+
}
|
|
347
|
+
const interests = loadInterests(paths);
|
|
348
|
+
const focus = pickFocus(state, interests, now.getTime(), loadPreference(guard, preferenceFilePath(paths.dataDir)));
|
|
349
|
+
if (!focus) {
|
|
350
|
+
return { focus: null, query: null, skipped: "no-focus", topicCount, refillsToday };
|
|
351
|
+
}
|
|
352
|
+
return { focus, query: `${focus} 2026 \u6700\u65B0`, skipped: null, topicCount, refillsToday };
|
|
353
|
+
}
|
|
354
|
+
function browseStatus(guard, paths) {
|
|
355
|
+
return loadState(guard, paths);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/notify/notify.ts
|
|
359
|
+
import path4 from "path";
|
|
360
|
+
|
|
361
|
+
// src/core/ps.ts
|
|
362
|
+
import { execFile } from "child_process";
|
|
363
|
+
function runPowerShell(args, timeoutMs) {
|
|
364
|
+
return new Promise((resolve) => {
|
|
365
|
+
const child = execFile(
|
|
366
|
+
"powershell.exe",
|
|
367
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", ...args],
|
|
368
|
+
{ timeout: timeoutMs, encoding: "utf8", windowsHide: true },
|
|
369
|
+
(err, stdout, stderr) => {
|
|
370
|
+
const code = err?.code;
|
|
371
|
+
const status = err ? typeof code === "number" ? code : 1 : 0;
|
|
372
|
+
resolve({ status, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
child.on("error", () => {
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function runPowerShellFile(script, args, timeoutMs) {
|
|
380
|
+
return runPowerShell(["-File", script, ...args], timeoutMs);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/notify/notify.ts
|
|
384
|
+
async function runNotify(paths, args) {
|
|
385
|
+
const r = await runPowerShellFile(path4.join(paths.assetsDir, "notify.ps1"), args, 2e4);
|
|
386
|
+
return { status: r.status, out: `${r.stdout}${r.stderr}`.trim() };
|
|
387
|
+
}
|
|
388
|
+
async function ensureRegistered(paths) {
|
|
389
|
+
const check = await runNotify(paths, ["-Check"]);
|
|
390
|
+
if (/REGISTERED: yes/.test(check.out)) return true;
|
|
391
|
+
const reg = await runNotify(paths, ["-RegisterOnly"]);
|
|
392
|
+
return reg.status === 0;
|
|
393
|
+
}
|
|
394
|
+
async function sendNewMessageHint(paths) {
|
|
395
|
+
const r = await runNotify(paths, ["-Title", "Heartbeat", "-Message", "\u6709\u65B0\u6D88\u606F"]);
|
|
396
|
+
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
397
|
+
}
|
|
398
|
+
async function sendWeeklyReadyHint(paths) {
|
|
399
|
+
const r = await runNotify(paths, ["-Title", "\u5FC3\u8DF3\u5468\u62A5", "-Message", "\u672C\u671F\u5468\u62A5\u5DF2\u751F\u6210"]);
|
|
400
|
+
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/weekly/report.ts
|
|
404
|
+
import fs2 from "fs";
|
|
405
|
+
import path5 from "path";
|
|
406
|
+
var WEEKLY_INTERVAL_MS = 7 * 864e5;
|
|
407
|
+
function weeklyDirPath(dataDir) {
|
|
408
|
+
return path5.join(dataDir, "weekly");
|
|
409
|
+
}
|
|
410
|
+
function stateFilePath(dataDir) {
|
|
411
|
+
return path5.join(weeklyDirPath(dataDir), "state.json");
|
|
412
|
+
}
|
|
413
|
+
function reportFileName(endIso) {
|
|
414
|
+
return `report-${endIso.slice(0, 10)}.json`;
|
|
415
|
+
}
|
|
416
|
+
function lastWeeklyGeneratedAt(guard, dataDir) {
|
|
417
|
+
const file = stateFilePath(dataDir);
|
|
418
|
+
let raw;
|
|
419
|
+
try {
|
|
420
|
+
raw = fs2.readFileSync(guard.assert(file), "utf8");
|
|
421
|
+
} catch {
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const parsed = JSON.parse(raw);
|
|
426
|
+
const n = Number(parsed.lastGeneratedAt);
|
|
427
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
appendAuditLine(path5.join(workspace().logsDir, "heartbeat.jsonl"), {
|
|
432
|
+
event: "weekly_anchor_corrupt",
|
|
433
|
+
file: path5.basename(file),
|
|
434
|
+
bytes: Buffer.byteLength(raw)
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
return fs2.statSync(file).mtimeMs;
|
|
440
|
+
} catch {
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function weeklyDue(guard, dataDir, now = Date.now()) {
|
|
445
|
+
const last = lastWeeklyGeneratedAt(guard, dataDir);
|
|
446
|
+
return now - last >= WEEKLY_INTERVAL_MS;
|
|
447
|
+
}
|
|
448
|
+
function ensureWeeklyAnchor(guard, dataDir, now = Date.now()) {
|
|
449
|
+
if (lastWeeklyGeneratedAt(guard, dataDir) > 0) return false;
|
|
450
|
+
fs2.mkdirSync(weeklyDirPath(dataDir), { recursive: true });
|
|
451
|
+
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: now });
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
function saveWeeklyReport(guard, dataDir, report) {
|
|
455
|
+
const dir = weeklyDirPath(dataDir);
|
|
456
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
457
|
+
const file = path5.join(dir, reportFileName(report.end));
|
|
458
|
+
saveEncryptedText(guard, file, JSON.stringify(report, null, 1));
|
|
459
|
+
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: Date.parse(report.generatedAt) });
|
|
460
|
+
return file;
|
|
461
|
+
}
|
|
462
|
+
function listWeeklyReports(guard, dataDir) {
|
|
463
|
+
const out = [];
|
|
464
|
+
try {
|
|
465
|
+
for (const file of fs2.readdirSync(weeklyDirPath(dataDir))) {
|
|
466
|
+
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) continue;
|
|
467
|
+
const rep = readWeeklyReport(guard, dataDir, file);
|
|
468
|
+
if (!rep) continue;
|
|
469
|
+
out.push({ file, start: rep.start, end: rep.end, generatedAt: rep.generatedAt, source: rep.source });
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
return out.sort((a, b) => a.end < b.end ? 1 : -1);
|
|
474
|
+
}
|
|
475
|
+
function readWeeklyReport(guard, dataDir, file) {
|
|
476
|
+
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) return null;
|
|
477
|
+
try {
|
|
478
|
+
const raw = loadEncryptedText(guard, path5.join(weeklyDirPath(dataDir), file));
|
|
479
|
+
if (!raw) return null;
|
|
480
|
+
const rep = JSON.parse(raw);
|
|
481
|
+
if (typeof rep.text !== "string") return null;
|
|
482
|
+
return rep;
|
|
483
|
+
} catch {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function buildWeeklyPrompt(facts) {
|
|
488
|
+
return [
|
|
489
|
+
"\u4F60\u662F\u5FC3\u8DF3\u63D2\u4EF6\u7684\u540E\u53F0\u62A5\u544A\u5668\u3002\u6839\u636E\u4E0B\u9762\u7684\u672C\u5468\u4E8B\u5B9E\u6E05\u5355\uFF0C\u7528\u4E2D\u6587\u5199\u4E00\u4EFD\u5468\u62A5\u6B63\u6587\u3002",
|
|
490
|
+
"\u89C4\u5219\uFF1A",
|
|
491
|
+
"- \u53D9\u8FF0\u8005\u81EA\u79F0\u300C\u5FC3\u8DF3\u300D\u2014\u2014\u8FD9\u662F\u63D2\u4EF6\u540E\u53F0\uFF0C\u4E0D\u662F\u4EFB\u4F55\u4F1A\u8BDD\u91CC\u7684 agent\uFF1B\u7EDD\u4E0D\u80FD\u4EE5\u4F1A\u8BDD agent \u7684\u8EAB\u4EFD\u6216\u4EBA\u683C\u81EA\u79F0\u3002",
|
|
492
|
+
"- \u53EA\u62A5\u544A\u4E8B\u5B9E\u6E05\u5355\u91CC\u6709\u7684\u5185\u5BB9\uFF1B\u67D0\u4E00\u8282\u6CA1\u6709\u6570\u636E\u5C31\u6574\u8282\u8DF3\u8FC7\uFF0C\u4E0D\u8981\u7F16\u9020\u3001\u4E0D\u8981\u51D1\u6570\u3002",
|
|
493
|
+
"- \u8BED\u6C14\u5E73\u5B9E\u4E2D\u6027\uFF0C\u4E0D\u7528\u611F\u53F9\u53F7\uFF1B\u7BC7\u5E45\u4E0D\u8D85\u8FC7 250 \u5B57\u3002",
|
|
494
|
+
"- \u5F85\u529E\u79EF\u538B\u90A3\u4E00\u8282\u7528\u5546\u91CF\u7684\u8BED\u6C14\u63D0\u4E00\u53E5\uFF0C\u4E0D\u50AC\u4FC3\u3002",
|
|
495
|
+
"- \u76F4\u63A5\u8F93\u51FA\u5468\u62A5\u6B63\u6587\uFF0C\u4E0D\u8981\u8F93\u51FA\u89E3\u91CA\u3001\u6807\u9898\u6216 JSON\u3002",
|
|
496
|
+
"",
|
|
497
|
+
"\u672C\u5468\u4E8B\u5B9E\uFF08JSON\uFF09\uFF1A",
|
|
498
|
+
JSON.stringify(facts, null, 1)
|
|
499
|
+
].join("\n");
|
|
500
|
+
}
|
|
501
|
+
function renderTemplateReport(facts) {
|
|
502
|
+
const d = (iso) => iso.slice(0, 10);
|
|
503
|
+
const lines = [`\u672C\u5468\uFF08${d(facts.windowStart)} ~ ${d(facts.windowEnd)}\uFF09`];
|
|
504
|
+
lines.push(`\u8868\u8FBE ${facts.spoken} \u6B21\uFF0C\u9759\u9ED8 ${facts.silent} \u6B21\u3002`);
|
|
505
|
+
if (facts.silentTopReasons.length > 0) {
|
|
506
|
+
lines.push(`\u9759\u9ED8\u4E3B\u56E0\uFF1A${facts.silentTopReasons.map((r) => `${r.reason}\uFF08${r.count}\uFF09`).join("\u3001")}\u3002`);
|
|
507
|
+
}
|
|
508
|
+
if (facts.profileAdds.length > 0) {
|
|
509
|
+
lines.push(`\u65B0\u8BA4\u8BC6 ${facts.profileAdds.length} \u6761\uFF1A${facts.profileAdds.map((a) => a.content).join("\uFF1B")}\u3002`);
|
|
510
|
+
}
|
|
511
|
+
if (facts.ledger.added.length > 0 || facts.ledger.done.length > 0) {
|
|
512
|
+
const parts = [];
|
|
513
|
+
if (facts.ledger.added.length > 0) parts.push(`\u65B0\u589E\u5F85\u529E\uFF1A${facts.ledger.added.join("\u3001")}`);
|
|
514
|
+
if (facts.ledger.done.length > 0) parts.push(`\u5DF2\u89E3\u51B3\uFF1A${facts.ledger.done.join("\u3001")}`);
|
|
515
|
+
lines.push(`${parts.join("\uFF1B")}\u3002`);
|
|
516
|
+
}
|
|
517
|
+
if (facts.ledger.stale.length > 0) {
|
|
518
|
+
lines.push(`\u6302\u4E86\u5F88\u4E45\uFF1A${facts.ledger.stale.map((s) => `${s.text}\uFF08${s.days} \u5929\uFF09`).join("\u3001")}\u2014\u2014\u8981\u5220\u6389\u8FD8\u662F\u7EE7\u7EED\u6302\u7740\uFF1F`);
|
|
519
|
+
}
|
|
520
|
+
if (facts.seeds.added.length > 0) {
|
|
521
|
+
lines.push(`\u7D20\u6750\u6C60\u65B0\u589E ${facts.seeds.added.length} \u6761\uFF0C\u5DF2\u6D88\u8D39 ${facts.seeds.consumed.length} \u6761\u3002`);
|
|
522
|
+
}
|
|
523
|
+
if (facts.seeds.waiting.length > 0) {
|
|
524
|
+
lines.push(`\u8FD8\u6CA1\u804A\u8FC7\u7684\u7D20\u6750\uFF1A${facts.seeds.waiting.join("\u3001")}\u3002`);
|
|
525
|
+
}
|
|
526
|
+
if (facts.reports.total > 0) {
|
|
527
|
+
const reasonPart = facts.reports.reasons.length > 0 ? `\uFF08${facts.reports.reasons.map((r) => `${r.reason} ${r.count} \u6B21`).join("\u3001")}\uFF09` : "";
|
|
528
|
+
lines.push(`\u6295\u9012\u62A5\u8D26 ${facts.reports.total} \u6B21\uFF1A\u7528\u7D20\u6750 ${facts.reports.material}\u3001\u8BF4\u771F\u5FC3\u8BDD ${facts.reports.heartfelt}\u3001\u6CA1\u8BF4\u8BDD ${facts.reports.silent}${reasonPart}\u3002`);
|
|
529
|
+
}
|
|
530
|
+
if (facts.reports.missing > 0) {
|
|
531
|
+
lines.push(`\u53E6\u6709 ${facts.reports.missing} \u6B21\u6295\u9012\u6CA1\u7B49\u5230\u62A5\u8D26\uFF08\u8FD9\u90E8\u5206\u6CA1\u7B97\u8FDB\u504F\u597D\u7EDF\u8BA1\uFF09\u3002`);
|
|
532
|
+
}
|
|
533
|
+
if (facts.peakHours.length > 0) {
|
|
534
|
+
lines.push(`\u6D3B\u8DC3\u9AD8\u5CF0\uFF1A${facts.peakHours.join("\u3001")}\u3002`);
|
|
535
|
+
}
|
|
536
|
+
return lines.join("\n");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export {
|
|
540
|
+
ledgerFilePath,
|
|
541
|
+
readLedger,
|
|
542
|
+
appendEntry,
|
|
543
|
+
markDone,
|
|
544
|
+
scanPending,
|
|
545
|
+
pendingOlderThan,
|
|
546
|
+
runPowerShell,
|
|
547
|
+
runPowerShellFile,
|
|
548
|
+
minutesOfDay,
|
|
549
|
+
inHhMmWindow,
|
|
550
|
+
preferenceFilePath,
|
|
551
|
+
recordDelivery,
|
|
552
|
+
loadInterests,
|
|
553
|
+
adviseWander,
|
|
554
|
+
completeWander,
|
|
555
|
+
adviseRefillWander,
|
|
556
|
+
browseStatus,
|
|
557
|
+
ensureRegistered,
|
|
558
|
+
sendNewMessageHint,
|
|
559
|
+
sendWeeklyReadyHint,
|
|
560
|
+
weeklyDirPath,
|
|
561
|
+
weeklyDue,
|
|
562
|
+
ensureWeeklyAnchor,
|
|
563
|
+
saveWeeklyReport,
|
|
564
|
+
listWeeklyReports,
|
|
565
|
+
readWeeklyReport,
|
|
566
|
+
buildWeeklyPrompt,
|
|
567
|
+
renderTemplateReport
|
|
568
|
+
};
|
|
569
|
+
//# sourceMappingURL=chunk-WRJCINZ7.js.map
|