@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.
Files changed (59) hide show
  1. package/README.md +58 -71
  2. package/assets/frontwin.ps1 +1 -0
  3. package/assets/idle.ps1 +1 -0
  4. package/assets/screenpulse.ps1 +4 -2
  5. package/config/profile-schema.json +306 -306
  6. package/cordis.patch.yml +18 -39
  7. package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
  8. package/dist/{chunk-63FUJLIF.js → chunk-3MDBU6WY.js} +17 -2
  9. package/dist/chunk-3MDBU6WY.js.map +1 -0
  10. package/dist/chunk-AJ5HLAL7.js +2477 -0
  11. package/dist/chunk-AJ5HLAL7.js.map +1 -0
  12. package/dist/{chunk-VIZNIQLK.js → chunk-CUOSICYJ.js} +82 -74
  13. package/dist/chunk-CUOSICYJ.js.map +1 -0
  14. package/dist/chunk-IG6YMUHW.js +569 -0
  15. package/dist/chunk-IG6YMUHW.js.map +1 -0
  16. package/dist/chunk-IV2ZWQA3.js +56 -0
  17. package/dist/chunk-IV2ZWQA3.js.map +1 -0
  18. package/dist/{chunk-PQTSXW4F.js → chunk-JDH2LDFS.js} +97 -71
  19. package/dist/chunk-JDH2LDFS.js.map +1 -0
  20. package/dist/chunk-K5Y6JP2B.js +399 -0
  21. package/dist/chunk-K5Y6JP2B.js.map +1 -0
  22. package/dist/{chunk-RB3NLLGF.js → chunk-KB5SMG3F.js} +3 -3
  23. package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
  24. package/dist/chunk-TY6UP74X.js.map +1 -0
  25. package/dist/chunk-WRUTATW4.js +47 -0
  26. package/dist/chunk-WRUTATW4.js.map +1 -0
  27. package/dist/{chunk-IPOZFZPY.js → chunk-XFNCTOW7.js} +48 -10
  28. package/dist/chunk-XFNCTOW7.js.map +1 -0
  29. package/dist/chunk-ZLV4NMFX.js +333 -0
  30. package/dist/chunk-ZLV4NMFX.js.map +1 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.js +565 -497
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/{inbox-57NHQHY3.js → inbox-P26DDMWC.js} +5 -3
  35. package/dist/index.d.ts +550 -0
  36. package/dist/index.js +665 -2730
  37. package/dist/index.js.map +1 -1
  38. package/dist/{migrate-N6NXMZ3G.js → migrate-D4VKAKYT.js} +4 -3
  39. package/dist/orchestrator-EUTZ3KG6.js +62 -0
  40. package/dist/{pool-7I4VACG2.js → pool-UTXQJJ5C.js} +5 -3
  41. package/dist/{snapshot-ZBCSX67Y.js → snapshot-K5VHETTK.js} +5 -4
  42. package/dist/snapshot-K5VHETTK.js.map +1 -0
  43. package/package.json +83 -76
  44. package/config/watchlist.json +0 -9
  45. package/dist/chunk-2SYXKKZF.js +0 -837
  46. package/dist/chunk-2SYXKKZF.js.map +0 -1
  47. package/dist/chunk-63FUJLIF.js.map +0 -1
  48. package/dist/chunk-IFTFDHZX.js +0 -203
  49. package/dist/chunk-IFTFDHZX.js.map +0 -1
  50. package/dist/chunk-IPOZFZPY.js.map +0 -1
  51. package/dist/chunk-J6ZTRFFW.js.map +0 -1
  52. package/dist/chunk-PQTSXW4F.js.map +0 -1
  53. package/dist/chunk-VIZNIQLK.js.map +0 -1
  54. /package/dist/{bindings-XPPSKILN.js.map → bindings-225SI7KC.js.map} +0 -0
  55. /package/dist/{chunk-RB3NLLGF.js.map → chunk-KB5SMG3F.js.map} +0 -0
  56. /package/dist/{inbox-57NHQHY3.js.map → inbox-P26DDMWC.js.map} +0 -0
  57. /package/dist/{migrate-N6NXMZ3G.js.map → migrate-D4VKAKYT.js.map} +0 -0
  58. /package/dist/{pool-7I4VACG2.js.map → orchestrator-EUTZ3KG6.js.map} +0 -0
  59. /package/dist/{snapshot-ZBCSX67Y.js.map → pool-UTXQJJ5C.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vault/migrate.ts"],"sourcesContent":["// Cross-machine migration (v1.8.0). DPAPI binds the sensitive stores to one\r\n// machine + account, so a new machine cannot read them — this module packs\r\n// the memory files into ONE passphrase-encrypted container (AES-256-GCM,\r\n// scrypt-derived key, node:crypto only) that can travel safely, and restores\r\n// them on the other side re-encrypted with the LOCAL DPAPI.\r\n//\r\n// Restore is byte-identical: for each file we record whether it was DPAPI\r\n// encrypted on disk (isEncrypted magic check) and write it back through the\r\n// same path — no per-module write semantics to keep in sync. Existing files\r\n// are backed up before overwrite (rule 4).\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport {\r\n createCipheriv,\r\n createDecipheriv,\r\n randomBytes,\r\n scryptSync,\r\n} from 'node:crypto';\r\nimport type { PathGuard } from '../core/path-guard.js';\r\nimport type { WorkspacePaths } from '../core/paths.js';\r\nimport { isEncrypted, loadEncryptedText, saveEncryptedText } from './vault.js';\r\n\r\nexport const MIGRATE_MAGIC = 'HBMIG1';\r\nexport const MIGRATE_VERSION = 1;\r\n/** scrypt cost for NEW containers (2^14 = Node's default, 16 MiB of work).\r\n * Reading is parameterised by the container's own kdf.N, so raising this\r\n * never orphans an existing export. */\r\nconst KDF_N = 163_84;\r\n/** Hard bounds for a container-supplied N: a hostile/broken container must not\r\n * be able to make the import allocate unbounded memory. */\r\nconst KDF_N_MIN = 1 << 12; // 2^12 = 4 MiB\r\nconst KDF_N_MAX = 1 << 16; // 2^16 = 64 MiB\r\n/** Node's scrypt maxmem defaults to 32 MiB, which N=2^15 already exceeds — a\r\n * legal container with a larger N would fail without an explicit cap. */\r\nconst SCRYPT_MAXMEM = 128 * 1024 * 1024;\r\nconst KEY_LEN = 32;\r\n\r\n/** Memory-relevant runtime files (dataDir-relative). Deliberately NOT here:\r\n * gate.json (machine-local counters + home session id — the new machine\r\n * builds its own) and screen snapshots (per-beat transients, not memory). */\r\nexport const MIGRATE_FILES = [\r\n 'profile.json',\r\n 'profile_snapshot.json',\r\n 'profile_inbox.jsonl',\r\n 'profile_journal.jsonl',\r\n 'profile_rhythm.json',\r\n 'sent.json',\r\n 'browse.json',\r\n 'seeds.jsonl',\r\n 'ledger.md',\r\n 'cursors.json',\r\n // v1.9.0: persona report log + topic preference stats\r\n 'seed_report.jsonl',\r\n 'preference.json',\r\n];\r\n\r\nexport interface MigrateEntry {\r\n path: string; // dataDir-relative\r\n encrypted: boolean; // was DPAPI-encrypted on the source machine\r\n content: string;\r\n}\r\n\r\nexport interface MigrateProgress {\r\n packed: string[];\r\n missing: string[];\r\n}\r\n\r\nfunction collectWeeklyFiles(guard: PathGuard, paths: WorkspacePaths): string[] {\r\n const dir = path.join(paths.dataDir, 'weekly');\r\n try {\r\n return fs.readdirSync(guard.assert(dir))\r\n .filter((f) => f.endsWith('.json'))\r\n .map((f) => path.join('weekly', f).replace(/\\\\/g, '/'));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/** Journal archive shards (snapshot rotation output) travel with the memory. */\r\nfunction collectJournalArchives(guard: PathGuard, paths: WorkspacePaths): string[] {\r\n try {\r\n return fs.readdirSync(guard.assert(paths.dataDir))\r\n .filter((f) => f.startsWith('profile_journal.archive-') && f.endsWith('.jsonl'));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/** Read every migration file. A DPAPI file that fails to decrypt aborts the\r\n * export — a silent half-memory container would be worse than a loud error. */\r\nexport function collectMigrationEntries(guard: PathGuard, paths: WorkspacePaths): { entries: MigrateEntry[]; progress: MigrateProgress } {\r\n const entries: MigrateEntry[] = [];\r\n const packed: string[] = [];\r\n const missing: string[] = [];\r\n const rels = [...MIGRATE_FILES, ...collectJournalArchives(guard, paths), ...collectWeeklyFiles(guard, paths)];\r\n for (const rel of rels) {\r\n const abs = path.join(paths.dataDir, rel);\r\n let absCanon: string;\r\n try {\r\n absCanon = guard.assert(abs);\r\n } catch {\r\n missing.push(rel);\r\n continue;\r\n }\r\n if (!fs.existsSync(absCanon)) {\r\n missing.push(rel);\r\n continue;\r\n }\r\n const encrypted = isEncrypted(absCanon);\r\n const content = encrypted\r\n ? loadEncryptedText(guard, absCanon) ?? (() => { throw new Error(`cannot decrypt ${rel} on this machine`); })()\r\n : fs.readFileSync(absCanon, 'utf8');\r\n entries.push({ path: rel.replace(/\\\\/g, '/'), encrypted, content });\r\n packed.push(rel);\r\n }\r\n return { entries, progress: { packed, missing } };\r\n}\r\n\r\n/** Derive the key with the container's OWN cost parameter. `n` must already\r\n * have passed assertKdfN (export uses the current default). */\r\nfunction deriveKey(passphrase: string, salt: Buffer, n: number): Buffer {\r\n return scryptSync(passphrase, salt, KEY_LEN, { N: n, r: 8, p: 1, maxmem: SCRYPT_MAXMEM });\r\n}\r\n\r\n/** Validate the scrypt cost carried by a container. A missing/absurd N is a\r\n * BROKEN container — reported as such, never as \"wrong passphrase\" (before\r\n * 2026-10-06 the N in the container was ignored and the two were the same\r\n * error, so raising the cost would have orphaned every old export). */\r\nfunction assertKdfN(v: unknown): number {\r\n const n = Number(v);\r\n if (!Number.isInteger(n) || n <= 0 || (n & (n - 1)) !== 0) {\r\n throw new Error('migrate: container has invalid kdf parameters (N must be a power of two)');\r\n }\r\n if (n < KDF_N_MIN || n > KDF_N_MAX) {\r\n throw new Error(`migrate: container kdf N ${n} is outside the supported range [${KDF_N_MIN}, ${KDF_N_MAX}]`);\r\n }\r\n return n;\r\n}\r\n\r\n/** Build the container file content. Everything sensitive lives inside the\r\n * ciphertext; the outer JSON carries only crypto parameters. */\r\nexport function encryptContainer(entries: MigrateEntry[], passphrase: string, now = new Date()): string {\r\n if (!passphrase) throw new Error('migrate: passphrase must not be empty');\r\n const salt = randomBytes(16);\r\n const iv = randomBytes(12);\r\n const key = deriveKey(passphrase, salt, KDF_N);\r\n const cipher = createCipheriv('aes-256-gcm', key, iv);\r\n const plain = Buffer.from(JSON.stringify({ version: MIGRATE_VERSION, createdAt: now.toISOString(), files: entries }), 'utf8');\r\n const data = Buffer.concat([cipher.update(plain), cipher.final()]);\r\n const tag = cipher.getAuthTag();\r\n return JSON.stringify({\r\n magic: MIGRATE_MAGIC,\r\n kdf: { name: 'scrypt', salt: salt.toString('hex'), N: KDF_N },\r\n iv: iv.toString('hex'),\r\n tag: tag.toString('hex'),\r\n data: data.toString('base64'),\r\n }, null, 1) + '\\n';\r\n}\r\n\r\n/** Parse + decrypt a container. Throws (loudly) on a wrong passphrase or a\r\n * corrupted file — never returns partial data. */\r\nexport function decryptContainer(text: string, passphrase: string): { createdAt: string; files: MigrateEntry[] } {\r\n let outer: { magic?: string; kdf?: { salt?: string; N?: unknown }; iv?: string; tag?: string; data?: string };\r\n try {\r\n outer = JSON.parse(text);\r\n } catch {\r\n throw new Error('migrate: not a migration container (bad JSON)');\r\n }\r\n if (outer.magic !== MIGRATE_MAGIC) throw new Error('migrate: not a migration container (bad magic)');\r\n const n = assertKdfN(outer.kdf?.N);\r\n const salt = Buffer.from(String(outer.kdf?.salt ?? ''), 'hex');\r\n const iv = Buffer.from(String(outer.iv ?? ''), 'hex');\r\n const tag = Buffer.from(String(outer.tag ?? ''), 'hex');\r\n const data = Buffer.from(String(outer.data ?? ''), 'base64');\r\n const key = deriveKey(passphrase, salt, n);\r\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\r\n decipher.setAuthTag(tag);\r\n let plain: Buffer;\r\n try {\r\n plain = Buffer.concat([decipher.update(data), decipher.final()]);\r\n } catch {\r\n throw new Error('migrate: wrong passphrase or corrupted container');\r\n }\r\n const inner = JSON.parse(plain.toString('utf8')) as { version: number; createdAt: string; files: MigrateEntry[] };\r\n if (inner.version !== MIGRATE_VERSION) throw new Error(`migrate: unsupported container version ${inner.version}`);\r\n if (!Array.isArray(inner.files)) throw new Error('migrate: container has no file list');\r\n return { createdAt: inner.createdAt, files: inner.files };\r\n}\r\n\r\nexport interface MigrateImportResult {\r\n restored: string[];\r\n backedUp: string[];\r\n skipped: string[];\r\n /** H-21: files put back from their backups after a failed commit phase. */\r\n rolledBack?: string[];\r\n /** H-21: set when the commit phase failed and the import was rolled back. */\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Restore entries into the local dataDir, re-encrypting DPAPI files with the\r\n * LOCAL machine key.\r\n *\r\n * H-21: this used to be a per-file \"back up, then overwrite\" loop with no\r\n * pre-flight and no rollback — a failure halfway through left a mixed memory\r\n * (a new `profile.json` next to a stale `profile_journal.jsonl`). Now it runs\r\n * in two phases: stage every entry next to its target (nothing visible changes\r\n * yet, and a staging failure only skips that entry), then commit each one by\r\n * rename. A commit failure rolls the already-committed files back from their\r\n * backups, so the import is all-or-nothing apart from entries that never\r\n * staged.\r\n */\r\nexport function applyMigrationEntries(guard: PathGuard, paths: WorkspacePaths, files: MigrateEntry[], now = Date.now()): MigrateImportResult {\r\n const restored: string[] = [];\r\n const backedUp: string[] = [];\r\n const skipped: string[] = [];\r\n const stamp = new Date(now).toISOString().replace(/[-:T]/g, '').slice(0, 14);\r\n const staged: Array<{ rel: string; abs: string; incoming: string }> = [];\r\n for (const f of files) {\r\n const rel = String(f.path ?? '');\r\n // String-level rejection BEFORE the path is resolved: the guard's realpath\r\n // check already refuses escapes, but an import must not depend on another\r\n // module staying correct.\r\n if (!rel || rel.includes('..') || path.isAbsolute(rel)) {\r\n skipped.push(rel);\r\n continue;\r\n }\r\n const abs = path.join(paths.dataDir, rel);\r\n let absCanon: string;\r\n try {\r\n absCanon = guard.assert(abs);\r\n } catch {\r\n skipped.push(rel); // path refused by the guard — never write it\r\n continue;\r\n }\r\n const incoming = `${absCanon}.incoming-${stamp}`;\r\n try {\r\n fs.mkdirSync(path.dirname(absCanon), { recursive: true });\r\n if (f.encrypted) saveEncryptedText(guard, incoming, f.content);\r\n else fs.writeFileSync(incoming, f.content, 'utf8');\r\n staged.push({ rel, abs: absCanon, incoming });\r\n } catch {\r\n skipped.push(rel);\r\n try {\r\n fs.rmSync(incoming, { force: true });\r\n } catch {\r\n /* nothing staged */\r\n }\r\n }\r\n }\r\n const committed: Array<{ rel: string; abs: string; backup: string | null }> = [];\r\n try {\r\n for (const s of staged) {\r\n let backup: string | null = null;\r\n if (fs.existsSync(s.abs)) {\r\n backup = `${s.abs}.bak-migrate-${stamp}`;\r\n fs.copyFileSync(s.abs, backup);\r\n backedUp.push(s.rel);\r\n }\r\n fs.renameSync(s.incoming, s.abs);\r\n committed.push({ rel: s.rel, abs: s.abs, backup });\r\n restored.push(s.rel);\r\n }\r\n return { restored, backedUp, skipped };\r\n } catch (e) {\r\n // Roll back so the dataDir never holds a half-migrated mix; staged files\r\n // that were never committed are simply dropped.\r\n const rolledBack: string[] = [];\r\n for (const c of committed.reverse()) {\r\n try {\r\n if (c.backup) fs.renameSync(c.backup, c.abs);\r\n else fs.rmSync(c.abs, { force: true });\r\n rolledBack.push(c.rel);\r\n } catch {\r\n /* best effort — the backup file itself is still on disk */\r\n }\r\n }\r\n for (const s of staged) {\r\n if (committed.some((c) => c.abs === s.abs)) continue;\r\n try {\r\n fs.rmSync(s.incoming, { force: true });\r\n } catch {\r\n /* already gone */\r\n }\r\n }\r\n return { restored: [], backedUp, skipped, rolledBack, error: String(e) };\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,333 @@
1
+ import {
2
+ atomicWriteFileSync
3
+ } from "./chunk-WRUTATW4.js";
4
+
5
+ // src/core/path-guard.ts
6
+ import fs from "fs";
7
+ import path from "path";
8
+ var PathOutsideWorkspaceError = class extends Error {
9
+ constructor(target, workspace) {
10
+ super(`path outside workspace: "${target}" (workspace: "${workspace}")`);
11
+ this.name = "PathOutsideWorkspaceError";
12
+ }
13
+ };
14
+ function canonicalize(target) {
15
+ const abs = path.resolve(target);
16
+ try {
17
+ return fs.realpathSync(abs);
18
+ } catch {
19
+ const tail = [];
20
+ let dir = abs;
21
+ for (; ; ) {
22
+ const base = path.basename(dir);
23
+ const parent = path.dirname(dir);
24
+ if (parent === dir) {
25
+ throw new Error(`cannot canonicalize "${target}": no existing ancestor`);
26
+ }
27
+ tail.push(base);
28
+ dir = parent;
29
+ try {
30
+ const realDir = fs.realpathSync(dir);
31
+ return path.join(realDir, ...tail.reverse());
32
+ } catch {
33
+ continue;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ function isInsideWorkspace(workspaceCanon, targetCanon) {
39
+ const norm = (p) => {
40
+ let n = path.normalize(p).toLowerCase();
41
+ if (!n.endsWith(path.sep)) n += path.sep;
42
+ return n;
43
+ };
44
+ const w = norm(workspaceCanon);
45
+ const t = norm(targetCanon);
46
+ return t === w || t.startsWith(w);
47
+ }
48
+ function createPathGuard(workspaceDir) {
49
+ const workspace = canonicalize(workspaceDir);
50
+ const guard = {
51
+ workspace,
52
+ check(target) {
53
+ const canon = canonicalize(target);
54
+ return isInsideWorkspace(workspace, canon) ? canon : null;
55
+ },
56
+ assert(target) {
57
+ const canon = guard.check(target);
58
+ if (canon === null) throw new PathOutsideWorkspaceError(target, workspace);
59
+ return canon;
60
+ }
61
+ };
62
+ return guard;
63
+ }
64
+
65
+ // src/config/schema.ts
66
+ var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
67
+ function isPlainObject(v) {
68
+ return typeof v === "object" && v !== null && !Array.isArray(v);
69
+ }
70
+ function fail(msg) {
71
+ throw new Error(`policy: ${msg}`);
72
+ }
73
+ function assertPolicy(input) {
74
+ if (!isPlainObject(input)) fail("root must be an object");
75
+ const p = input;
76
+ const hb = p.heartbeat;
77
+ if (!isPlainObject(hb)) fail("heartbeat missing");
78
+ if (typeof hb.intervalMin !== "number" || hb.intervalMin < 1 || hb.intervalMin > 1440) {
79
+ fail("heartbeat.intervalMin must be a number in [1, 1440]");
80
+ }
81
+ if (typeof hb.idleMode !== "boolean") fail("heartbeat.idleMode must be boolean");
82
+ if (typeof hb.archiveRotatedHome !== "boolean") fail("heartbeat.archiveRotatedHome must be boolean");
83
+ const g = p.gate;
84
+ if (!isPlainObject(g)) fail("gate missing");
85
+ if (typeof g.maxDailySend !== "number" || g.maxDailySend < 0) fail("gate.maxDailySend must be >= 0");
86
+ if (typeof g.cooldownMinutes !== "number" || g.cooldownMinutes < 0) fail("gate.cooldownMinutes must be >= 0");
87
+ const qh = g.quietHours;
88
+ if (!isPlainObject(qh)) fail("gate.quietHours missing");
89
+ if (typeof qh.start !== "string" || !HHMM.test(qh.start) || typeof qh.end !== "string" || !HHMM.test(qh.end)) {
90
+ fail('gate.quietHours must be {start:"HH:MM", end:"HH:MM"}');
91
+ }
92
+ const b = p.browse;
93
+ if (!isPlainObject(b)) fail("browse missing");
94
+ if (!Array.isArray(b.windows) || b.windows.length === 0) fail("browse.windows must be a non-empty array");
95
+ for (const w of b.windows) {
96
+ if (!isPlainObject(w)) fail("browse.windows entries must be objects");
97
+ if (typeof w.start !== "string" || !HHMM.test(w.start) || typeof w.end !== "string" || !HHMM.test(w.end)) {
98
+ fail('browse.windows entries must be {start:"HH:MM", end:"HH:MM"}');
99
+ }
100
+ }
101
+ if (typeof b.minIntervalHours !== "number" || b.minIntervalHours <= 0) fail("browse.minIntervalHours must be > 0");
102
+ if (typeof b.maxSeedsPerVisit !== "number" || b.maxSeedsPerVisit < 1) fail("browse.maxSeedsPerVisit must be >= 1");
103
+ const s = p.seeds;
104
+ if (!isPlainObject(s)) fail("seeds missing");
105
+ if (typeof s.maxActive !== "number" || s.maxActive < 1) fail("seeds.maxActive must be >= 1");
106
+ if (!isPlainObject(s.ttlDays)) fail("seeds.ttlDays missing");
107
+ for (const k of ["news", "fandom", "scene", "promise"]) {
108
+ if (typeof s.ttlDays[k] !== "number") fail(`seeds.ttlDays.${k} missing`);
109
+ }
110
+ if (typeof s.coldBenchDays !== "number") fail("seeds.coldBenchDays missing");
111
+ if (typeof s.archiveCap !== "number" || s.archiveCap < 1) fail("seeds.archiveCap must be >= 1");
112
+ if (typeof s.retireAfterUsed !== "number" || s.retireAfterUsed < 1) fail("seeds.retireAfterUsed must be >= 1");
113
+ if (!isPlainObject(s.scoreWeights)) fail("seeds.scoreWeights missing");
114
+ const pr = p.profile;
115
+ if (!isPlainObject(pr)) fail("profile missing");
116
+ const c = pr.consolidation;
117
+ if (!isPlainObject(c)) fail("profile.consolidation missing");
118
+ if (typeof c.minIntervalHours !== "number" || typeof c.inboxBacklog !== "number") fail("profile.consolidation fields missing");
119
+ if (typeof pr.partitionCap !== "number" || pr.partitionCap < 1) fail("profile.partitionCap must be >= 1");
120
+ if (typeof pr.maxOpsPerRun !== "number" || pr.maxOpsPerRun < 1) fail("profile.maxOpsPerRun must be >= 1");
121
+ const cc = pr.confidenceCap;
122
+ if (!isPlainObject(cc)) fail("profile.confidenceCap missing");
123
+ if (typeof cc.chat !== "number" || typeof cc.screen !== "number" || typeof cc.browse !== "number") {
124
+ fail("profile.confidenceCap fields missing");
125
+ }
126
+ if (typeof pr.volatileDays !== "number" || pr.volatileDays < 1) fail("profile.volatileDays must be >= 1");
127
+ if (typeof pr.stableLowActivityDays !== "number" || pr.stableLowActivityDays < 1) fail("profile.stableLowActivityDays must be >= 1");
128
+ if (typeof pr.psyEnabled !== "boolean") fail("profile.psyEnabled must be boolean");
129
+ const ob = p.observe;
130
+ if (!isPlainObject(ob)) fail("observe missing");
131
+ if (typeof ob.maxChars !== "number" || ob.maxChars < 20 || ob.maxChars > 2e3) fail("observe.maxChars must be in [20, 2000]");
132
+ if (typeof ob.perBeat !== "number" || ob.perBeat < 1 || ob.perBeat > 100) fail("observe.perBeat must be in [1, 100]");
133
+ if (!isPlainObject(p.weekly)) fail("weekly missing");
134
+ if (typeof p.weekly.enabled !== "boolean") fail("weekly.enabled must be boolean");
135
+ const r = p.retention;
136
+ if (!isPlainObject(r)) fail("retention missing");
137
+ if (typeof r.envPulseHours !== "number" || typeof r.decisionLogDays !== "number") fail("retention fields missing");
138
+ }
139
+ function deepMerge(base, override) {
140
+ if (!isPlainObject(base) || !isPlainObject(override)) {
141
+ return override === void 0 ? base : override;
142
+ }
143
+ const out = { ...base };
144
+ for (const [k, v] of Object.entries(override)) {
145
+ out[k] = v === void 0 ? base[k] : deepMerge(base[k], v);
146
+ }
147
+ return out;
148
+ }
149
+
150
+ // src/config/load.ts
151
+ import fs2 from "fs";
152
+ import path2 from "path";
153
+ var USER_POLICY_FILE = "policy.json";
154
+ function loadPolicy(guard, configDir, settingsDir) {
155
+ const factoryPath = path2.join(configDir, "policy.json");
156
+ let factoryRaw;
157
+ try {
158
+ factoryRaw = JSON.parse(fs2.readFileSync(factoryPath, "utf8"));
159
+ } catch (e) {
160
+ throw new Error(`factory policy unreadable at ${factoryPath}: ${String(e)}`);
161
+ }
162
+ assertPolicy(factoryRaw);
163
+ const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
164
+ let merged = factoryRaw;
165
+ if (fs2.existsSync(userPath)) {
166
+ try {
167
+ const userRaw = JSON.parse(fs2.readFileSync(userPath, "utf8"));
168
+ merged = deepMerge(factoryRaw, userRaw);
169
+ } catch (e) {
170
+ throw new Error(`user policy layer unparseable at ${userPath}: ${String(e)}`);
171
+ }
172
+ }
173
+ assertPolicy(merged);
174
+ return merged;
175
+ }
176
+ function updateUserPolicy(guard, settingsDir, patch) {
177
+ const userPath = guard.assert(path2.join(settingsDir, USER_POLICY_FILE));
178
+ let user = {};
179
+ try {
180
+ user = JSON.parse(fs2.readFileSync(userPath, "utf8"));
181
+ if (!user || typeof user !== "object" || Array.isArray(user)) user = {};
182
+ } catch {
183
+ user = {};
184
+ }
185
+ const merged = deepMerge(user, patch);
186
+ fs2.mkdirSync(path2.dirname(userPath), { recursive: true });
187
+ atomicWriteFileSync(userPath, JSON.stringify(merged, null, 2) + "\n");
188
+ return merged;
189
+ }
190
+
191
+ // src/core/preset-install.ts
192
+ import fs3 from "fs";
193
+ import os from "os";
194
+ import path3 from "path";
195
+ import { fileURLToPath } from "url";
196
+ var COMPOSITION_FILE = "agent.cordis.yml";
197
+ var METADATA_FILE = "preset.yml";
198
+ var BUNDLED_PRESET_ID = "heartbeat";
199
+ function bundledPresetDir(moduleUrl, id = BUNDLED_PRESET_ID) {
200
+ let dir;
201
+ try {
202
+ dir = path3.dirname(fileURLToPath(moduleUrl));
203
+ } catch {
204
+ return void 0;
205
+ }
206
+ for (let depth = 0; depth < 5; depth += 1) {
207
+ const candidate = path3.join(dir, "assets", "presets", id);
208
+ if (fs3.existsSync(path3.join(candidate, COMPOSITION_FILE))) return candidate;
209
+ const parent = path3.dirname(dir);
210
+ if (parent === dir) break;
211
+ dir = parent;
212
+ }
213
+ return void 0;
214
+ }
215
+ function userPresetRoot(roots) {
216
+ const found = roots?.find(
217
+ (root) => root?.trust === "user" && typeof root.path === "string" && root.path.length > 0
218
+ );
219
+ return found?.path === void 0 ? void 0 : path3.resolve(found.path);
220
+ }
221
+ function conventionalUserPresetRoot(env = process.env, home = os.homedir()) {
222
+ const override = env.DSH_HOME?.trim();
223
+ const root = override && override.length > 0 ? override : path3.join(home, ".dsh");
224
+ return path3.join(root, ".agent-presets");
225
+ }
226
+ function installBundledPreset(options) {
227
+ const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;
228
+ if (options.enabled === false) {
229
+ return { action: "skipped-disabled", id, detail: "installPreset=false" };
230
+ }
231
+ const bundledDir = bundledPresetDir(options.moduleUrl, BUNDLED_PRESET_ID);
232
+ if (id !== BUNDLED_PRESET_ID) {
233
+ return {
234
+ action: "skipped-custom-id",
235
+ id,
236
+ ...bundledDir === void 0 ? {} : { bundledDir },
237
+ detail: `only "${BUNDLED_PRESET_ID}" ships with the plugin; "${id}" is yours to provide`
238
+ };
239
+ }
240
+ if (bundledDir === void 0) {
241
+ return {
242
+ action: "error",
243
+ id,
244
+ detail: "bundled template not found next to the plugin (assets/presets/heartbeat)"
245
+ };
246
+ }
247
+ const root = options.root ?? (options.rosterKnown ? void 0 : conventionalUserPresetRoot());
248
+ if (root === void 0) {
249
+ return {
250
+ action: "skipped-no-root",
251
+ id,
252
+ bundledDir,
253
+ detail: "the roster mounts no user preset root (includeUserRoot=false)"
254
+ };
255
+ }
256
+ const dir = path3.join(root, id);
257
+ const composition = path3.join(dir, COMPOSITION_FILE);
258
+ try {
259
+ if (fs3.existsSync(composition)) {
260
+ if (options.force !== true) {
261
+ const drifted = !sameBytes(composition, path3.join(bundledDir, COMPOSITION_FILE));
262
+ return {
263
+ action: "exists",
264
+ id,
265
+ dir,
266
+ bundledDir,
267
+ detail: drifted ? "kept as-is (differs from the bundled template)" : "kept as-is"
268
+ };
269
+ }
270
+ fs3.copyFileSync(path3.join(bundledDir, COMPOSITION_FILE), composition);
271
+ return {
272
+ action: "restored",
273
+ id,
274
+ dir,
275
+ bundledDir,
276
+ detail: "composition replaced from the bundled template"
277
+ };
278
+ }
279
+ const existed = fs3.existsSync(dir);
280
+ fs3.mkdirSync(dir, { recursive: true });
281
+ fs3.copyFileSync(path3.join(bundledDir, COMPOSITION_FILE), composition);
282
+ const metadata = path3.join(dir, METADATA_FILE);
283
+ if (!fs3.existsSync(metadata)) fs3.copyFileSync(path3.join(bundledDir, METADATA_FILE), metadata);
284
+ return {
285
+ action: existed ? "repaired" : "created",
286
+ id,
287
+ dir,
288
+ bundledDir,
289
+ detail: existed ? "directory existed without a composition file (it occupied the id as a broken row)" : void 0
290
+ };
291
+ } catch (error) {
292
+ return { action: "error", id, dir, bundledDir, detail: String(error).slice(0, 200) };
293
+ }
294
+ }
295
+ function describeInstall(result) {
296
+ const where = result.dir === void 0 ? "" : ` (${result.dir})`;
297
+ const why = result.detail === void 0 ? "" : ` \u2014 ${result.detail}`;
298
+ return `preset ${result.id} ${result.action}${where}${why}`;
299
+ }
300
+ function presetStatus(moduleUrl, id = BUNDLED_PRESET_ID, root = conventionalUserPresetRoot()) {
301
+ const dir = path3.join(root, id);
302
+ const bundledDir = bundledPresetDir(moduleUrl, id);
303
+ const installed = fs3.existsSync(path3.join(dir, COMPOSITION_FILE));
304
+ return {
305
+ id,
306
+ dir,
307
+ ...bundledDir === void 0 ? {} : { bundledDir },
308
+ installed,
309
+ compositionMatches: installed && bundledDir !== void 0 && sameBytes(path3.join(dir, COMPOSITION_FILE), path3.join(bundledDir, COMPOSITION_FILE)),
310
+ metadataMatches: bundledDir !== void 0 && fs3.existsSync(path3.join(dir, METADATA_FILE)) && sameBytes(path3.join(dir, METADATA_FILE), path3.join(bundledDir, METADATA_FILE))
311
+ };
312
+ }
313
+ function sameBytes(left, right) {
314
+ try {
315
+ return fs3.readFileSync(left).equals(fs3.readFileSync(right));
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ export {
322
+ createPathGuard,
323
+ deepMerge,
324
+ loadPolicy,
325
+ updateUserPolicy,
326
+ BUNDLED_PRESET_ID,
327
+ userPresetRoot,
328
+ conventionalUserPresetRoot,
329
+ installBundledPreset,
330
+ describeInstall,
331
+ presetStatus
332
+ };
333
+ //# sourceMappingURL=chunk-ZLV4NMFX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/path-guard.ts","../src/config/schema.ts","../src/config/load.ts","../src/core/preset-install.ts"],"sourcesContent":["// Path whitelist guard (requirement 8 / design doc §10.2).\n//\n// Every fs write/delete must have its target canonicalized first, then be\n// checked against the canonical workspace prefix with a separator boundary.\n// A raw startsWith check is bypassable via \"..\", symlinks/junctions, and\n// case variants; canonical realpath closes all of those on Windows.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport class PathOutsideWorkspaceError extends Error {\n constructor(target: string, workspace: string) {\n super(`path outside workspace: \"${target}\" (workspace: \"${workspace}\")`);\n this.name = 'PathOutsideWorkspaceError';\n }\n}\n\n/**\n * Resolve to an absolute canonical path. For targets that do not exist yet,\n * realpath the deepest existing ancestor and re-append the virtual tail, so\n * planned files under the workspace validate while `..` escapes still resolve\n * through the real filesystem.\n */\nexport function canonicalize(target: string): string {\n const abs = path.resolve(target);\n try {\n return fs.realpathSync(abs);\n } catch {\n // Walk up from the missing leaf, recording each missing component, until\n // an existing ancestor is found; re-append the missing tail afterwards.\n const tail: string[] = [];\n let dir = abs;\n for (;;) {\n const base = path.basename(dir);\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(`cannot canonicalize \"${target}\": no existing ancestor`);\n }\n tail.push(base);\n dir = parent;\n try {\n const realDir = fs.realpathSync(dir);\n return path.join(realDir, ...tail.reverse());\n } catch {\n continue;\n }\n }\n }\n}\n\n/**\n * True when `targetCanon` equals the workspace dir or lies under it.\n * Comparison is case-insensitive (Windows filesystems) and requires a\n * separator boundary so `D:\\ws-data-evil` does not match workspace `D:\\ws-data`.\n */\nexport function isInsideWorkspace(workspaceCanon: string, targetCanon: string): boolean {\n const norm = (p: string) => {\n let n = path.normalize(p).toLowerCase();\n if (!n.endsWith(path.sep)) n += path.sep;\n return n;\n };\n const w = norm(workspaceCanon);\n const t = norm(targetCanon);\n return t === w || t.startsWith(w);\n}\n\nexport interface PathGuard {\n /** Canonical workspace boundary. */\n readonly workspace: string;\n /** Canonicalize then validate; returns the canonical path or throws. */\n assert(target: string): string;\n /** Canonicalize then validate; returns null instead of throwing. */\n check(target: string): string | null;\n}\n\nexport function createPathGuard(workspaceDir: string): PathGuard {\n const workspace = canonicalize(workspaceDir);\n const guard: PathGuard = {\n workspace,\n check(target: string): string | null {\n const canon = canonicalize(target);\n return isInsideWorkspace(workspace, canon) ? canon : null;\n },\n assert(target: string): string {\n const canon = guard.check(target);\n if (canon === null) throw new PathOutsideWorkspaceError(target, workspace);\n return canon;\n },\n };\n return guard;\n}\n","// Policy shape + runtime validation. Factory defaults live in config/policy.json\r\n// (read-only); the user layer in data/settings/policy.json overrides via deep\r\n// merge (design doc §13, D3).\r\n\r\nexport interface QuietHours {\r\n start: string; // \"HH:MM\"\r\n end: string; // \"HH:MM\"\r\n}\r\n\r\nexport interface BrowseWindow {\r\n start: string;\r\n end: string;\r\n}\r\n\r\nexport interface Policy {\r\n heartbeat: { intervalMin: number; idleMode: boolean; archiveRotatedHome: boolean };\r\n gate: {\r\n maxDailySend: number;\r\n cooldownMinutes: number;\r\n quietHours: QuietHours;\r\n };\r\n browse: {\r\n windows: BrowseWindow[];\r\n minIntervalHours: number;\r\n maxSeedsPerVisit: number;\r\n };\r\n seeds: {\r\n maxActive: number;\r\n ttlDays: { news: number; fandom: number; scene: number; promise: number };\r\n coldBenchDays: number;\r\n /** Archive-area cap (v1.9.0): gc trims archived seeds beyond this, oldest first. */\r\n archiveCap: number;\r\n retireAfterUsed: number;\r\n scoreWeights: { freshness: number; unused: number; confidence: number };\r\n };\r\n profile: {\r\n consolidation: { minIntervalHours: number; inboxBacklog: number };\r\n partitionCap: number;\r\n maxOpsPerRun: number;\r\n confidenceCap: { chat: number; screen: number; browse: number };\r\n volatileDays: number;\r\n stableLowActivityDays: number;\r\n psyEnabled: boolean;\r\n };\r\n /** Observation depth (v1.8.0): how much of each user message enters the inbox. */\r\n observe: { maxChars: number; perBeat: number };\r\n weekly: { enabled: boolean };\r\n retention: { envPulseHours: number; decisionLogDays: number };\r\n}\r\n\r\nconst HHMM = /^([01]\\d|2[0-3]):[0-5]\\d$/;\r\n\r\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\r\n return typeof v === 'object' && v !== null && !Array.isArray(v);\r\n}\r\n\r\nfunction fail(msg: string): never {\r\n throw new Error(`policy: ${msg}`);\r\n}\r\n\r\nexport function assertPolicy(input: unknown): asserts input is Policy {\r\n if (!isPlainObject(input)) fail('root must be an object');\r\n const p = input;\r\n const hb = p.heartbeat;\r\n if (!isPlainObject(hb)) fail('heartbeat missing');\r\n if (typeof hb.intervalMin !== 'number' || hb.intervalMin < 1 || hb.intervalMin > 1440) {\r\n fail('heartbeat.intervalMin must be a number in [1, 1440]');\r\n }\r\n if (typeof hb.idleMode !== 'boolean') fail('heartbeat.idleMode must be boolean');\r\n if (typeof hb.archiveRotatedHome !== 'boolean') fail('heartbeat.archiveRotatedHome must be boolean');\r\n const g = p.gate;\r\n if (!isPlainObject(g)) fail('gate missing');\r\n if (typeof g.maxDailySend !== 'number' || g.maxDailySend < 0) fail('gate.maxDailySend must be >= 0');\r\n if (typeof g.cooldownMinutes !== 'number' || g.cooldownMinutes < 0) fail('gate.cooldownMinutes must be >= 0');\r\n const qh = g.quietHours;\r\n if (!isPlainObject(qh)) fail('gate.quietHours missing');\r\n if (typeof qh.start !== 'string' || !HHMM.test(qh.start) || typeof qh.end !== 'string' || !HHMM.test(qh.end)) {\r\n fail('gate.quietHours must be {start:\"HH:MM\", end:\"HH:MM\"}');\r\n }\r\n const b = p.browse;\r\n if (!isPlainObject(b)) fail('browse missing');\r\n if (!Array.isArray(b.windows) || b.windows.length === 0) fail('browse.windows must be a non-empty array');\r\n for (const w of b.windows) {\r\n if (!isPlainObject(w)) fail('browse.windows entries must be objects');\r\n if (typeof w.start !== 'string' || !HHMM.test(w.start) || typeof w.end !== 'string' || !HHMM.test(w.end)) {\r\n fail('browse.windows entries must be {start:\"HH:MM\", end:\"HH:MM\"}');\r\n }\r\n }\r\n if (typeof b.minIntervalHours !== 'number' || b.minIntervalHours <= 0) fail('browse.minIntervalHours must be > 0');\r\n if (typeof b.maxSeedsPerVisit !== 'number' || b.maxSeedsPerVisit < 1) fail('browse.maxSeedsPerVisit must be >= 1');\r\n const s = p.seeds;\r\n if (!isPlainObject(s)) fail('seeds missing');\r\n if (typeof s.maxActive !== 'number' || s.maxActive < 1) fail('seeds.maxActive must be >= 1');\r\n if (!isPlainObject(s.ttlDays)) fail('seeds.ttlDays missing');\r\n for (const k of ['news', 'fandom', 'scene', 'promise'] as const) {\r\n if (typeof s.ttlDays[k] !== 'number') fail(`seeds.ttlDays.${k} missing`);\r\n }\r\n if (typeof s.coldBenchDays !== 'number') fail('seeds.coldBenchDays missing');\r\n if (typeof s.archiveCap !== 'number' || s.archiveCap < 1) fail('seeds.archiveCap must be >= 1');\r\n if (typeof s.retireAfterUsed !== 'number' || s.retireAfterUsed < 1) fail('seeds.retireAfterUsed must be >= 1');\r\n if (!isPlainObject(s.scoreWeights)) fail('seeds.scoreWeights missing');\r\n const pr = p.profile;\r\n if (!isPlainObject(pr)) fail('profile missing');\r\n const c = pr.consolidation;\r\n if (!isPlainObject(c)) fail('profile.consolidation missing');\r\n if (typeof c.minIntervalHours !== 'number' || typeof c.inboxBacklog !== 'number') fail('profile.consolidation fields missing');\r\n if (typeof pr.partitionCap !== 'number' || pr.partitionCap < 1) fail('profile.partitionCap must be >= 1');\r\n if (typeof pr.maxOpsPerRun !== 'number' || pr.maxOpsPerRun < 1) fail('profile.maxOpsPerRun must be >= 1');\r\n const cc = pr.confidenceCap;\r\n if (!isPlainObject(cc)) fail('profile.confidenceCap missing');\r\n if (typeof cc.chat !== 'number' || typeof cc.screen !== 'number' || typeof cc.browse !== 'number') {\r\n fail('profile.confidenceCap fields missing');\r\n }\r\n if (typeof pr.volatileDays !== 'number' || pr.volatileDays < 1) fail('profile.volatileDays must be >= 1');\r\n if (typeof pr.stableLowActivityDays !== 'number' || pr.stableLowActivityDays < 1) fail('profile.stableLowActivityDays must be >= 1');\r\n if (typeof pr.psyEnabled !== 'boolean') fail('profile.psyEnabled must be boolean');\r\n const ob = p.observe;\r\n if (!isPlainObject(ob)) fail('observe missing');\r\n if (typeof ob.maxChars !== 'number' || ob.maxChars < 20 || ob.maxChars > 2000) fail('observe.maxChars must be in [20, 2000]');\r\n if (typeof ob.perBeat !== 'number' || ob.perBeat < 1 || ob.perBeat > 100) fail('observe.perBeat must be in [1, 100]');\r\n if (!isPlainObject(p.weekly)) fail('weekly missing');\r\n if (typeof p.weekly.enabled !== 'boolean') fail('weekly.enabled must be boolean');\r\n const r = p.retention;\r\n if (!isPlainObject(r)) fail('retention missing');\r\n if (typeof r.envPulseHours !== 'number' || typeof r.decisionLogDays !== 'number') fail('retention fields missing');\r\n}\r\n\r\n/** Recursive merge: user values win; objects merge, arrays and scalars replace. */\r\nexport function deepMerge<T>(base: T, override: unknown): T {\r\n if (!isPlainObject(base) || !isPlainObject(override)) {\r\n return (override === undefined ? base : (override as T));\r\n }\r\n const out: Record<string, unknown> = { ...base };\r\n for (const [k, v] of Object.entries(override)) {\r\n out[k] = v === undefined ? (base as Record<string, unknown>)[k] : deepMerge((base as Record<string, unknown>)[k], v);\r\n }\r\n return out as T;\r\n}","// Two-layer policy loading (D3): factory defaults (config/policy.json, read-only)\n// + user layer (data/settings/policy.json). Missing user layer is normal; an\n// invalid factory file or user layer is fail-closed (throw at startup).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { assertPolicy, deepMerge, type Policy } from './schema.js';\nimport { atomicWriteFileSync } from '../core/atomic-fs.js';\n\nexport const USER_POLICY_FILE = 'policy.json';\n\nexport function loadPolicy(guard: PathGuard, configDir: string, settingsDir: string): Policy {\n const factoryPath = path.join(configDir, 'policy.json');\n let factoryRaw: unknown;\n try {\n factoryRaw = JSON.parse(fs.readFileSync(factoryPath, 'utf8'));\n } catch (e) {\n throw new Error(`factory policy unreadable at ${factoryPath}: ${String(e)}`);\n }\n assertPolicy(factoryRaw);\n\n const userPath = guard.assert(path.join(settingsDir, USER_POLICY_FILE));\n let merged: Policy = factoryRaw;\n if (fs.existsSync(userPath)) {\n try {\n const userRaw: unknown = JSON.parse(fs.readFileSync(userPath, 'utf8'));\n merged = deepMerge(factoryRaw, userRaw);\n } catch (e) {\n throw new Error(`user policy layer unparseable at ${userPath}: ${String(e)}`);\n }\n }\n assertPolicy(merged);\n return merged;\n}\n\n/**\n * Merge `patch` into the user policy layer (data/settings/policy.json) without\n * touching the factory file. Used by the settings card for policy-backed\n * toggles (psyEnabled, v1.7.0). No full-Policy validation here: the layer is\n * merged over the factory and validated together at the next loadPolicy.\n */\nexport function updateUserPolicy(guard: PathGuard, settingsDir: string, patch: Record<string, unknown>): Record<string, unknown> {\n const userPath = guard.assert(path.join(settingsDir, USER_POLICY_FILE));\n let user: Record<string, unknown> = {};\n try {\n user = JSON.parse(fs.readFileSync(userPath, 'utf8')) as Record<string, unknown>;\n if (!user || typeof user !== 'object' || Array.isArray(user)) user = {};\n } catch {\n user = {}; // fresh layer\n }\n const merged = deepMerge(user, patch) as Record<string, unknown>;\n fs.mkdirSync(path.dirname(userPath), { recursive: true });\n atomicWriteFileSync(userPath, JSON.stringify(merged, null, 2) + '\\n');\n return merged;\n}\n","// Bundled agent-preset installation (contract C13).\n//\n// Why this exists: the heartbeat agent is created by the host `agents` service,\n// which does NOT go through the session-start preset picker. A bare agent joins\n// no preset, and dsh-agent-presets states the consequence verbatim — \"its tools,\n// prompt sections, and skill catalog resolve against the empty global layer\" —\n// so it cannot even see `web_search`. The preset is therefore not optional, and\n// asking the operator to hand-copy two YAML files was the most error-prone step\n// of setup. The plugin now materialises its own bundled template in the roster's\n// USER root on first run.\n//\n// Two properties keep that safe:\n// 1. An existing preset is NEVER overwritten — the composition file belongs to\n// whoever edited it. Only a missing (or ghost) directory is filled in.\n// 2. The target comes from the roster's OWN roots (`agentPresets.roots`,\n// trust === \"user\") rather than a guessed `~/.dsh`, so `$DSH_HOME` and a\n// configured home are honoured without re-deriving them here.\n//\n// Timing: dsh-agent-presets re-scans the filesystem on every read\n// (`list()` -> `discoverPresets` -> `scanRoot`, no cache), so a directory\n// created here is visible to the very next `mount()` — no restart in between.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const COMPOSITION_FILE = 'agent.cordis.yml';\nexport const METADATA_FILE = 'preset.yml';\n/** The template this package ships, in `assets/presets/<id>/`. */\nexport const BUNDLED_PRESET_ID = 'heartbeat';\n\n/** One entry of the roster's root list (`AgentPresets.roots`). */\nexport interface PresetRootLike {\n path?: string;\n trust?: string;\n}\n\nexport type PresetInstallAction =\n | 'exists'\n | 'created'\n | 'repaired'\n | 'restored'\n | 'skipped-disabled'\n | 'skipped-custom-id'\n | 'skipped-no-root'\n | 'error';\n\nexport interface PresetInstallResult {\n action: PresetInstallAction;\n id: string;\n /** Where the preset lives (or would live). */\n dir?: string;\n /** Where the bundled template was read from. */\n bundledDir?: string;\n detail?: string;\n}\n\n/**\n * Locate the bundled template directory by walking up from a module URL.\n * Works from both entry points (`dist/index.js` and `dist/cli/index.js`) and\n * from the pnpm copy of an installed package.\n */\nexport function bundledPresetDir(\n moduleUrl: string,\n id: string = BUNDLED_PRESET_ID,\n): string | undefined {\n let dir: string;\n try {\n dir = path.dirname(fileURLToPath(moduleUrl));\n } catch {\n return undefined;\n }\n for (let depth = 0; depth < 5; depth += 1) {\n const candidate = path.join(dir, 'assets', 'presets', id);\n if (fs.existsSync(path.join(candidate, COMPOSITION_FILE))) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\n/** The roster's user-trust root, which is where locally authored presets live. */\nexport function userPresetRoot(roots?: readonly PresetRootLike[]): string | undefined {\n const found = roots?.find(\n (root) => root?.trust === 'user' && typeof root.path === 'string' && root.path.length > 0,\n );\n return found?.path === undefined ? undefined : path.resolve(found.path);\n}\n\n/**\n * `<dshHome>/.agent-presets` derived from the documented precedence\n * (`$DSH_HOME`, then `~/.dsh`). Only used where no roster is available — the\n * plugin itself prefers {@link userPresetRoot}.\n */\nexport function conventionalUserPresetRoot(\n env: NodeJS.ProcessEnv = process.env,\n home: string = os.homedir(),\n): string {\n const override = env.DSH_HOME?.trim();\n const root = override && override.length > 0 ? override : path.join(home, '.dsh');\n return path.join(root, '.agent-presets');\n}\n\nexport interface InstallPresetOptions {\n /** `import.meta.url` of the calling entry point. */\n moduleUrl: string;\n /** Preset id the heartbeat agent will join (config `agentPreset`). */\n id?: string;\n /** User-trust root reported by the roster. */\n root?: string;\n /** True when the roster answered — then an absent user root is a fact, not a guess. */\n rosterKnown?: boolean;\n /** `false` disables the install (config `installPreset`). */\n enabled?: boolean;\n /** Overwrite an existing composition file with the bundled one. */\n force?: boolean;\n}\n\n/**\n * Materialise the bundled preset in the roster's user root when it is absent.\n * Never destructive: an existing composition file is kept unless `force`.\n */\nexport function installBundledPreset(options: InstallPresetOptions): PresetInstallResult {\n const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;\n if (options.enabled === false) {\n return { action: 'skipped-disabled', id, detail: 'installPreset=false' };\n }\n const bundledDir = bundledPresetDir(options.moduleUrl, BUNDLED_PRESET_ID);\n if (id !== BUNDLED_PRESET_ID) {\n return {\n action: 'skipped-custom-id',\n id,\n ...(bundledDir === undefined ? {} : { bundledDir }),\n detail: `only \"${BUNDLED_PRESET_ID}\" ships with the plugin; \"${id}\" is yours to provide`,\n };\n }\n if (bundledDir === undefined) {\n return {\n action: 'error',\n id,\n detail: 'bundled template not found next to the plugin (assets/presets/heartbeat)',\n };\n }\n const root =\n options.root ?? (options.rosterKnown ? undefined : conventionalUserPresetRoot());\n if (root === undefined) {\n return {\n action: 'skipped-no-root',\n id,\n bundledDir,\n detail: 'the roster mounts no user preset root (includeUserRoot=false)',\n };\n }\n const dir = path.join(root, id);\n const composition = path.join(dir, COMPOSITION_FILE);\n try {\n if (fs.existsSync(composition)) {\n if (options.force !== true) {\n const drifted = !sameBytes(composition, path.join(bundledDir, COMPOSITION_FILE));\n return {\n action: 'exists',\n id,\n dir,\n bundledDir,\n detail: drifted ? 'kept as-is (differs from the bundled template)' : 'kept as-is',\n };\n }\n fs.copyFileSync(path.join(bundledDir, COMPOSITION_FILE), composition);\n return {\n action: 'restored',\n id,\n dir,\n bundledDir,\n detail: 'composition replaced from the bundled template',\n };\n }\n const existed = fs.existsSync(dir);\n fs.mkdirSync(dir, { recursive: true });\n fs.copyFileSync(path.join(bundledDir, COMPOSITION_FILE), composition);\n const metadata = path.join(dir, METADATA_FILE);\n // A directory with a composition but no metadata is legal; only fill a void.\n if (!fs.existsSync(metadata)) fs.copyFileSync(path.join(bundledDir, METADATA_FILE), metadata);\n return {\n action: existed ? 'repaired' : 'created',\n id,\n dir,\n bundledDir,\n detail: existed\n ? 'directory existed without a composition file (it occupied the id as a broken row)'\n : undefined,\n };\n } catch (error) {\n return { action: 'error', id, dir, bundledDir, detail: String(error).slice(0, 200) };\n }\n}\n\n/** One line suitable for the audit log and `ctx.logger`. */\nexport function describeInstall(result: PresetInstallResult): string {\n const where = result.dir === undefined ? '' : ` (${result.dir})`;\n const why = result.detail === undefined ? '' : ` — ${result.detail}`;\n return `preset ${result.id} ${result.action}${where}${why}`;\n}\n\nexport interface PresetStatus {\n id: string;\n dir: string;\n bundledDir?: string;\n installed: boolean;\n compositionMatches: boolean;\n metadataMatches: boolean;\n}\n\n/** Read-only inspection for the CLI (`preset status`). */\nexport function presetStatus(\n moduleUrl: string,\n id: string = BUNDLED_PRESET_ID,\n root: string = conventionalUserPresetRoot(),\n): PresetStatus {\n const dir = path.join(root, id);\n const bundledDir = bundledPresetDir(moduleUrl, id);\n const installed = fs.existsSync(path.join(dir, COMPOSITION_FILE));\n return {\n id,\n dir,\n ...(bundledDir === undefined ? {} : { bundledDir }),\n installed,\n compositionMatches:\n installed && bundledDir !== undefined && sameBytes(path.join(dir, COMPOSITION_FILE), path.join(bundledDir, COMPOSITION_FILE)),\n metadataMatches:\n bundledDir !== undefined &&\n fs.existsSync(path.join(dir, METADATA_FILE)) &&\n sameBytes(path.join(dir, METADATA_FILE), path.join(bundledDir, METADATA_FILE)),\n };\n}\n\nfunction sameBytes(left: string, right: string): boolean {\n try {\n return fs.readFileSync(left).equals(fs.readFileSync(right));\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs","fs","path"]}
@@ -0,0 +1,3 @@
1
+ declare function main(argv: string[]): Promise<number>;
2
+
3
+ export { main };