@kanadego/dsh-heartbeat 1.7.3 → 1.8.0
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 +39 -11
- package/client.js +93 -0
- package/config/policy.json +9 -1
- package/dist/{chunk-4UE74TUB.js → chunk-63FUJLIF.js} +2 -2
- package/dist/{chunk-LLD7LUNN.js → chunk-IFTFDHZX.js} +2 -1
- package/dist/{chunk-SVP2NDRF.js → chunk-MXJPX3FM.js} +2 -2
- package/dist/chunk-RB3NLLGF.js +87 -0
- package/dist/chunk-RB3NLLGF.js.map +1 -0
- package/dist/{chunk-ON4MSU6E.js → chunk-SQJLKP7L.js} +175 -477
- package/dist/chunk-SQJLKP7L.js.map +1 -0
- package/dist/chunk-UCJR6TC7.js +163 -0
- package/dist/chunk-UCJR6TC7.js.map +1 -0
- package/dist/chunk-VIZNIQLK.js +472 -0
- package/dist/chunk-VIZNIQLK.js.map +1 -0
- package/dist/cli/index.js +138 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-MMLHISQV.js → inbox-57NHQHY3.js} +3 -3
- package/dist/index.js +540 -81
- package/dist/index.js.map +1 -1
- package/dist/migrate-43PUK25G.js +20 -0
- package/dist/{pool-YAIBE2L5.js → pool-JQC5JWIZ.js} +3 -3
- package/dist/pool-JQC5JWIZ.js.map +1 -0
- package/dist/snapshot-ZBCSX67Y.js +15 -0
- package/dist/snapshot-ZBCSX67Y.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-ON4MSU6E.js.map +0 -1
- /package/dist/{chunk-4UE74TUB.js.map → chunk-63FUJLIF.js.map} +0 -0
- /package/dist/{chunk-LLD7LUNN.js.map → chunk-IFTFDHZX.js.map} +0 -0
- /package/dist/{chunk-SVP2NDRF.js.map → chunk-MXJPX3FM.js.map} +0 -0
- /package/dist/{inbox-MMLHISQV.js.map → inbox-57NHQHY3.js.map} +0 -0
- /package/dist/{pool-YAIBE2L5.js.map → migrate-43PUK25G.js.map} +0 -0
|
@@ -3,13 +3,18 @@ import {
|
|
|
3
3
|
loadPool,
|
|
4
4
|
normalizeCategory,
|
|
5
5
|
seedsFilePath
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-MXJPX3FM.js";
|
|
7
7
|
import {
|
|
8
|
+
atomicWriteJsonSync
|
|
9
|
+
} from "./chunk-VIZNIQLK.js";
|
|
10
|
+
import {
|
|
11
|
+
loadEncryptedText,
|
|
8
12
|
loadJson,
|
|
9
13
|
readText,
|
|
14
|
+
saveEncryptedText,
|
|
10
15
|
saveJson,
|
|
11
16
|
writeText
|
|
12
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-IFTFDHZX.js";
|
|
13
18
|
|
|
14
19
|
// src/core/path-guard.ts
|
|
15
20
|
import fs from "fs";
|
|
@@ -71,80 +76,6 @@ function createPathGuard(workspaceDir) {
|
|
|
71
76
|
return guard;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
// src/core/audit-log.ts
|
|
75
|
-
import fs3 from "fs";
|
|
76
|
-
import path3 from "path";
|
|
77
|
-
|
|
78
|
-
// src/core/atomic-fs.ts
|
|
79
|
-
import fs2 from "fs";
|
|
80
|
-
import path2 from "path";
|
|
81
|
-
import { randomUUID, randomFillSync } from "crypto";
|
|
82
|
-
function tmpSibling(target, tag = "w") {
|
|
83
|
-
return path2.join(
|
|
84
|
-
path2.dirname(target),
|
|
85
|
-
`.${path2.basename(target)}.${tag}-${randomUUID().slice(0, 8)}.tmp`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
function atomicWriteFileSync(target, data) {
|
|
89
|
-
const tmp = tmpSibling(target);
|
|
90
|
-
try {
|
|
91
|
-
fs2.writeFileSync(tmp, data);
|
|
92
|
-
fs2.renameSync(tmp, target);
|
|
93
|
-
} finally {
|
|
94
|
-
fs2.rmSync(tmp, { force: true });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function atomicWriteJsonSync(target, value) {
|
|
98
|
-
atomicWriteFileSync(target, JSON.stringify(value, null, 2));
|
|
99
|
-
}
|
|
100
|
-
function shredFileSync(target, passes = 3) {
|
|
101
|
-
const stat = fs2.statSync(target);
|
|
102
|
-
if (!stat.isFile()) throw new Error(`shred: not a file: ${target}`);
|
|
103
|
-
const buf = Buffer.alloc(Math.max(stat.size, 1));
|
|
104
|
-
for (let i = 0; i < passes; i++) {
|
|
105
|
-
randomFillSync(buf);
|
|
106
|
-
fs2.writeFileSync(target, buf);
|
|
107
|
-
}
|
|
108
|
-
fs2.rmSync(target, { force: true });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/core/audit-log.ts
|
|
112
|
-
function appendAuditLine(file, event) {
|
|
113
|
-
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
114
|
-
const line = JSON.stringify({ ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
115
|
-
fs3.appendFileSync(file, line + "\n", "utf8");
|
|
116
|
-
}
|
|
117
|
-
function readAuditLines(file) {
|
|
118
|
-
if (!fs3.existsSync(file)) return [];
|
|
119
|
-
const out = [];
|
|
120
|
-
const raw = fs3.readFileSync(file, "utf8");
|
|
121
|
-
for (const line of raw.split("\n")) {
|
|
122
|
-
const trimmed = line.trim();
|
|
123
|
-
if (!trimmed) continue;
|
|
124
|
-
try {
|
|
125
|
-
out.push(JSON.parse(trimmed));
|
|
126
|
-
} catch {
|
|
127
|
-
out.push({ ts: "", corrupt: true, raw: trimmed.slice(0, 200) });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
function pruneAuditFile(file, maxAgeMs, now = Date.now()) {
|
|
133
|
-
if (!fs3.existsSync(file)) return 0;
|
|
134
|
-
const lines = readAuditLines(file);
|
|
135
|
-
const kept = lines.filter((e) => {
|
|
136
|
-
const ev = e;
|
|
137
|
-
const ts = Date.parse(ev.ts ?? "");
|
|
138
|
-
if (!Number.isFinite(ts)) return true;
|
|
139
|
-
return now - ts <= maxAgeMs;
|
|
140
|
-
});
|
|
141
|
-
const removed = lines.length - kept.length;
|
|
142
|
-
if (removed === 0) return 0;
|
|
143
|
-
const body = kept.map((e) => JSON.stringify(e)).join("\n");
|
|
144
|
-
atomicWriteFileSync(file, body ? body + "\n" : "");
|
|
145
|
-
return removed;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
79
|
// src/config/schema.ts
|
|
149
80
|
var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
150
81
|
function isPlainObject(v) {
|
|
@@ -162,6 +93,7 @@ function assertPolicy(input) {
|
|
|
162
93
|
fail("heartbeat.intervalMin must be a number in [1, 1440]");
|
|
163
94
|
}
|
|
164
95
|
if (typeof hb.idleMode !== "boolean") fail("heartbeat.idleMode must be boolean");
|
|
96
|
+
if (typeof hb.archiveRotatedHome !== "boolean") fail("heartbeat.archiveRotatedHome must be boolean");
|
|
165
97
|
const g = p.gate;
|
|
166
98
|
if (!isPlainObject(g)) fail("gate missing");
|
|
167
99
|
if (typeof g.maxDailySend !== "number" || g.maxDailySend < 0) fail("gate.maxDailySend must be >= 0");
|
|
@@ -207,6 +139,12 @@ function assertPolicy(input) {
|
|
|
207
139
|
if (typeof pr.volatileDays !== "number" || pr.volatileDays < 1) fail("profile.volatileDays must be >= 1");
|
|
208
140
|
if (typeof pr.stableLowActivityDays !== "number" || pr.stableLowActivityDays < 1) fail("profile.stableLowActivityDays must be >= 1");
|
|
209
141
|
if (typeof pr.psyEnabled !== "boolean") fail("profile.psyEnabled must be boolean");
|
|
142
|
+
const ob = p.observe;
|
|
143
|
+
if (!isPlainObject(ob)) fail("observe missing");
|
|
144
|
+
if (typeof ob.maxChars !== "number" || ob.maxChars < 20 || ob.maxChars > 2e3) fail("observe.maxChars must be in [20, 2000]");
|
|
145
|
+
if (typeof ob.perBeat !== "number" || ob.perBeat < 1 || ob.perBeat > 100) fail("observe.perBeat must be in [1, 100]");
|
|
146
|
+
if (!isPlainObject(p.weekly)) fail("weekly missing");
|
|
147
|
+
if (typeof p.weekly.enabled !== "boolean") fail("weekly.enabled must be boolean");
|
|
210
148
|
const r = p.retention;
|
|
211
149
|
if (!isPlainObject(r)) fail("retention missing");
|
|
212
150
|
if (typeof r.envPulseHours !== "number" || typeof r.decisionLogDays !== "number") fail("retention fields missing");
|
|
@@ -223,23 +161,23 @@ function deepMerge(base, override) {
|
|
|
223
161
|
}
|
|
224
162
|
|
|
225
163
|
// src/config/load.ts
|
|
226
|
-
import
|
|
227
|
-
import
|
|
164
|
+
import fs2 from "fs";
|
|
165
|
+
import path2 from "path";
|
|
228
166
|
var USER_POLICY_FILE = "policy.json";
|
|
229
167
|
function loadPolicy(guard, configDir, settingsDir) {
|
|
230
|
-
const factoryPath =
|
|
168
|
+
const factoryPath = path2.join(configDir, "policy.json");
|
|
231
169
|
let factoryRaw;
|
|
232
170
|
try {
|
|
233
|
-
factoryRaw = JSON.parse(
|
|
171
|
+
factoryRaw = JSON.parse(fs2.readFileSync(factoryPath, "utf8"));
|
|
234
172
|
} catch (e) {
|
|
235
173
|
throw new Error(`factory policy unreadable at ${factoryPath}: ${String(e)}`);
|
|
236
174
|
}
|
|
237
175
|
assertPolicy(factoryRaw);
|
|
238
|
-
const userPath = guard.assert(
|
|
176
|
+
const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
|
|
239
177
|
let merged = factoryRaw;
|
|
240
|
-
if (
|
|
178
|
+
if (fs2.existsSync(userPath)) {
|
|
241
179
|
try {
|
|
242
|
-
const userRaw = JSON.parse(
|
|
180
|
+
const userRaw = JSON.parse(fs2.readFileSync(userPath, "utf8"));
|
|
243
181
|
merged = deepMerge(factoryRaw, userRaw);
|
|
244
182
|
} catch (e) {
|
|
245
183
|
throw new Error(`user policy layer unparseable at ${userPath}: ${String(e)}`);
|
|
@@ -249,26 +187,26 @@ function loadPolicy(guard, configDir, settingsDir) {
|
|
|
249
187
|
return merged;
|
|
250
188
|
}
|
|
251
189
|
function updateUserPolicy(guard, settingsDir, patch) {
|
|
252
|
-
const userPath = guard.assert(
|
|
190
|
+
const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
|
|
253
191
|
let user = {};
|
|
254
192
|
try {
|
|
255
|
-
user = JSON.parse(
|
|
193
|
+
user = JSON.parse(fs2.readFileSync(userPath, "utf8"));
|
|
256
194
|
if (!user || typeof user !== "object" || Array.isArray(user)) user = {};
|
|
257
195
|
} catch {
|
|
258
196
|
user = {};
|
|
259
197
|
}
|
|
260
198
|
const merged = deepMerge(user, patch);
|
|
261
|
-
|
|
262
|
-
|
|
199
|
+
fs2.mkdirSync(path2.dirname(userPath), { recursive: true });
|
|
200
|
+
fs2.writeFileSync(userPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
263
201
|
return merged;
|
|
264
202
|
}
|
|
265
203
|
|
|
266
204
|
// src/ledger/ledger.ts
|
|
267
|
-
import
|
|
268
|
-
import { randomUUID
|
|
205
|
+
import path3 from "path";
|
|
206
|
+
import { randomUUID } from "crypto";
|
|
269
207
|
var DAY_MS = 864e5;
|
|
270
208
|
function ledgerFilePath(dataDir) {
|
|
271
|
-
return
|
|
209
|
+
return path3.join(dataDir, "ledger.md");
|
|
272
210
|
}
|
|
273
211
|
var LINE_RE = /^- \[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\]\[(open|done)\]\[#([0-9a-f]{6})\] (.*)$/;
|
|
274
212
|
function renderEntry(e) {
|
|
@@ -294,7 +232,7 @@ function appendEntry(guard, file, text, now = Date.now()) {
|
|
|
294
232
|
const d = new Date(now);
|
|
295
233
|
const pad = (n) => String(n).padStart(2, "0");
|
|
296
234
|
const entry = {
|
|
297
|
-
id:
|
|
235
|
+
id: randomUUID().slice(0, 6),
|
|
298
236
|
date: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`,
|
|
299
237
|
time: `${pad(d.getHours())}:${pad(d.getMinutes())}`,
|
|
300
238
|
status: "open",
|
|
@@ -311,9 +249,10 @@ function markDone(guard, file, key, now = Date.now()) {
|
|
|
311
249
|
if (!target) return null;
|
|
312
250
|
const d = new Date(now);
|
|
313
251
|
const pad = (n) => String(n).padStart(2, "0");
|
|
252
|
+
const doneDate = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
314
253
|
const out = rawLines.map((line) => {
|
|
315
254
|
if (line.includes(`#${target.id}] `)) {
|
|
316
|
-
return `- [${
|
|
255
|
+
return `- [${doneDate} ${pad(d.getHours())}:${pad(d.getMinutes())}][done][#${target.id}] ${target.text}`;
|
|
317
256
|
}
|
|
318
257
|
return line;
|
|
319
258
|
});
|
|
@@ -330,32 +269,32 @@ function pendingOlderThan(guard, file, days, now = Date.now()) {
|
|
|
330
269
|
}
|
|
331
270
|
|
|
332
271
|
// src/browse/browse.ts
|
|
333
|
-
import
|
|
334
|
-
import
|
|
272
|
+
import fs3 from "fs";
|
|
273
|
+
import path4 from "path";
|
|
335
274
|
var WATCH_THROTTLE_MS = 6 * 36e5;
|
|
336
275
|
var UA = { "User-Agent": "dsh-heartbeat/2.0 (+local; personal companion)" };
|
|
337
276
|
function emptyBrowseState() {
|
|
338
277
|
return { targets: {}, last_check_at: 0, wander: { focusHistory: {}, focusCount: {}, last_wander_at: 0, refillCount: {} } };
|
|
339
278
|
}
|
|
340
279
|
function browseStatePath(paths) {
|
|
341
|
-
return
|
|
280
|
+
return path4.join(paths.dataDir, "browse.json");
|
|
342
281
|
}
|
|
343
282
|
function readJsonFile(file, fallback) {
|
|
344
283
|
try {
|
|
345
|
-
return JSON.parse(
|
|
284
|
+
return JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
346
285
|
} catch {
|
|
347
286
|
return fallback;
|
|
348
287
|
}
|
|
349
288
|
}
|
|
350
289
|
function loadInterests(paths) {
|
|
351
|
-
const userPath =
|
|
352
|
-
if (
|
|
353
|
-
return readJsonFile(
|
|
290
|
+
const userPath = path4.join(paths.settingsDir, "interests.json");
|
|
291
|
+
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { interests: [], _schedule: {} });
|
|
292
|
+
return readJsonFile(path4.join(paths.configDir, "interests.json"), { interests: [], _schedule: {} });
|
|
354
293
|
}
|
|
355
294
|
function loadWatchlist(paths) {
|
|
356
|
-
const userPath =
|
|
357
|
-
if (
|
|
358
|
-
return readJsonFile(
|
|
295
|
+
const userPath = path4.join(paths.settingsDir, "watchlist.json");
|
|
296
|
+
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { targets: [] });
|
|
297
|
+
return readJsonFile(path4.join(paths.configDir, "watchlist.json"), { targets: [] });
|
|
359
298
|
}
|
|
360
299
|
function loadState(guard, paths) {
|
|
361
300
|
return loadJson(guard, browseStatePath(paths)) ?? emptyBrowseState();
|
|
@@ -493,376 +432,139 @@ function browseStatus(guard, paths) {
|
|
|
493
432
|
return loadState(guard, paths);
|
|
494
433
|
}
|
|
495
434
|
|
|
496
|
-
// src/
|
|
497
|
-
import
|
|
498
|
-
import
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
browse: 0.4,
|
|
507
|
-
hand: 1,
|
|
508
|
-
ledger: 0.6
|
|
509
|
-
};
|
|
510
|
-
function emptyProfile() {
|
|
511
|
-
return {
|
|
512
|
-
version: 1,
|
|
513
|
-
partitions: { interest: { entries: [] }, projects: { entries: [] }, comm: { entries: [] }, psy: { entries: [] } }
|
|
514
|
-
};
|
|
435
|
+
// src/notify/notify.ts
|
|
436
|
+
import { spawnSync } from "child_process";
|
|
437
|
+
import path5 from "path";
|
|
438
|
+
function runNotify(paths, args) {
|
|
439
|
+
const r = spawnSync(
|
|
440
|
+
"powershell.exe",
|
|
441
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path5.join(paths.assetsDir, "notify.ps1"), ...args],
|
|
442
|
+
{ timeout: 2e4, encoding: "utf8" }
|
|
443
|
+
);
|
|
444
|
+
return { status: r.status ?? -1, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
515
445
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const userPath = path7.join(paths.settingsDir, "profile-schema.json");
|
|
522
|
-
const file = fs6.existsSync(userPath) ? userPath : path7.join(paths.configDir, "profile-schema.json");
|
|
523
|
-
try {
|
|
524
|
-
const raw = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
525
|
-
if (!raw.partitions) throw new Error("partitions missing");
|
|
526
|
-
return raw;
|
|
527
|
-
} catch (e) {
|
|
528
|
-
throw new Error(`profile-schema unreadable at ${file}: ${String(e)}`);
|
|
529
|
-
}
|
|
446
|
+
function ensureRegistered(paths) {
|
|
447
|
+
const check = runNotify(paths, ["-Check"]);
|
|
448
|
+
if (/REGISTERED: yes/.test(check.out)) return true;
|
|
449
|
+
const reg = runNotify(paths, ["-RegisterOnly"]);
|
|
450
|
+
return reg.status === 0;
|
|
530
451
|
}
|
|
531
|
-
function
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
const t = p.topics[topic];
|
|
535
|
-
if (!t) return { ok: false, reason: `topic not in schema: ${partition}/${topic}`, temporal: "stable" };
|
|
536
|
-
const st = t.subtopics[subTopic];
|
|
537
|
-
if (!st) return { ok: false, reason: `sub_topic not in schema: ${partition}/${topic}/${subTopic}`, temporal: "stable" };
|
|
538
|
-
const allowed = st.allowed && st.allowed.length > 0 ? st.allowed : ["stable"];
|
|
539
|
-
const def = st.default && allowed.includes(st.default) ? st.default : allowed[0];
|
|
540
|
-
if (!nominated) return { ok: true, temporal: def };
|
|
541
|
-
if (!allowed.includes(nominated)) {
|
|
542
|
-
return {
|
|
543
|
-
ok: false,
|
|
544
|
-
reason: `temporal "${nominated}" not allowed for ${partition}/${topic}/${subTopic} (allowed: ${allowed.join("|")})`,
|
|
545
|
-
temporal: def
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
return { ok: true, temporal: nominated };
|
|
452
|
+
function sendNewMessageHint(paths) {
|
|
453
|
+
const r = runNotify(paths, ["-Title", "Heartbeat", "-Message", "\u6709\u65B0\u6D88\u606F"]);
|
|
454
|
+
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
549
455
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
function profileFilePath(dataDir) {
|
|
554
|
-
return path8.join(dataDir, "profile.json");
|
|
456
|
+
function sendWeeklyReadyHint(paths) {
|
|
457
|
+
const r = runNotify(paths, ["-Title", "\u5FC3\u8DF3\u5468\u62A5", "-Message", "\u672C\u671F\u5468\u62A5\u5DF2\u751F\u6210"]);
|
|
458
|
+
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
555
459
|
}
|
|
556
|
-
|
|
557
|
-
|
|
460
|
+
|
|
461
|
+
// src/weekly/report.ts
|
|
462
|
+
import fs4 from "fs";
|
|
463
|
+
import path6 from "path";
|
|
464
|
+
var WEEKLY_INTERVAL_MS = 7 * 864e5;
|
|
465
|
+
function weeklyDirPath(dataDir) {
|
|
466
|
+
return path6.join(dataDir, "weekly");
|
|
558
467
|
}
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
if (!doc || !doc.partitions) return emptyProfile();
|
|
562
|
-
for (const p of PARTITIONS) {
|
|
563
|
-
if (!doc.partitions[p]) doc.partitions[p] = { entries: [] };
|
|
564
|
-
}
|
|
565
|
-
return doc;
|
|
468
|
+
function stateFilePath(dataDir) {
|
|
469
|
+
return path6.join(weeklyDirPath(dataDir), "state.json");
|
|
566
470
|
}
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
return Number.isFinite(t) ? t : 0;
|
|
471
|
+
function reportFileName(endIso) {
|
|
472
|
+
return `report-${endIso.slice(0, 10)}.json`;
|
|
570
473
|
}
|
|
571
|
-
function
|
|
572
|
-
const base = ref.split("#")[0] ?? "";
|
|
573
|
-
if (!base) return false;
|
|
574
|
-
const target = path8.join(dataDir, base);
|
|
474
|
+
function lastWeeklyGeneratedAt(guard, dataDir) {
|
|
575
475
|
try {
|
|
576
|
-
|
|
476
|
+
const raw = JSON.parse(fs4.readFileSync(guard.assert(stateFilePath(dataDir)), "utf8"));
|
|
477
|
+
return Number(raw.lastGeneratedAt) || 0;
|
|
577
478
|
} catch {
|
|
578
|
-
return
|
|
479
|
+
return 0;
|
|
579
480
|
}
|
|
580
481
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
return
|
|
482
|
+
function weeklyDue(guard, dataDir, now = Date.now()) {
|
|
483
|
+
const last = lastWeeklyGeneratedAt(guard, dataDir);
|
|
484
|
+
return now - last >= WEEKLY_INTERVAL_MS;
|
|
584
485
|
}
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
486
|
+
function saveWeeklyReport(guard, dataDir, report) {
|
|
487
|
+
const dir = weeklyDirPath(dataDir);
|
|
488
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
489
|
+
const file = path6.join(dir, reportFileName(report.end));
|
|
490
|
+
saveEncryptedText(guard, file, JSON.stringify(report, null, 1));
|
|
491
|
+
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: Date.parse(report.generatedAt) });
|
|
492
|
+
return file;
|
|
591
493
|
}
|
|
592
|
-
function
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
if (op.op === "ADD") {
|
|
602
|
-
if (op.partition === "psy" && !policy.profile.psyEnabled) {
|
|
603
|
-
rejected.push({ op, reason: "psy partition is disabled" });
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
const check = checkAddAgainstSchema(schema, op.partition, op.topic, op.subTopic, op.temporal);
|
|
607
|
-
if (!check.ok) {
|
|
608
|
-
rejected.push({ op, reason: check.reason });
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (!op.evidence || op.evidence.length === 0) {
|
|
612
|
-
rejected.push({ op, reason: "ADD without evidence (no provenance, axiom 1)" });
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
const badRef = op.evidence.find((e) => !refExists(guard, dataDir, e.ref));
|
|
616
|
-
if (badRef) {
|
|
617
|
-
rejected.push({ op, reason: `evidence ref does not resolve: ${badRef.ref}` });
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
const cap = capForKinds(op.evidence.map((e) => e.kind));
|
|
621
|
-
const active = doc.partitions[op.partition].entries.filter((e) => e.validTo === null);
|
|
622
|
-
if (active.length >= policy.profile.partitionCap) {
|
|
623
|
-
rejected.push({ op, reason: `partition ${op.partition} at cap (${policy.profile.partitionCap}); converge first` });
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
dbSeq += 1;
|
|
627
|
-
const entry = {
|
|
628
|
-
id: `p${dbSeq.toString(36)}${randomUUID3().slice(0, 4)}`,
|
|
629
|
-
partition: op.partition,
|
|
630
|
-
topic: op.topic,
|
|
631
|
-
subTopic: op.subTopic,
|
|
632
|
-
content: op.content.trim(),
|
|
633
|
-
confidence: Math.min(op.confidence ?? cap, cap),
|
|
634
|
-
temporal: check.temporal,
|
|
635
|
-
validFrom: nowIso,
|
|
636
|
-
validTo: null,
|
|
637
|
-
supersededBy: null,
|
|
638
|
-
evidence: op.evidence,
|
|
639
|
-
createdAt: nowIso,
|
|
640
|
-
updatedAt: nowIso,
|
|
641
|
-
updateCount: 0
|
|
642
|
-
};
|
|
643
|
-
op.assignedId = entry.id;
|
|
644
|
-
doc.partitions[op.partition].entries.push(entry);
|
|
645
|
-
applied.push(op);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
if (op.op === "UPDATE") {
|
|
649
|
-
const entry = findActive(doc, op.id);
|
|
650
|
-
if (!entry) {
|
|
651
|
-
rejected.push({ op, reason: `unknown or inactive entry: ${op.id}` });
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (op.changes.content !== void 0) entry.content = op.changes.content.trim();
|
|
655
|
-
if (op.changes.confidence !== void 0) {
|
|
656
|
-
const cap = capForKinds(entry.evidence.map((e) => e.kind));
|
|
657
|
-
if (op.changes.confidence > entry.confidence && entry.evidence.length < 2) {
|
|
658
|
-
rejected.push({ op, reason: "confidence upgrade requires a second confirming observation" });
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
entry.confidence = Math.min(op.changes.confidence, cap);
|
|
662
|
-
}
|
|
663
|
-
entry.updatedAt = nowIso;
|
|
664
|
-
entry.updateCount += 1;
|
|
665
|
-
applied.push(op);
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
if (op.op === "INVALIDATE") {
|
|
669
|
-
const entry = findActive(doc, op.id);
|
|
670
|
-
if (!entry) {
|
|
671
|
-
rejected.push({ op, reason: `unknown or inactive entry: ${op.id}` });
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
674
|
-
if (entry.temporal === "volatile") {
|
|
675
|
-
rejected.push({ op, reason: "volatile expiry is code-owned (time-driven), not LLM-nominated" });
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
const hasNewObservation = (op.evidence ?? []).length > 0 && (op.evidence ?? []).some((e) => parseIso(e.at) > parseIso(entry.evidence[entry.evidence.length - 1]?.at ?? ""));
|
|
679
|
-
if (!hasNewObservation) {
|
|
680
|
-
rejected.push({ op, reason: "stable INVALIDATE requires a newer contradicting observation" });
|
|
681
|
-
continue;
|
|
682
|
-
}
|
|
683
|
-
entry.validTo = nowIso;
|
|
684
|
-
entry.supersededBy = null;
|
|
685
|
-
entry.updatedAt = nowIso;
|
|
686
|
-
entry.updateCount += 1;
|
|
687
|
-
if (op.evidence) entry.evidence.push(...op.evidence);
|
|
688
|
-
applied.push(op);
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return { applied, rejected };
|
|
693
|
-
}
|
|
694
|
-
var dbSeq = 0;
|
|
695
|
-
function runDeterministicAging(doc, policy, now) {
|
|
696
|
-
const nowIso = new Date(now).toISOString();
|
|
697
|
-
let volatileExpired = 0;
|
|
698
|
-
let lowActivityMarked = 0;
|
|
699
|
-
for (const p of PARTITIONS) {
|
|
700
|
-
for (const e of doc.partitions[p].entries) {
|
|
701
|
-
if (e.validTo !== null) continue;
|
|
702
|
-
const lastEvidence = Math.max(...e.evidence.map((x) => parseIso(x.at)), parseIso(e.updatedAt));
|
|
703
|
-
if (e.temporal === "volatile") {
|
|
704
|
-
if (now - lastEvidence > policy.profile.volatileDays * DAY_MS2) {
|
|
705
|
-
e.validTo = nowIso;
|
|
706
|
-
e.updatedAt = nowIso;
|
|
707
|
-
e.updateCount += 1;
|
|
708
|
-
volatileExpired += 1;
|
|
709
|
-
}
|
|
710
|
-
} else if (!e.lowActivity && now - lastEvidence > policy.profile.stableLowActivityDays * DAY_MS2) {
|
|
711
|
-
e.lowActivity = true;
|
|
712
|
-
lowActivityMarked += 1;
|
|
713
|
-
}
|
|
494
|
+
function listWeeklyReports(guard, dataDir) {
|
|
495
|
+
const out = [];
|
|
496
|
+
try {
|
|
497
|
+
for (const file of fs4.readdirSync(weeklyDirPath(dataDir))) {
|
|
498
|
+
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) continue;
|
|
499
|
+
const rep = readWeeklyReport(guard, dataDir, file);
|
|
500
|
+
if (!rep) continue;
|
|
501
|
+
out.push({ file, start: rep.start, end: rep.end, generatedAt: rep.generatedAt, source: rep.source });
|
|
714
502
|
}
|
|
503
|
+
} catch {
|
|
715
504
|
}
|
|
716
|
-
return
|
|
505
|
+
return out.sort((a, b) => a.end < b.end ? 1 : -1);
|
|
717
506
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
720
|
-
const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...record });
|
|
721
|
-
const journal = journalFilePath(dataDir);
|
|
507
|
+
function readWeeklyReport(guard, dataDir, file) {
|
|
508
|
+
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) return null;
|
|
722
509
|
try {
|
|
723
|
-
|
|
510
|
+
const raw = loadEncryptedText(guard, path6.join(weeklyDirPath(dataDir), file));
|
|
511
|
+
if (!raw) return null;
|
|
512
|
+
const rep = JSON.parse(raw);
|
|
513
|
+
if (typeof rep.text !== "string") return null;
|
|
514
|
+
return rep;
|
|
724
515
|
} catch {
|
|
725
|
-
|
|
726
|
-
fs7.appendFileSync(guard.assert(journal), line + "\n", "utf8");
|
|
516
|
+
return null;
|
|
727
517
|
}
|
|
728
518
|
}
|
|
729
|
-
function
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
519
|
+
function buildWeeklyPrompt(facts) {
|
|
520
|
+
return [
|
|
521
|
+
"\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",
|
|
522
|
+
"\u89C4\u5219\uFF1A",
|
|
523
|
+
"- \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",
|
|
524
|
+
"- \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",
|
|
525
|
+
"- \u8BED\u6C14\u5E73\u5B9E\u4E2D\u6027\uFF0C\u4E0D\u7528\u611F\u53F9\u53F7\uFF1B\u7BC7\u5E45\u4E0D\u8D85\u8FC7 250 \u5B57\u3002",
|
|
526
|
+
"- \u5F85\u529E\u79EF\u538B\u90A3\u4E00\u8282\u7528\u5546\u91CF\u7684\u8BED\u6C14\u63D0\u4E00\u53E5\uFF0C\u4E0D\u50AC\u4FC3\u3002",
|
|
527
|
+
"- \u76F4\u63A5\u8F93\u51FA\u5468\u62A5\u6B63\u6587\uFF0C\u4E0D\u8981\u8F93\u51FA\u89E3\u91CA\u3001\u6807\u9898\u6216 JSON\u3002",
|
|
528
|
+
"",
|
|
529
|
+
"\u672C\u5468\u4E8B\u5B9E\uFF08JSON\uFF09\uFF1A",
|
|
530
|
+
JSON.stringify(facts, null, 1)
|
|
531
|
+
].join("\n");
|
|
532
|
+
}
|
|
533
|
+
function renderTemplateReport(facts) {
|
|
534
|
+
const d = (iso) => iso.slice(0, 10);
|
|
535
|
+
const lines = [`\u672C\u5468\uFF08${d(facts.windowStart)} ~ ${d(facts.windowEnd)}\uFF09`];
|
|
536
|
+
lines.push(`\u8868\u8FBE ${facts.spoken} \u6B21\uFF0C\u9759\u9ED8 ${facts.silent} \u6B21\u3002`);
|
|
537
|
+
if (facts.silentTopReasons.length > 0) {
|
|
538
|
+
lines.push(`\u9759\u9ED8\u4E3B\u56E0\uFF1A${facts.silentTopReasons.map((r) => `${r.reason}\uFF08${r.count}\uFF09`).join("\u3001")}\u3002`);
|
|
749
539
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
if (e) {
|
|
753
|
-
if (op.changes.content !== void 0) e.content = op.changes.content;
|
|
754
|
-
if (op.changes.confidence !== void 0) e.confidence = op.changes.confidence;
|
|
755
|
-
e.updatedAt = ts;
|
|
756
|
-
e.updateCount += 1;
|
|
757
|
-
}
|
|
758
|
-
return;
|
|
540
|
+
if (facts.profileAdds.length > 0) {
|
|
541
|
+
lines.push(`\u65B0\u8BA4\u8BC6 ${facts.profileAdds.length} \u6761\uFF1A${facts.profileAdds.map((a) => a.content).join("\uFF1B")}\u3002`);
|
|
759
542
|
}
|
|
760
|
-
if (
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
e.updateCount += 1;
|
|
766
|
-
}
|
|
543
|
+
if (facts.ledger.added.length > 0 || facts.ledger.done.length > 0) {
|
|
544
|
+
const parts = [];
|
|
545
|
+
if (facts.ledger.added.length > 0) parts.push(`\u65B0\u589E\u5F85\u529E\uFF1A${facts.ledger.added.join("\u3001")}`);
|
|
546
|
+
if (facts.ledger.done.length > 0) parts.push(`\u5DF2\u89E3\u51B3\uFF1A${facts.ledger.done.join("\u3001")}`);
|
|
547
|
+
lines.push(`${parts.join("\uFF1B")}\u3002`);
|
|
767
548
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const journal = journalFilePath(dataDir);
|
|
771
|
-
const raw = readText(guard, journal, "");
|
|
772
|
-
const doc = emptyProfile();
|
|
773
|
-
let records = 0;
|
|
774
|
-
let truncatedTail = 0;
|
|
775
|
-
const lines = raw.split("\n");
|
|
776
|
-
for (let i = 0; i < lines.length; i++) {
|
|
777
|
-
const trimmed = lines[i].trim();
|
|
778
|
-
if (!trimmed) continue;
|
|
779
|
-
try {
|
|
780
|
-
const rec = JSON.parse(trimmed);
|
|
781
|
-
for (const op of rec.applied ?? []) applyOpPermissive(doc, op, rec.ts);
|
|
782
|
-
records += 1;
|
|
783
|
-
} catch {
|
|
784
|
-
const isLast = lines.slice(i + 1).every((l) => !l.trim());
|
|
785
|
-
if (isLast) {
|
|
786
|
-
truncatedTail = lines.length - i;
|
|
787
|
-
break;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
549
|
+
if (facts.ledger.stale.length > 0) {
|
|
550
|
+
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`);
|
|
790
551
|
}
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
function verifyProfile(guard, dataDir) {
|
|
794
|
-
const replayed = replayJournal(guard, dataDir);
|
|
795
|
-
const onDisk = loadProfile(guard, profileFilePath(dataDir));
|
|
796
|
-
const strip = (doc) => JSON.stringify(doc.partitions, (k, v) => ["id", "supersededBy", "validFrom", "createdAt", "updatedAt", "retiredAt"].includes(k) ? "<norm>" : v);
|
|
797
|
-
const ok = strip(replayed.doc) === strip(onDisk);
|
|
798
|
-
if (ok) return { ok: true, truncatedTail: replayed.truncatedTail, records: replayed.records };
|
|
799
|
-
const diskIds = new Set([...PARTITIONS].flatMap((p) => onDisk.partitions[p].entries.map((e) => e.content)));
|
|
800
|
-
const replayIds = new Set([...PARTITIONS].flatMap((p) => replayed.doc.partitions[p].entries.map((e) => e.content)));
|
|
801
|
-
const onlyDisk = [...diskIds].find((c) => !replayIds.has(c));
|
|
802
|
-
const onlyReplay = [...replayIds].find((c) => !diskIds.has(c));
|
|
803
|
-
return {
|
|
804
|
-
ok: false,
|
|
805
|
-
firstDivergence: {
|
|
806
|
-
id: onlyDisk ?? onlyReplay ?? "(content)",
|
|
807
|
-
expected: onlyReplay ? "absent in journal replay" : "present in journal replay",
|
|
808
|
-
actual: onlyDisk ? "present on disk" : "absent on disk"
|
|
809
|
-
},
|
|
810
|
-
truncatedTail: replayed.truncatedTail,
|
|
811
|
-
records: replayed.records
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
function rebuildProfile(guard, dataDir, opts = {}) {
|
|
815
|
-
const replayed = replayJournal(guard, dataDir);
|
|
816
|
-
const target = profileFilePath(dataDir);
|
|
817
|
-
if (opts.check) {
|
|
818
|
-
const onDisk = loadProfile(guard, target);
|
|
819
|
-
const same = JSON.stringify(onDisk) === JSON.stringify(replayed.doc);
|
|
820
|
-
return {
|
|
821
|
-
ok: same,
|
|
822
|
-
truncatedTail: replayed.truncatedTail,
|
|
823
|
-
records: replayed.records,
|
|
824
|
-
wrote: false,
|
|
825
|
-
diffSummary: same ? "no diff" : "materialized view differs from journal replay"
|
|
826
|
-
};
|
|
552
|
+
if (facts.seeds.added.length > 0) {
|
|
553
|
+
lines.push(`\u7D20\u6750\u6C60\u65B0\u589E ${facts.seeds.added.length} \u6761\uFF0C\u5DF2\u6D88\u8D39 ${facts.seeds.consumed.length} \u6761\u3002`);
|
|
827
554
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
writeText(
|
|
831
|
-
guard,
|
|
832
|
-
path8.join(dataDir, "logs", "rebuild-report.txt"),
|
|
833
|
-
`rebuild truncated ${replayed.truncatedTail} torn line(s) at journal tail; ${replayed.records} records applied
|
|
834
|
-
`
|
|
835
|
-
);
|
|
555
|
+
if (facts.seeds.waiting.length > 0) {
|
|
556
|
+
lines.push(`\u8FD8\u6CA1\u804A\u8FC7\u7684\u7D20\u6750\uFF1A${facts.seeds.waiting.join("\u3001")}\u3002`);
|
|
836
557
|
}
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
import { spawnSync } from "child_process";
|
|
842
|
-
import path9 from "path";
|
|
843
|
-
function runNotify(paths, args) {
|
|
844
|
-
const r = spawnSync(
|
|
845
|
-
"powershell.exe",
|
|
846
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path9.join(paths.assetsDir, "notify.ps1"), ...args],
|
|
847
|
-
{ timeout: 2e4, encoding: "utf8" }
|
|
848
|
-
);
|
|
849
|
-
return { status: r.status ?? -1, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
850
|
-
}
|
|
851
|
-
function ensureRegistered(paths) {
|
|
852
|
-
const check = runNotify(paths, ["-Check"]);
|
|
853
|
-
if (/REGISTERED: yes/.test(check.out)) return true;
|
|
854
|
-
const reg = runNotify(paths, ["-RegisterOnly"]);
|
|
855
|
-
return reg.status === 0;
|
|
856
|
-
}
|
|
857
|
-
function sendNewMessageHint(paths) {
|
|
858
|
-
const r = runNotify(paths, ["-Title", "Heartbeat", "-Message", "\u6709\u65B0\u6D88\u606F"]);
|
|
859
|
-
return r.status === 0 && /TOAST_SENT/.test(r.out);
|
|
558
|
+
if (facts.peakHours.length > 0) {
|
|
559
|
+
lines.push(`\u6D3B\u8DC3\u9AD8\u5CF0\uFF1A${facts.peakHours.join("\u3001")}\u3002`);
|
|
560
|
+
}
|
|
561
|
+
return lines.join("\n");
|
|
860
562
|
}
|
|
861
563
|
|
|
862
564
|
// src/core/preset-install.ts
|
|
863
|
-
import
|
|
565
|
+
import fs5 from "fs";
|
|
864
566
|
import os from "os";
|
|
865
|
-
import
|
|
567
|
+
import path7 from "path";
|
|
866
568
|
import { fileURLToPath } from "url";
|
|
867
569
|
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
868
570
|
var METADATA_FILE = "preset.yml";
|
|
@@ -870,14 +572,14 @@ var BUNDLED_PRESET_ID = "heartbeat";
|
|
|
870
572
|
function bundledPresetDir(moduleUrl, id = BUNDLED_PRESET_ID) {
|
|
871
573
|
let dir;
|
|
872
574
|
try {
|
|
873
|
-
dir =
|
|
575
|
+
dir = path7.dirname(fileURLToPath(moduleUrl));
|
|
874
576
|
} catch {
|
|
875
577
|
return void 0;
|
|
876
578
|
}
|
|
877
579
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
878
|
-
const candidate =
|
|
879
|
-
if (
|
|
880
|
-
const parent =
|
|
580
|
+
const candidate = path7.join(dir, "assets", "presets", id);
|
|
581
|
+
if (fs5.existsSync(path7.join(candidate, COMPOSITION_FILE))) return candidate;
|
|
582
|
+
const parent = path7.dirname(dir);
|
|
881
583
|
if (parent === dir) break;
|
|
882
584
|
dir = parent;
|
|
883
585
|
}
|
|
@@ -887,12 +589,12 @@ function userPresetRoot(roots) {
|
|
|
887
589
|
const found = roots?.find(
|
|
888
590
|
(root) => root?.trust === "user" && typeof root.path === "string" && root.path.length > 0
|
|
889
591
|
);
|
|
890
|
-
return found?.path === void 0 ? void 0 :
|
|
592
|
+
return found?.path === void 0 ? void 0 : path7.resolve(found.path);
|
|
891
593
|
}
|
|
892
594
|
function conventionalUserPresetRoot(env = process.env, home = os.homedir()) {
|
|
893
595
|
const override = env.DSH_HOME?.trim();
|
|
894
|
-
const root = override && override.length > 0 ? override :
|
|
895
|
-
return
|
|
596
|
+
const root = override && override.length > 0 ? override : path7.join(home, ".dsh");
|
|
597
|
+
return path7.join(root, ".agent-presets");
|
|
896
598
|
}
|
|
897
599
|
function installBundledPreset(options) {
|
|
898
600
|
const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;
|
|
@@ -924,12 +626,12 @@ function installBundledPreset(options) {
|
|
|
924
626
|
detail: "the roster mounts no user preset root (includeUserRoot=false)"
|
|
925
627
|
};
|
|
926
628
|
}
|
|
927
|
-
const dir =
|
|
928
|
-
const composition =
|
|
629
|
+
const dir = path7.join(root, id);
|
|
630
|
+
const composition = path7.join(dir, COMPOSITION_FILE);
|
|
929
631
|
try {
|
|
930
|
-
if (
|
|
632
|
+
if (fs5.existsSync(composition)) {
|
|
931
633
|
if (options.force !== true) {
|
|
932
|
-
const drifted = !sameBytes(composition,
|
|
634
|
+
const drifted = !sameBytes(composition, path7.join(bundledDir, COMPOSITION_FILE));
|
|
933
635
|
return {
|
|
934
636
|
action: "exists",
|
|
935
637
|
id,
|
|
@@ -938,7 +640,7 @@ function installBundledPreset(options) {
|
|
|
938
640
|
detail: drifted ? "kept as-is (differs from the bundled template)" : "kept as-is"
|
|
939
641
|
};
|
|
940
642
|
}
|
|
941
|
-
|
|
643
|
+
fs5.copyFileSync(path7.join(bundledDir, COMPOSITION_FILE), composition);
|
|
942
644
|
return {
|
|
943
645
|
action: "restored",
|
|
944
646
|
id,
|
|
@@ -947,11 +649,11 @@ function installBundledPreset(options) {
|
|
|
947
649
|
detail: "composition replaced from the bundled template"
|
|
948
650
|
};
|
|
949
651
|
}
|
|
950
|
-
const existed =
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const metadata =
|
|
954
|
-
if (!
|
|
652
|
+
const existed = fs5.existsSync(dir);
|
|
653
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
654
|
+
fs5.copyFileSync(path7.join(bundledDir, COMPOSITION_FILE), composition);
|
|
655
|
+
const metadata = path7.join(dir, METADATA_FILE);
|
|
656
|
+
if (!fs5.existsSync(metadata)) fs5.copyFileSync(path7.join(bundledDir, METADATA_FILE), metadata);
|
|
955
657
|
return {
|
|
956
658
|
action: existed ? "repaired" : "created",
|
|
957
659
|
id,
|
|
@@ -969,21 +671,21 @@ function describeInstall(result) {
|
|
|
969
671
|
return `preset ${result.id} ${result.action}${where}${why}`;
|
|
970
672
|
}
|
|
971
673
|
function presetStatus(moduleUrl, id = BUNDLED_PRESET_ID, root = conventionalUserPresetRoot()) {
|
|
972
|
-
const dir =
|
|
674
|
+
const dir = path7.join(root, id);
|
|
973
675
|
const bundledDir = bundledPresetDir(moduleUrl, id);
|
|
974
|
-
const installed =
|
|
676
|
+
const installed = fs5.existsSync(path7.join(dir, COMPOSITION_FILE));
|
|
975
677
|
return {
|
|
976
678
|
id,
|
|
977
679
|
dir,
|
|
978
680
|
...bundledDir === void 0 ? {} : { bundledDir },
|
|
979
681
|
installed,
|
|
980
|
-
compositionMatches: installed && bundledDir !== void 0 && sameBytes(
|
|
981
|
-
metadataMatches: bundledDir !== void 0 &&
|
|
682
|
+
compositionMatches: installed && bundledDir !== void 0 && sameBytes(path7.join(dir, COMPOSITION_FILE), path7.join(bundledDir, COMPOSITION_FILE)),
|
|
683
|
+
metadataMatches: bundledDir !== void 0 && fs5.existsSync(path7.join(dir, METADATA_FILE)) && sameBytes(path7.join(dir, METADATA_FILE), path7.join(bundledDir, METADATA_FILE))
|
|
982
684
|
};
|
|
983
685
|
}
|
|
984
686
|
function sameBytes(left, right) {
|
|
985
687
|
try {
|
|
986
|
-
return
|
|
688
|
+
return fs5.readFileSync(left).equals(fs5.readFileSync(right));
|
|
987
689
|
} catch {
|
|
988
690
|
return false;
|
|
989
691
|
}
|
|
@@ -991,10 +693,6 @@ function sameBytes(left, right) {
|
|
|
991
693
|
|
|
992
694
|
export {
|
|
993
695
|
createPathGuard,
|
|
994
|
-
atomicWriteJsonSync,
|
|
995
|
-
shredFileSync,
|
|
996
|
-
appendAuditLine,
|
|
997
|
-
pruneAuditFile,
|
|
998
696
|
deepMerge,
|
|
999
697
|
loadPolicy,
|
|
1000
698
|
updateUserPolicy,
|
|
@@ -1011,16 +709,16 @@ export {
|
|
|
1011
709
|
completeWander,
|
|
1012
710
|
adviseRefillWander,
|
|
1013
711
|
browseStatus,
|
|
1014
|
-
loadProfileSchema,
|
|
1015
|
-
profileFilePath,
|
|
1016
|
-
loadProfile,
|
|
1017
|
-
applyOpsToDoc,
|
|
1018
|
-
runDeterministicAging,
|
|
1019
|
-
persistWithJournal,
|
|
1020
|
-
verifyProfile,
|
|
1021
|
-
rebuildProfile,
|
|
1022
712
|
ensureRegistered,
|
|
1023
713
|
sendNewMessageHint,
|
|
714
|
+
sendWeeklyReadyHint,
|
|
715
|
+
weeklyDirPath,
|
|
716
|
+
weeklyDue,
|
|
717
|
+
saveWeeklyReport,
|
|
718
|
+
listWeeklyReports,
|
|
719
|
+
readWeeklyReport,
|
|
720
|
+
buildWeeklyPrompt,
|
|
721
|
+
renderTemplateReport,
|
|
1024
722
|
BUNDLED_PRESET_ID,
|
|
1025
723
|
userPresetRoot,
|
|
1026
724
|
conventionalUserPresetRoot,
|
|
@@ -1028,4 +726,4 @@ export {
|
|
|
1028
726
|
describeInstall,
|
|
1029
727
|
presetStatus
|
|
1030
728
|
};
|
|
1031
|
-
//# sourceMappingURL=chunk-
|
|
729
|
+
//# sourceMappingURL=chunk-SQJLKP7L.js.map
|