@kanadego/dsh-heartbeat 1.9.0 → 1.9.2
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 +58 -71
- 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/cordis.patch.yml +18 -39
- package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
- package/dist/{chunk-63FUJLIF.js → chunk-3MDBU6WY.js} +17 -2
- package/dist/chunk-3MDBU6WY.js.map +1 -0
- package/dist/chunk-AJ5HLAL7.js +2477 -0
- package/dist/chunk-AJ5HLAL7.js.map +1 -0
- package/dist/{chunk-VIZNIQLK.js → chunk-CUOSICYJ.js} +82 -74
- package/dist/chunk-CUOSICYJ.js.map +1 -0
- package/dist/chunk-IG6YMUHW.js +569 -0
- package/dist/chunk-IG6YMUHW.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-JDH2LDFS.js} +97 -71
- package/dist/chunk-JDH2LDFS.js.map +1 -0
- package/dist/chunk-K5Y6JP2B.js +399 -0
- package/dist/chunk-K5Y6JP2B.js.map +1 -0
- package/dist/{chunk-RB3NLLGF.js → chunk-KB5SMG3F.js} +3 -3
- package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
- package/dist/chunk-TY6UP74X.js.map +1 -0
- package/dist/chunk-WRUTATW4.js +47 -0
- package/dist/chunk-WRUTATW4.js.map +1 -0
- package/dist/{chunk-IPOZFZPY.js → chunk-XFNCTOW7.js} +48 -10
- package/dist/chunk-XFNCTOW7.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-P26DDMWC.js} +5 -3
- package/dist/index.d.ts +550 -0
- package/dist/index.js +665 -2730
- package/dist/index.js.map +1 -1
- package/dist/{migrate-N6NXMZ3G.js → migrate-D4VKAKYT.js} +4 -3
- package/dist/orchestrator-EUTZ3KG6.js +62 -0
- package/dist/{pool-7I4VACG2.js → pool-UTXQJJ5C.js} +5 -3
- package/dist/{snapshot-ZBCSX67Y.js → snapshot-K5VHETTK.js} +5 -4
- package/dist/snapshot-K5VHETTK.js.map +1 -0
- package/package.json +83 -76
- 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 +0 -203
- 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-KB5SMG3F.js.map} +0 -0
- /package/dist/{inbox-57NHQHY3.js.map → inbox-P26DDMWC.js.map} +0 -0
- /package/dist/{migrate-N6NXMZ3G.js.map → migrate-D4VKAKYT.js.map} +0 -0
- /package/dist/{pool-7I4VACG2.js.map → orchestrator-EUTZ3KG6.js.map} +0 -0
- /package/dist/{snapshot-ZBCSX67Y.js.map → pool-UTXQJJ5C.js.map} +0 -0
package/dist/chunk-2SYXKKZF.js
DELETED
|
@@ -1,837 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
activeSeeds,
|
|
3
|
-
loadPool,
|
|
4
|
-
normalizeCategory,
|
|
5
|
-
seedsFilePath
|
|
6
|
-
} from "./chunk-PQTSXW4F.js";
|
|
7
|
-
import {
|
|
8
|
-
atomicWriteJsonSync
|
|
9
|
-
} from "./chunk-VIZNIQLK.js";
|
|
10
|
-
import {
|
|
11
|
-
loadEncryptedText,
|
|
12
|
-
loadJson,
|
|
13
|
-
readText,
|
|
14
|
-
saveEncryptedText,
|
|
15
|
-
saveJson,
|
|
16
|
-
writeText
|
|
17
|
-
} from "./chunk-IFTFDHZX.js";
|
|
18
|
-
|
|
19
|
-
// src/core/path-guard.ts
|
|
20
|
-
import fs from "fs";
|
|
21
|
-
import path from "path";
|
|
22
|
-
var PathOutsideWorkspaceError = class extends Error {
|
|
23
|
-
constructor(target, workspace) {
|
|
24
|
-
super(`path outside workspace: "${target}" (workspace: "${workspace}")`);
|
|
25
|
-
this.name = "PathOutsideWorkspaceError";
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
function canonicalize(target) {
|
|
29
|
-
const abs = path.resolve(target);
|
|
30
|
-
try {
|
|
31
|
-
return fs.realpathSync(abs);
|
|
32
|
-
} catch {
|
|
33
|
-
const tail = [];
|
|
34
|
-
let dir = abs;
|
|
35
|
-
for (; ; ) {
|
|
36
|
-
const base = path.basename(dir);
|
|
37
|
-
const parent = path.dirname(dir);
|
|
38
|
-
if (parent === dir) {
|
|
39
|
-
throw new Error(`cannot canonicalize "${target}": no existing ancestor`);
|
|
40
|
-
}
|
|
41
|
-
tail.push(base);
|
|
42
|
-
dir = parent;
|
|
43
|
-
try {
|
|
44
|
-
const realDir = fs.realpathSync(dir);
|
|
45
|
-
return path.join(realDir, ...tail.reverse());
|
|
46
|
-
} catch {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function isInsideWorkspace(workspaceCanon, targetCanon) {
|
|
53
|
-
const norm = (p) => {
|
|
54
|
-
let n = path.normalize(p).toLowerCase();
|
|
55
|
-
if (!n.endsWith(path.sep)) n += path.sep;
|
|
56
|
-
return n;
|
|
57
|
-
};
|
|
58
|
-
const w = norm(workspaceCanon);
|
|
59
|
-
const t = norm(targetCanon);
|
|
60
|
-
return t === w || t.startsWith(w);
|
|
61
|
-
}
|
|
62
|
-
function createPathGuard(workspaceDir) {
|
|
63
|
-
const workspace = canonicalize(workspaceDir);
|
|
64
|
-
const guard = {
|
|
65
|
-
workspace,
|
|
66
|
-
check(target) {
|
|
67
|
-
const canon = canonicalize(target);
|
|
68
|
-
return isInsideWorkspace(workspace, canon) ? canon : null;
|
|
69
|
-
},
|
|
70
|
-
assert(target) {
|
|
71
|
-
const canon = guard.check(target);
|
|
72
|
-
if (canon === null) throw new PathOutsideWorkspaceError(target, workspace);
|
|
73
|
-
return canon;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
return guard;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/config/schema.ts
|
|
80
|
-
var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
81
|
-
function isPlainObject(v) {
|
|
82
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
83
|
-
}
|
|
84
|
-
function fail(msg) {
|
|
85
|
-
throw new Error(`policy: ${msg}`);
|
|
86
|
-
}
|
|
87
|
-
function assertPolicy(input) {
|
|
88
|
-
if (!isPlainObject(input)) fail("root must be an object");
|
|
89
|
-
const p = input;
|
|
90
|
-
const hb = p.heartbeat;
|
|
91
|
-
if (!isPlainObject(hb)) fail("heartbeat missing");
|
|
92
|
-
if (typeof hb.intervalMin !== "number" || hb.intervalMin < 1 || hb.intervalMin > 1440) {
|
|
93
|
-
fail("heartbeat.intervalMin must be a number in [1, 1440]");
|
|
94
|
-
}
|
|
95
|
-
if (typeof hb.idleMode !== "boolean") fail("heartbeat.idleMode must be boolean");
|
|
96
|
-
if (typeof hb.archiveRotatedHome !== "boolean") fail("heartbeat.archiveRotatedHome must be boolean");
|
|
97
|
-
const g = p.gate;
|
|
98
|
-
if (!isPlainObject(g)) fail("gate missing");
|
|
99
|
-
if (typeof g.maxDailySend !== "number" || g.maxDailySend < 0) fail("gate.maxDailySend must be >= 0");
|
|
100
|
-
if (typeof g.cooldownMinutes !== "number" || g.cooldownMinutes < 0) fail("gate.cooldownMinutes must be >= 0");
|
|
101
|
-
const qh = g.quietHours;
|
|
102
|
-
if (!isPlainObject(qh)) fail("gate.quietHours missing");
|
|
103
|
-
if (typeof qh.start !== "string" || !HHMM.test(qh.start) || typeof qh.end !== "string" || !HHMM.test(qh.end)) {
|
|
104
|
-
fail('gate.quietHours must be {start:"HH:MM", end:"HH:MM"}');
|
|
105
|
-
}
|
|
106
|
-
const b = p.browse;
|
|
107
|
-
if (!isPlainObject(b)) fail("browse missing");
|
|
108
|
-
if (!Array.isArray(b.windows) || b.windows.length === 0) fail("browse.windows must be a non-empty array");
|
|
109
|
-
for (const w of b.windows) {
|
|
110
|
-
if (!isPlainObject(w)) fail("browse.windows entries must be objects");
|
|
111
|
-
if (typeof w.start !== "string" || !HHMM.test(w.start) || typeof w.end !== "string" || !HHMM.test(w.end)) {
|
|
112
|
-
fail('browse.windows entries must be {start:"HH:MM", end:"HH:MM"}');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (typeof b.minIntervalHours !== "number" || b.minIntervalHours <= 0) fail("browse.minIntervalHours must be > 0");
|
|
116
|
-
if (typeof b.maxSeedsPerVisit !== "number" || b.maxSeedsPerVisit < 1) fail("browse.maxSeedsPerVisit must be >= 1");
|
|
117
|
-
const s = p.seeds;
|
|
118
|
-
if (!isPlainObject(s)) fail("seeds missing");
|
|
119
|
-
if (typeof s.maxActive !== "number" || s.maxActive < 1) fail("seeds.maxActive must be >= 1");
|
|
120
|
-
if (!isPlainObject(s.ttlDays)) fail("seeds.ttlDays missing");
|
|
121
|
-
for (const k of ["news", "fandom", "scene", "promise"]) {
|
|
122
|
-
if (typeof s.ttlDays[k] !== "number") fail(`seeds.ttlDays.${k} missing`);
|
|
123
|
-
}
|
|
124
|
-
if (typeof s.coldBenchDays !== "number") fail("seeds.coldBenchDays missing");
|
|
125
|
-
if (typeof s.archiveCap !== "number" || s.archiveCap < 1) fail("seeds.archiveCap must be >= 1");
|
|
126
|
-
if (typeof s.retireAfterUsed !== "number" || s.retireAfterUsed < 1) fail("seeds.retireAfterUsed must be >= 1");
|
|
127
|
-
if (!isPlainObject(s.scoreWeights)) fail("seeds.scoreWeights missing");
|
|
128
|
-
const pr = p.profile;
|
|
129
|
-
if (!isPlainObject(pr)) fail("profile missing");
|
|
130
|
-
const c = pr.consolidation;
|
|
131
|
-
if (!isPlainObject(c)) fail("profile.consolidation missing");
|
|
132
|
-
if (typeof c.minIntervalHours !== "number" || typeof c.inboxBacklog !== "number") fail("profile.consolidation fields missing");
|
|
133
|
-
if (typeof pr.partitionCap !== "number" || pr.partitionCap < 1) fail("profile.partitionCap must be >= 1");
|
|
134
|
-
if (typeof pr.maxOpsPerRun !== "number" || pr.maxOpsPerRun < 1) fail("profile.maxOpsPerRun must be >= 1");
|
|
135
|
-
const cc = pr.confidenceCap;
|
|
136
|
-
if (!isPlainObject(cc)) fail("profile.confidenceCap missing");
|
|
137
|
-
if (typeof cc.chat !== "number" || typeof cc.screen !== "number" || typeof cc.browse !== "number") {
|
|
138
|
-
fail("profile.confidenceCap fields missing");
|
|
139
|
-
}
|
|
140
|
-
if (typeof pr.volatileDays !== "number" || pr.volatileDays < 1) fail("profile.volatileDays must be >= 1");
|
|
141
|
-
if (typeof pr.stableLowActivityDays !== "number" || pr.stableLowActivityDays < 1) fail("profile.stableLowActivityDays must be >= 1");
|
|
142
|
-
if (typeof pr.psyEnabled !== "boolean") fail("profile.psyEnabled must be boolean");
|
|
143
|
-
const ob = p.observe;
|
|
144
|
-
if (!isPlainObject(ob)) fail("observe missing");
|
|
145
|
-
if (typeof ob.maxChars !== "number" || ob.maxChars < 20 || ob.maxChars > 2e3) fail("observe.maxChars must be in [20, 2000]");
|
|
146
|
-
if (typeof ob.perBeat !== "number" || ob.perBeat < 1 || ob.perBeat > 100) fail("observe.perBeat must be in [1, 100]");
|
|
147
|
-
if (!isPlainObject(p.weekly)) fail("weekly missing");
|
|
148
|
-
if (typeof p.weekly.enabled !== "boolean") fail("weekly.enabled must be boolean");
|
|
149
|
-
const r = p.retention;
|
|
150
|
-
if (!isPlainObject(r)) fail("retention missing");
|
|
151
|
-
if (typeof r.envPulseHours !== "number" || typeof r.decisionLogDays !== "number") fail("retention fields missing");
|
|
152
|
-
}
|
|
153
|
-
function deepMerge(base, override) {
|
|
154
|
-
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
155
|
-
return override === void 0 ? base : override;
|
|
156
|
-
}
|
|
157
|
-
const out = { ...base };
|
|
158
|
-
for (const [k, v] of Object.entries(override)) {
|
|
159
|
-
out[k] = v === void 0 ? base[k] : deepMerge(base[k], v);
|
|
160
|
-
}
|
|
161
|
-
return out;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/config/load.ts
|
|
165
|
-
import fs2 from "fs";
|
|
166
|
-
import path2 from "path";
|
|
167
|
-
var USER_POLICY_FILE = "policy.json";
|
|
168
|
-
function loadPolicy(guard, configDir, settingsDir) {
|
|
169
|
-
const factoryPath = path2.join(configDir, "policy.json");
|
|
170
|
-
let factoryRaw;
|
|
171
|
-
try {
|
|
172
|
-
factoryRaw = JSON.parse(fs2.readFileSync(factoryPath, "utf8"));
|
|
173
|
-
} catch (e) {
|
|
174
|
-
throw new Error(`factory policy unreadable at ${factoryPath}: ${String(e)}`);
|
|
175
|
-
}
|
|
176
|
-
assertPolicy(factoryRaw);
|
|
177
|
-
const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
|
|
178
|
-
let merged = factoryRaw;
|
|
179
|
-
if (fs2.existsSync(userPath)) {
|
|
180
|
-
try {
|
|
181
|
-
const userRaw = JSON.parse(fs2.readFileSync(userPath, "utf8"));
|
|
182
|
-
merged = deepMerge(factoryRaw, userRaw);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
throw new Error(`user policy layer unparseable at ${userPath}: ${String(e)}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
assertPolicy(merged);
|
|
188
|
-
return merged;
|
|
189
|
-
}
|
|
190
|
-
function updateUserPolicy(guard, settingsDir, patch) {
|
|
191
|
-
const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
|
|
192
|
-
let user = {};
|
|
193
|
-
try {
|
|
194
|
-
user = JSON.parse(fs2.readFileSync(userPath, "utf8"));
|
|
195
|
-
if (!user || typeof user !== "object" || Array.isArray(user)) user = {};
|
|
196
|
-
} catch {
|
|
197
|
-
user = {};
|
|
198
|
-
}
|
|
199
|
-
const merged = deepMerge(user, patch);
|
|
200
|
-
fs2.mkdirSync(path2.dirname(userPath), { recursive: true });
|
|
201
|
-
fs2.writeFileSync(userPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
202
|
-
return merged;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// src/ledger/ledger.ts
|
|
206
|
-
import path3 from "path";
|
|
207
|
-
import { randomUUID } from "crypto";
|
|
208
|
-
var DAY_MS = 864e5;
|
|
209
|
-
function ledgerFilePath(dataDir) {
|
|
210
|
-
return path3.join(dataDir, "ledger.md");
|
|
211
|
-
}
|
|
212
|
-
var LINE_RE = /^- \[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\]\[(open|done)\]\[#([0-9a-f]{6})\] (.*)$/;
|
|
213
|
-
function renderEntry(e) {
|
|
214
|
-
return `- [${e.date} ${e.time}][${e.status}][#${e.id}] ${e.text}`;
|
|
215
|
-
}
|
|
216
|
-
function readLedger(guard, file) {
|
|
217
|
-
const raw = readText(guard, file, "# \u8D26\u672C\n");
|
|
218
|
-
const lines = raw.split("\n");
|
|
219
|
-
const entries = [];
|
|
220
|
-
const rawLines = [];
|
|
221
|
-
for (const line of lines) {
|
|
222
|
-
const m = LINE_RE.exec(line);
|
|
223
|
-
if (m) {
|
|
224
|
-
entries.push({ date: m[1], time: m[2], status: m[3], id: m[4], text: m[5] });
|
|
225
|
-
}
|
|
226
|
-
rawLines.push(line);
|
|
227
|
-
}
|
|
228
|
-
return { header: lines[0] ?? "# \u8D26\u672C", entries, rawLines };
|
|
229
|
-
}
|
|
230
|
-
function appendEntry(guard, file, text, now = Date.now()) {
|
|
231
|
-
const textTrimmed = text.trim();
|
|
232
|
-
if (!textTrimmed) throw new Error("ledger entry must not be empty");
|
|
233
|
-
const d = new Date(now);
|
|
234
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
235
|
-
const entry = {
|
|
236
|
-
id: randomUUID().slice(0, 6),
|
|
237
|
-
date: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`,
|
|
238
|
-
time: `${pad(d.getHours())}:${pad(d.getMinutes())}`,
|
|
239
|
-
status: "open",
|
|
240
|
-
text: textTrimmed.replace(/\r?\n/g, " ")
|
|
241
|
-
};
|
|
242
|
-
const { rawLines } = readLedger(guard, file);
|
|
243
|
-
rawLines.push(renderEntry(entry));
|
|
244
|
-
writeText(guard, file, rawLines.join("\n").replace(/\n*$/, "\n"));
|
|
245
|
-
return entry;
|
|
246
|
-
}
|
|
247
|
-
function markDone(guard, file, key, now = Date.now()) {
|
|
248
|
-
const { rawLines, entries } = readLedger(guard, file);
|
|
249
|
-
const target = entries.find((e) => e.status === "open" && (e.id === key || e.text.includes(key)));
|
|
250
|
-
if (!target) return null;
|
|
251
|
-
const d = new Date(now);
|
|
252
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
253
|
-
const doneDate = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
254
|
-
const out = rawLines.map((line) => {
|
|
255
|
-
if (line.includes(`#${target.id}] `)) {
|
|
256
|
-
return `- [${doneDate} ${pad(d.getHours())}:${pad(d.getMinutes())}][done][#${target.id}] ${target.text}`;
|
|
257
|
-
}
|
|
258
|
-
return line;
|
|
259
|
-
});
|
|
260
|
-
writeText(guard, file, out.join("\n").replace(/\n*$/, "\n"));
|
|
261
|
-
return target;
|
|
262
|
-
}
|
|
263
|
-
function scanPending(guard, file, now = Date.now()) {
|
|
264
|
-
const { entries } = readLedger(guard, file);
|
|
265
|
-
return entries.filter((e) => e.status === "open").sort((a, b) => a.date < b.date ? -1 : 1);
|
|
266
|
-
}
|
|
267
|
-
function pendingOlderThan(guard, file, days, now = Date.now()) {
|
|
268
|
-
const cutoff = new Date(now - days * DAY_MS).toISOString().slice(0, 10);
|
|
269
|
-
return scanPending(guard, file, now).filter((e) => e.date <= cutoff);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// src/browse/browse.ts
|
|
273
|
-
import fs3 from "fs";
|
|
274
|
-
import path5 from "path";
|
|
275
|
-
|
|
276
|
-
// src/browse/preference.ts
|
|
277
|
-
import path4 from "path";
|
|
278
|
-
var DAY_MS2 = 864e5;
|
|
279
|
-
var W_MIN = 0.5;
|
|
280
|
-
var W_MAX = 2;
|
|
281
|
-
var SMOOTH_ADOPTED = 1;
|
|
282
|
-
var SMOOTH_DELIVERED = 4;
|
|
283
|
-
var DECAY_HALF_LIFE_DAYS = 30;
|
|
284
|
-
function preferenceFilePath(dataDir) {
|
|
285
|
-
return path4.join(dataDir, "preference.json");
|
|
286
|
-
}
|
|
287
|
-
function num(v) {
|
|
288
|
-
const n = Number(v);
|
|
289
|
-
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
290
|
-
}
|
|
291
|
-
function sanitizeTopicPref(raw) {
|
|
292
|
-
if (!raw || typeof raw !== "object") return null;
|
|
293
|
-
const t = raw;
|
|
294
|
-
if (!Number.isFinite(Number(t.delivered)) || !Number.isFinite(Number(t.adopted))) return null;
|
|
295
|
-
const delivered = Math.floor(num(t.delivered));
|
|
296
|
-
return {
|
|
297
|
-
delivered,
|
|
298
|
-
adopted: Math.min(delivered, Math.floor(num(t.adopted))),
|
|
299
|
-
lastDeliveredAt: num(t.lastDeliveredAt),
|
|
300
|
-
lastAdoptedAt: num(t.lastAdoptedAt)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
function loadPreference(guard, file) {
|
|
304
|
-
const doc = loadJson(guard, file);
|
|
305
|
-
if (!doc || typeof doc !== "object" || !doc.topics || typeof doc.topics !== "object") {
|
|
306
|
-
return { version: 1, topics: {}, updatedAt: 0 };
|
|
307
|
-
}
|
|
308
|
-
const topics = {};
|
|
309
|
-
for (const [key, raw] of Object.entries(doc.topics)) {
|
|
310
|
-
const t = sanitizeTopicPref(raw);
|
|
311
|
-
if (key && t) topics[key] = t;
|
|
312
|
-
}
|
|
313
|
-
const updatedAt = Number(doc.updatedAt);
|
|
314
|
-
return { version: 1, topics, updatedAt: Number.isFinite(updatedAt) ? updatedAt : 0 };
|
|
315
|
-
}
|
|
316
|
-
function savePreference(guard, file, state) {
|
|
317
|
-
saveJson(guard, file, state);
|
|
318
|
-
}
|
|
319
|
-
function recordDelivery(guard, file, opts) {
|
|
320
|
-
const state = loadPreference(guard, file);
|
|
321
|
-
const adopted = new Set(opts.adoptedTopics);
|
|
322
|
-
for (const topic of opts.offeredTopics) {
|
|
323
|
-
if (!topic) continue;
|
|
324
|
-
const t = state.topics[topic] ?? { delivered: 0, adopted: 0, lastDeliveredAt: 0, lastAdoptedAt: 0 };
|
|
325
|
-
t.delivered += 1;
|
|
326
|
-
t.lastDeliveredAt = opts.now;
|
|
327
|
-
if (adopted.has(topic)) {
|
|
328
|
-
t.adopted += 1;
|
|
329
|
-
t.lastAdoptedAt = opts.now;
|
|
330
|
-
}
|
|
331
|
-
state.topics[topic] = t;
|
|
332
|
-
}
|
|
333
|
-
state.updatedAt = opts.now;
|
|
334
|
-
savePreference(guard, file, state);
|
|
335
|
-
return state;
|
|
336
|
-
}
|
|
337
|
-
function decayed(t, now) {
|
|
338
|
-
const dAgeDays = Math.max(0, now - t.lastDeliveredAt) / DAY_MS2;
|
|
339
|
-
const aAgeDays = Math.max(0, now - t.lastAdoptedAt) / DAY_MS2;
|
|
340
|
-
const dHalf = Math.pow(0.5, dAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
341
|
-
const aHalf = Math.pow(0.5, aAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
342
|
-
return { delivered: t.delivered * dHalf, adopted: t.adopted * aHalf };
|
|
343
|
-
}
|
|
344
|
-
function preferenceWeight(state, topic, now) {
|
|
345
|
-
const t = state.topics[topic];
|
|
346
|
-
const rate = t ? (() => {
|
|
347
|
-
const d = decayed(t, now);
|
|
348
|
-
return (d.adopted + SMOOTH_ADOPTED) / (d.delivered + SMOOTH_DELIVERED);
|
|
349
|
-
})() : SMOOTH_ADOPTED / SMOOTH_DELIVERED;
|
|
350
|
-
return Math.min(W_MAX, Math.max(W_MIN, rate / (SMOOTH_ADOPTED / SMOOTH_DELIVERED)));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// src/browse/browse.ts
|
|
354
|
-
var WATCH_THROTTLE_MS = 6 * 36e5;
|
|
355
|
-
var UA = { "User-Agent": "dsh-heartbeat/2.0 (+local; personal companion)" };
|
|
356
|
-
function emptyBrowseState() {
|
|
357
|
-
return { targets: {}, last_check_at: 0, wander: { focusHistory: {}, focusCount: {}, last_wander_at: 0, refillCount: {} } };
|
|
358
|
-
}
|
|
359
|
-
function browseStatePath(paths) {
|
|
360
|
-
return path5.join(paths.dataDir, "browse.json");
|
|
361
|
-
}
|
|
362
|
-
function readJsonFile(file, fallback) {
|
|
363
|
-
try {
|
|
364
|
-
return JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
365
|
-
} catch {
|
|
366
|
-
return fallback;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function loadInterests(paths) {
|
|
370
|
-
const userPath = path5.join(paths.settingsDir, "interests.json");
|
|
371
|
-
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { interests: [], _schedule: {} });
|
|
372
|
-
return readJsonFile(path5.join(paths.configDir, "interests.json"), { interests: [], _schedule: {} });
|
|
373
|
-
}
|
|
374
|
-
function loadWatchlist(paths) {
|
|
375
|
-
const userPath = path5.join(paths.settingsDir, "watchlist.json");
|
|
376
|
-
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { targets: [] });
|
|
377
|
-
return readJsonFile(path5.join(paths.configDir, "watchlist.json"), { targets: [] });
|
|
378
|
-
}
|
|
379
|
-
function loadState(guard, paths) {
|
|
380
|
-
return loadJson(guard, browseStatePath(paths)) ?? emptyBrowseState();
|
|
381
|
-
}
|
|
382
|
-
async function checkNpm(fetcher, name) {
|
|
383
|
-
const r = await fetcher(`https://registry.npmjs.org/${name}/latest`, { headers: UA });
|
|
384
|
-
if (!r.ok) throw new Error(`npm ${r.status}`);
|
|
385
|
-
const j = await r.json();
|
|
386
|
-
if (!j.version) throw new Error("npm: no version");
|
|
387
|
-
return { version: j.version, seen: `npm:${j.version}` };
|
|
388
|
-
}
|
|
389
|
-
async function checkGithub(fetcher, repo) {
|
|
390
|
-
const r = await fetcher(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
391
|
-
headers: { ...UA, Accept: "application/vnd.github+json" }
|
|
392
|
-
});
|
|
393
|
-
if (r.status === 404) return null;
|
|
394
|
-
if (!r.ok) throw new Error(`gh ${r.status}`);
|
|
395
|
-
const j = await r.json();
|
|
396
|
-
if (!j.tag_name) throw new Error("gh: no tag");
|
|
397
|
-
return { version: j.tag_name, seen: `gh:${j.tag_name}`, title: j.name || "" };
|
|
398
|
-
}
|
|
399
|
-
async function checkWatchlist(guard, paths, opts = {}, now = Date.now()) {
|
|
400
|
-
const fetcher = opts.fetcher ?? globalThis.fetch;
|
|
401
|
-
const state = loadState(guard, paths);
|
|
402
|
-
if (opts.throttleOk !== true && now - state.last_check_at < WATCH_THROTTLE_MS) {
|
|
403
|
-
return { items: [], errors: [], checked: 0 };
|
|
404
|
-
}
|
|
405
|
-
const watchlist = loadWatchlist(paths);
|
|
406
|
-
const report = { items: [], errors: [], checked: 0 };
|
|
407
|
-
for (const t of watchlist.targets ?? []) {
|
|
408
|
-
report.checked += 1;
|
|
409
|
-
try {
|
|
410
|
-
const info = t.type === "npm" && t.name ? await checkNpm(fetcher, t.name) : t.type === "github" && t.repo ? await checkGithub(fetcher, t.repo) : null;
|
|
411
|
-
if (!info) continue;
|
|
412
|
-
const prev = state.targets[t.id];
|
|
413
|
-
if (prev && prev.seen !== info.seen) {
|
|
414
|
-
const title = info.title ? `\uFF08${info.title.slice(0, 60)}\uFF09` : "";
|
|
415
|
-
report.items.push({
|
|
416
|
-
text: `${t.note || t.id} \u6709\u66F4\u65B0\uFF1A${prev.version} -> ${info.version}${title}`,
|
|
417
|
-
topic: `watch:${t.id}`,
|
|
418
|
-
tag: "news",
|
|
419
|
-
source: "browse",
|
|
420
|
-
confidence: 0.4
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
state.targets[t.id] = info;
|
|
424
|
-
} catch (e) {
|
|
425
|
-
report.errors.push(`${t.id}: ${String(e)}`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
state.last_check_at = now;
|
|
429
|
-
saveJson(guard, browseStatePath(paths), state);
|
|
430
|
-
return report;
|
|
431
|
-
}
|
|
432
|
-
function inWanderWindow(now, windows) {
|
|
433
|
-
const hm = now.getHours() * 60 + now.getMinutes();
|
|
434
|
-
for (const w of windows) {
|
|
435
|
-
const [sh, sm] = w.start.split(":").map(Number);
|
|
436
|
-
const [eh, em] = w.end.split(":").map(Number);
|
|
437
|
-
if (hm >= sh * 60 + sm && hm <= eh * 60 + em) return `${w.start}-${w.end}`;
|
|
438
|
-
}
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
function onCooldown(state, focus, cooldownDays, now) {
|
|
442
|
-
const last = state.wander.focusHistory[focus] ?? 0;
|
|
443
|
-
return last > now - cooldownDays * 864e5;
|
|
444
|
-
}
|
|
445
|
-
var STARVATION_DAYS = 14;
|
|
446
|
-
function pickFocus(state, interests, now, pref) {
|
|
447
|
-
const sc = interests._schedule ?? {};
|
|
448
|
-
const cooldown = sc.focus_cooldown_days ?? 3;
|
|
449
|
-
const pool = (interests.interests ?? []).filter((t) => !onCooldown(state, t, cooldown, now));
|
|
450
|
-
if (pool.length === 0) return null;
|
|
451
|
-
if (!pref) {
|
|
452
|
-
pool.sort((a, b) => (state.wander.focusHistory[a] ?? 0) - (state.wander.focusHistory[b] ?? 0));
|
|
453
|
-
return pool[0];
|
|
454
|
-
}
|
|
455
|
-
const age = (t) => now - (state.wander.focusHistory[t] ?? 0);
|
|
456
|
-
const starved = pool.filter((t) => age(t) >= STARVATION_DAYS * 864e5);
|
|
457
|
-
if (starved.length > 0) {
|
|
458
|
-
starved.sort((a, b) => age(b) - age(a));
|
|
459
|
-
return starved[0];
|
|
460
|
-
}
|
|
461
|
-
const effAge = (t) => age(t) * preferenceWeight(pref, t, now);
|
|
462
|
-
pool.sort((a, b) => effAge(b) - effAge(a));
|
|
463
|
-
return pool[0];
|
|
464
|
-
}
|
|
465
|
-
function adviseWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
466
|
-
const state = loadState(guard, paths);
|
|
467
|
-
const interests = loadInterests(paths);
|
|
468
|
-
const windows = interests._schedule?.windows?.length ? interests._schedule.windows : policy.browse.windows;
|
|
469
|
-
const win = inWanderWindow(now, windows);
|
|
470
|
-
if (!win) {
|
|
471
|
-
const hh = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
472
|
-
return { focus: null, query: null, skipped: `window(now=${hh})` };
|
|
473
|
-
}
|
|
474
|
-
const minGap = policy.browse.minIntervalHours * 36e5;
|
|
475
|
-
if (now.getTime() - state.wander.last_wander_at < minGap) {
|
|
476
|
-
return { focus: null, query: null, skipped: "min-interval" };
|
|
477
|
-
}
|
|
478
|
-
const focus = pickFocus(state, interests, now.getTime(), loadPreference(guard, preferenceFilePath(paths.dataDir)));
|
|
479
|
-
if (!focus) return { focus: null, query: null, skipped: "no-focus" };
|
|
480
|
-
return { focus, query: `${focus} 2026 \u6700\u65B0`, skipped: null };
|
|
481
|
-
}
|
|
482
|
-
function completeWander(guard, paths, focus, now = Date.now(), opts = {}) {
|
|
483
|
-
const state = loadState(guard, paths);
|
|
484
|
-
state.wander.focusHistory[focus] = now;
|
|
485
|
-
state.wander.focusCount[focus] = (state.wander.focusCount[focus] ?? 0) + 1;
|
|
486
|
-
state.wander.last_wander_at = now;
|
|
487
|
-
let refillsToday;
|
|
488
|
-
if (opts.refill) {
|
|
489
|
-
const today = localDayKey(new Date(now));
|
|
490
|
-
state.wander.refillCount = state.wander.refillCount ?? {};
|
|
491
|
-
state.wander.refillCount[today] = (state.wander.refillCount[today] ?? 0) + 1;
|
|
492
|
-
refillsToday = state.wander.refillCount[today];
|
|
493
|
-
}
|
|
494
|
-
saveJson(guard, browseStatePath(paths), state);
|
|
495
|
-
return { focus, count: state.wander.focusCount[focus], ...refillsToday === void 0 ? {} : { refillsToday } };
|
|
496
|
-
}
|
|
497
|
-
function localDayKey(d) {
|
|
498
|
-
const y = d.getFullYear();
|
|
499
|
-
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
500
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
501
|
-
return `${y}-${m}-${day}`;
|
|
502
|
-
}
|
|
503
|
-
var REFILL_TOPIC_THRESHOLD = 4;
|
|
504
|
-
var REFILL_MAX_PER_DAY = 2;
|
|
505
|
-
function adviseRefillWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
506
|
-
const state = loadState(guard, paths);
|
|
507
|
-
const today = localDayKey(now);
|
|
508
|
-
const refillsToday = state.wander.refillCount?.[today] ?? 0;
|
|
509
|
-
const topicCount = activeSeeds(loadPool(guard, seedsFilePath(paths.dataDir))).filter((s) => normalizeCategory(s.category) === "topic").length;
|
|
510
|
-
if (refillsToday >= REFILL_MAX_PER_DAY) {
|
|
511
|
-
return { focus: null, query: null, skipped: `refill-daily-cap(${refillsToday})`, topicCount, refillsToday };
|
|
512
|
-
}
|
|
513
|
-
if (topicCount > REFILL_TOPIC_THRESHOLD) {
|
|
514
|
-
return { focus: null, query: null, skipped: `topic-stock-ok(${topicCount})`, topicCount, refillsToday };
|
|
515
|
-
}
|
|
516
|
-
const interests = loadInterests(paths);
|
|
517
|
-
const focus = pickFocus(state, interests, now.getTime(), loadPreference(guard, preferenceFilePath(paths.dataDir)));
|
|
518
|
-
if (!focus) {
|
|
519
|
-
return { focus: null, query: null, skipped: "no-focus", topicCount, refillsToday };
|
|
520
|
-
}
|
|
521
|
-
return { focus, query: `${focus} 2026 \u6700\u65B0`, skipped: null, topicCount, refillsToday };
|
|
522
|
-
}
|
|
523
|
-
function browseStatus(guard, paths) {
|
|
524
|
-
return loadState(guard, paths);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// src/notify/notify.ts
|
|
528
|
-
import { spawnSync } from "child_process";
|
|
529
|
-
import path6 from "path";
|
|
530
|
-
function runNotify(paths, args) {
|
|
531
|
-
const r = spawnSync(
|
|
532
|
-
"powershell.exe",
|
|
533
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path6.join(paths.assetsDir, "notify.ps1"), ...args],
|
|
534
|
-
{ timeout: 2e4, encoding: "utf8" }
|
|
535
|
-
);
|
|
536
|
-
return { status: r.status ?? -1, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
537
|
-
}
|
|
538
|
-
function ensureRegistered(paths) {
|
|
539
|
-
const check = runNotify(paths, ["-Check"]);
|
|
540
|
-
if (/REGISTERED: yes/.test(check.out)) return true;
|
|
541
|
-
const reg = runNotify(paths, ["-RegisterOnly"]);
|
|
542
|
-
return reg.status === 0;
|
|
543
|
-
}
|
|
544
|
-
function sendNewMessageHint(paths) {
|
|
545
|
-
const r = runNotify(paths, ["-Title", "Heartbeat", "-Message", "\u6709\u65B0\u6D88\u606F"]);
|
|
546
|
-
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
547
|
-
}
|
|
548
|
-
function sendWeeklyReadyHint(paths) {
|
|
549
|
-
const r = runNotify(paths, ["-Title", "\u5FC3\u8DF3\u5468\u62A5", "-Message", "\u672C\u671F\u5468\u62A5\u5DF2\u751F\u6210"]);
|
|
550
|
-
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// src/weekly/report.ts
|
|
554
|
-
import fs4 from "fs";
|
|
555
|
-
import path7 from "path";
|
|
556
|
-
var WEEKLY_INTERVAL_MS = 7 * 864e5;
|
|
557
|
-
function weeklyDirPath(dataDir) {
|
|
558
|
-
return path7.join(dataDir, "weekly");
|
|
559
|
-
}
|
|
560
|
-
function stateFilePath(dataDir) {
|
|
561
|
-
return path7.join(weeklyDirPath(dataDir), "state.json");
|
|
562
|
-
}
|
|
563
|
-
function reportFileName(endIso) {
|
|
564
|
-
return `report-${endIso.slice(0, 10)}.json`;
|
|
565
|
-
}
|
|
566
|
-
function lastWeeklyGeneratedAt(guard, dataDir) {
|
|
567
|
-
try {
|
|
568
|
-
const raw = JSON.parse(fs4.readFileSync(guard.assert(stateFilePath(dataDir)), "utf8"));
|
|
569
|
-
return Number(raw.lastGeneratedAt) || 0;
|
|
570
|
-
} catch {
|
|
571
|
-
return 0;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function weeklyDue(guard, dataDir, now = Date.now()) {
|
|
575
|
-
const last = lastWeeklyGeneratedAt(guard, dataDir);
|
|
576
|
-
return now - last >= WEEKLY_INTERVAL_MS;
|
|
577
|
-
}
|
|
578
|
-
function ensureWeeklyAnchor(guard, dataDir, now = Date.now()) {
|
|
579
|
-
if (lastWeeklyGeneratedAt(guard, dataDir) > 0) return false;
|
|
580
|
-
fs4.mkdirSync(weeklyDirPath(dataDir), { recursive: true });
|
|
581
|
-
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: now });
|
|
582
|
-
return true;
|
|
583
|
-
}
|
|
584
|
-
function saveWeeklyReport(guard, dataDir, report) {
|
|
585
|
-
const dir = weeklyDirPath(dataDir);
|
|
586
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
587
|
-
const file = path7.join(dir, reportFileName(report.end));
|
|
588
|
-
saveEncryptedText(guard, file, JSON.stringify(report, null, 1));
|
|
589
|
-
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: Date.parse(report.generatedAt) });
|
|
590
|
-
return file;
|
|
591
|
-
}
|
|
592
|
-
function listWeeklyReports(guard, dataDir) {
|
|
593
|
-
const out = [];
|
|
594
|
-
try {
|
|
595
|
-
for (const file of fs4.readdirSync(weeklyDirPath(dataDir))) {
|
|
596
|
-
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) continue;
|
|
597
|
-
const rep = readWeeklyReport(guard, dataDir, file);
|
|
598
|
-
if (!rep) continue;
|
|
599
|
-
out.push({ file, start: rep.start, end: rep.end, generatedAt: rep.generatedAt, source: rep.source });
|
|
600
|
-
}
|
|
601
|
-
} catch {
|
|
602
|
-
}
|
|
603
|
-
return out.sort((a, b) => a.end < b.end ? 1 : -1);
|
|
604
|
-
}
|
|
605
|
-
function readWeeklyReport(guard, dataDir, file) {
|
|
606
|
-
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) return null;
|
|
607
|
-
try {
|
|
608
|
-
const raw = loadEncryptedText(guard, path7.join(weeklyDirPath(dataDir), file));
|
|
609
|
-
if (!raw) return null;
|
|
610
|
-
const rep = JSON.parse(raw);
|
|
611
|
-
if (typeof rep.text !== "string") return null;
|
|
612
|
-
return rep;
|
|
613
|
-
} catch {
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
function buildWeeklyPrompt(facts) {
|
|
618
|
-
return [
|
|
619
|
-
"\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",
|
|
620
|
-
"\u89C4\u5219\uFF1A",
|
|
621
|
-
"- \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",
|
|
622
|
-
"- \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",
|
|
623
|
-
"- \u8BED\u6C14\u5E73\u5B9E\u4E2D\u6027\uFF0C\u4E0D\u7528\u611F\u53F9\u53F7\uFF1B\u7BC7\u5E45\u4E0D\u8D85\u8FC7 250 \u5B57\u3002",
|
|
624
|
-
"- \u5F85\u529E\u79EF\u538B\u90A3\u4E00\u8282\u7528\u5546\u91CF\u7684\u8BED\u6C14\u63D0\u4E00\u53E5\uFF0C\u4E0D\u50AC\u4FC3\u3002",
|
|
625
|
-
"- \u76F4\u63A5\u8F93\u51FA\u5468\u62A5\u6B63\u6587\uFF0C\u4E0D\u8981\u8F93\u51FA\u89E3\u91CA\u3001\u6807\u9898\u6216 JSON\u3002",
|
|
626
|
-
"",
|
|
627
|
-
"\u672C\u5468\u4E8B\u5B9E\uFF08JSON\uFF09\uFF1A",
|
|
628
|
-
JSON.stringify(facts, null, 1)
|
|
629
|
-
].join("\n");
|
|
630
|
-
}
|
|
631
|
-
function renderTemplateReport(facts) {
|
|
632
|
-
const d = (iso) => iso.slice(0, 10);
|
|
633
|
-
const lines = [`\u672C\u5468\uFF08${d(facts.windowStart)} ~ ${d(facts.windowEnd)}\uFF09`];
|
|
634
|
-
lines.push(`\u8868\u8FBE ${facts.spoken} \u6B21\uFF0C\u9759\u9ED8 ${facts.silent} \u6B21\u3002`);
|
|
635
|
-
if (facts.silentTopReasons.length > 0) {
|
|
636
|
-
lines.push(`\u9759\u9ED8\u4E3B\u56E0\uFF1A${facts.silentTopReasons.map((r) => `${r.reason}\uFF08${r.count}\uFF09`).join("\u3001")}\u3002`);
|
|
637
|
-
}
|
|
638
|
-
if (facts.profileAdds.length > 0) {
|
|
639
|
-
lines.push(`\u65B0\u8BA4\u8BC6 ${facts.profileAdds.length} \u6761\uFF1A${facts.profileAdds.map((a) => a.content).join("\uFF1B")}\u3002`);
|
|
640
|
-
}
|
|
641
|
-
if (facts.ledger.added.length > 0 || facts.ledger.done.length > 0) {
|
|
642
|
-
const parts = [];
|
|
643
|
-
if (facts.ledger.added.length > 0) parts.push(`\u65B0\u589E\u5F85\u529E\uFF1A${facts.ledger.added.join("\u3001")}`);
|
|
644
|
-
if (facts.ledger.done.length > 0) parts.push(`\u5DF2\u89E3\u51B3\uFF1A${facts.ledger.done.join("\u3001")}`);
|
|
645
|
-
lines.push(`${parts.join("\uFF1B")}\u3002`);
|
|
646
|
-
}
|
|
647
|
-
if (facts.ledger.stale.length > 0) {
|
|
648
|
-
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`);
|
|
649
|
-
}
|
|
650
|
-
if (facts.seeds.added.length > 0) {
|
|
651
|
-
lines.push(`\u7D20\u6750\u6C60\u65B0\u589E ${facts.seeds.added.length} \u6761\uFF0C\u5DF2\u6D88\u8D39 ${facts.seeds.consumed.length} \u6761\u3002`);
|
|
652
|
-
}
|
|
653
|
-
if (facts.seeds.waiting.length > 0) {
|
|
654
|
-
lines.push(`\u8FD8\u6CA1\u804A\u8FC7\u7684\u7D20\u6750\uFF1A${facts.seeds.waiting.join("\u3001")}\u3002`);
|
|
655
|
-
}
|
|
656
|
-
if (facts.reports.total > 0) {
|
|
657
|
-
const reasonPart = facts.reports.reasons.length > 0 ? `\uFF08${facts.reports.reasons.map((r) => `${r.reason} ${r.count} \u6B21`).join("\u3001")}\uFF09` : "";
|
|
658
|
-
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`);
|
|
659
|
-
}
|
|
660
|
-
if (facts.reports.missing > 0) {
|
|
661
|
-
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`);
|
|
662
|
-
}
|
|
663
|
-
if (facts.peakHours.length > 0) {
|
|
664
|
-
lines.push(`\u6D3B\u8DC3\u9AD8\u5CF0\uFF1A${facts.peakHours.join("\u3001")}\u3002`);
|
|
665
|
-
}
|
|
666
|
-
return lines.join("\n");
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/core/preset-install.ts
|
|
670
|
-
import fs5 from "fs";
|
|
671
|
-
import os from "os";
|
|
672
|
-
import path8 from "path";
|
|
673
|
-
import { fileURLToPath } from "url";
|
|
674
|
-
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
675
|
-
var METADATA_FILE = "preset.yml";
|
|
676
|
-
var BUNDLED_PRESET_ID = "heartbeat";
|
|
677
|
-
function bundledPresetDir(moduleUrl, id = BUNDLED_PRESET_ID) {
|
|
678
|
-
let dir;
|
|
679
|
-
try {
|
|
680
|
-
dir = path8.dirname(fileURLToPath(moduleUrl));
|
|
681
|
-
} catch {
|
|
682
|
-
return void 0;
|
|
683
|
-
}
|
|
684
|
-
for (let depth = 0; depth < 5; depth += 1) {
|
|
685
|
-
const candidate = path8.join(dir, "assets", "presets", id);
|
|
686
|
-
if (fs5.existsSync(path8.join(candidate, COMPOSITION_FILE))) return candidate;
|
|
687
|
-
const parent = path8.dirname(dir);
|
|
688
|
-
if (parent === dir) break;
|
|
689
|
-
dir = parent;
|
|
690
|
-
}
|
|
691
|
-
return void 0;
|
|
692
|
-
}
|
|
693
|
-
function userPresetRoot(roots) {
|
|
694
|
-
const found = roots?.find(
|
|
695
|
-
(root) => root?.trust === "user" && typeof root.path === "string" && root.path.length > 0
|
|
696
|
-
);
|
|
697
|
-
return found?.path === void 0 ? void 0 : path8.resolve(found.path);
|
|
698
|
-
}
|
|
699
|
-
function conventionalUserPresetRoot(env = process.env, home = os.homedir()) {
|
|
700
|
-
const override = env.DSH_HOME?.trim();
|
|
701
|
-
const root = override && override.length > 0 ? override : path8.join(home, ".dsh");
|
|
702
|
-
return path8.join(root, ".agent-presets");
|
|
703
|
-
}
|
|
704
|
-
function installBundledPreset(options) {
|
|
705
|
-
const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;
|
|
706
|
-
if (options.enabled === false) {
|
|
707
|
-
return { action: "skipped-disabled", id, detail: "installPreset=false" };
|
|
708
|
-
}
|
|
709
|
-
const bundledDir = bundledPresetDir(options.moduleUrl, BUNDLED_PRESET_ID);
|
|
710
|
-
if (id !== BUNDLED_PRESET_ID) {
|
|
711
|
-
return {
|
|
712
|
-
action: "skipped-custom-id",
|
|
713
|
-
id,
|
|
714
|
-
...bundledDir === void 0 ? {} : { bundledDir },
|
|
715
|
-
detail: `only "${BUNDLED_PRESET_ID}" ships with the plugin; "${id}" is yours to provide`
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
if (bundledDir === void 0) {
|
|
719
|
-
return {
|
|
720
|
-
action: "error",
|
|
721
|
-
id,
|
|
722
|
-
detail: "bundled template not found next to the plugin (assets/presets/heartbeat)"
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
const root = options.root ?? (options.rosterKnown ? void 0 : conventionalUserPresetRoot());
|
|
726
|
-
if (root === void 0) {
|
|
727
|
-
return {
|
|
728
|
-
action: "skipped-no-root",
|
|
729
|
-
id,
|
|
730
|
-
bundledDir,
|
|
731
|
-
detail: "the roster mounts no user preset root (includeUserRoot=false)"
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
const dir = path8.join(root, id);
|
|
735
|
-
const composition = path8.join(dir, COMPOSITION_FILE);
|
|
736
|
-
try {
|
|
737
|
-
if (fs5.existsSync(composition)) {
|
|
738
|
-
if (options.force !== true) {
|
|
739
|
-
const drifted = !sameBytes(composition, path8.join(bundledDir, COMPOSITION_FILE));
|
|
740
|
-
return {
|
|
741
|
-
action: "exists",
|
|
742
|
-
id,
|
|
743
|
-
dir,
|
|
744
|
-
bundledDir,
|
|
745
|
-
detail: drifted ? "kept as-is (differs from the bundled template)" : "kept as-is"
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
fs5.copyFileSync(path8.join(bundledDir, COMPOSITION_FILE), composition);
|
|
749
|
-
return {
|
|
750
|
-
action: "restored",
|
|
751
|
-
id,
|
|
752
|
-
dir,
|
|
753
|
-
bundledDir,
|
|
754
|
-
detail: "composition replaced from the bundled template"
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
const existed = fs5.existsSync(dir);
|
|
758
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
759
|
-
fs5.copyFileSync(path8.join(bundledDir, COMPOSITION_FILE), composition);
|
|
760
|
-
const metadata = path8.join(dir, METADATA_FILE);
|
|
761
|
-
if (!fs5.existsSync(metadata)) fs5.copyFileSync(path8.join(bundledDir, METADATA_FILE), metadata);
|
|
762
|
-
return {
|
|
763
|
-
action: existed ? "repaired" : "created",
|
|
764
|
-
id,
|
|
765
|
-
dir,
|
|
766
|
-
bundledDir,
|
|
767
|
-
detail: existed ? "directory existed without a composition file (it occupied the id as a broken row)" : void 0
|
|
768
|
-
};
|
|
769
|
-
} catch (error) {
|
|
770
|
-
return { action: "error", id, dir, bundledDir, detail: String(error).slice(0, 200) };
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
function describeInstall(result) {
|
|
774
|
-
const where = result.dir === void 0 ? "" : ` (${result.dir})`;
|
|
775
|
-
const why = result.detail === void 0 ? "" : ` \u2014 ${result.detail}`;
|
|
776
|
-
return `preset ${result.id} ${result.action}${where}${why}`;
|
|
777
|
-
}
|
|
778
|
-
function presetStatus(moduleUrl, id = BUNDLED_PRESET_ID, root = conventionalUserPresetRoot()) {
|
|
779
|
-
const dir = path8.join(root, id);
|
|
780
|
-
const bundledDir = bundledPresetDir(moduleUrl, id);
|
|
781
|
-
const installed = fs5.existsSync(path8.join(dir, COMPOSITION_FILE));
|
|
782
|
-
return {
|
|
783
|
-
id,
|
|
784
|
-
dir,
|
|
785
|
-
...bundledDir === void 0 ? {} : { bundledDir },
|
|
786
|
-
installed,
|
|
787
|
-
compositionMatches: installed && bundledDir !== void 0 && sameBytes(path8.join(dir, COMPOSITION_FILE), path8.join(bundledDir, COMPOSITION_FILE)),
|
|
788
|
-
metadataMatches: bundledDir !== void 0 && fs5.existsSync(path8.join(dir, METADATA_FILE)) && sameBytes(path8.join(dir, METADATA_FILE), path8.join(bundledDir, METADATA_FILE))
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
function sameBytes(left, right) {
|
|
792
|
-
try {
|
|
793
|
-
return fs5.readFileSync(left).equals(fs5.readFileSync(right));
|
|
794
|
-
} catch {
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
export {
|
|
800
|
-
createPathGuard,
|
|
801
|
-
deepMerge,
|
|
802
|
-
loadPolicy,
|
|
803
|
-
updateUserPolicy,
|
|
804
|
-
ledgerFilePath,
|
|
805
|
-
readLedger,
|
|
806
|
-
appendEntry,
|
|
807
|
-
markDone,
|
|
808
|
-
scanPending,
|
|
809
|
-
pendingOlderThan,
|
|
810
|
-
preferenceFilePath,
|
|
811
|
-
recordDelivery,
|
|
812
|
-
loadInterests,
|
|
813
|
-
loadWatchlist,
|
|
814
|
-
checkWatchlist,
|
|
815
|
-
adviseWander,
|
|
816
|
-
completeWander,
|
|
817
|
-
adviseRefillWander,
|
|
818
|
-
browseStatus,
|
|
819
|
-
ensureRegistered,
|
|
820
|
-
sendNewMessageHint,
|
|
821
|
-
sendWeeklyReadyHint,
|
|
822
|
-
weeklyDirPath,
|
|
823
|
-
weeklyDue,
|
|
824
|
-
ensureWeeklyAnchor,
|
|
825
|
-
saveWeeklyReport,
|
|
826
|
-
listWeeklyReports,
|
|
827
|
-
readWeeklyReport,
|
|
828
|
-
buildWeeklyPrompt,
|
|
829
|
-
renderTemplateReport,
|
|
830
|
-
BUNDLED_PRESET_ID,
|
|
831
|
-
userPresetRoot,
|
|
832
|
-
conventionalUserPresetRoot,
|
|
833
|
-
installBundledPreset,
|
|
834
|
-
describeInstall,
|
|
835
|
-
presetStatus
|
|
836
|
-
};
|
|
837
|
-
//# sourceMappingURL=chunk-2SYXKKZF.js.map
|