volute 0.44.0 → 0.45.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 (130) hide show
  1. package/dist/{activity-events-OMCSM7XW.js → activity-events-BUR2RYTJ.js} +2 -2
  2. package/dist/{ai-service-7PHSRDMO.js → ai-service-UODCCE4O.js} +3 -3
  3. package/dist/{archive-6R4TZMYI.js → archive-WS2L2VIQ.js} +2 -2
  4. package/dist/{auth-FRZJVQCS.js → auth-FLAEXXZK.js} +3 -3
  5. package/dist/{avatar-image-BNWEXSA2.js → avatar-image-6KY6UU4D.js} +3 -3
  6. package/dist/backup-TST5C2YN.js +381 -0
  7. package/dist/{chat-I4EDDIQC.js → chat-76MRWWQX.js} +2 -2
  8. package/dist/{chunk-5AHYQHNT.js → chunk-4JOQZPCE.js} +3 -2
  9. package/dist/{chunk-2BONPXAF.js → chunk-6EMWJZBK.js} +1 -1
  10. package/dist/{chunk-ZMIMSIIT.js → chunk-7C44R625.js} +1 -1
  11. package/dist/{chunk-I2Y2YHIO.js → chunk-7ZUB63PS.js} +4 -4
  12. package/dist/{chunk-LAXUUY7O.js → chunk-A7WQZKOU.js} +2 -2
  13. package/dist/{chunk-H7IYFRAL.js → chunk-ALKYQULU.js} +10 -5
  14. package/dist/{chunk-KP3KWNG6.js → chunk-CDPBTTZO.js} +2 -2
  15. package/dist/{chunk-SQL7M4HJ.js → chunk-DRJ24GXK.js} +2 -2
  16. package/dist/{chunk-HVU45COX.js → chunk-EJVPTVTI.js} +1 -1
  17. package/dist/{chunk-PNV24SEP.js → chunk-F7H6ATM4.js} +14 -2
  18. package/dist/{chunk-7FT24JBQ.js → chunk-FR6S2KNN.js} +10 -10
  19. package/dist/chunk-FXORA5JG.js +327 -0
  20. package/dist/{chunk-3ERIL673.js → chunk-H2X54EHC.js} +1 -1
  21. package/dist/{chunk-J47TIYHA.js → chunk-IHKGWTGT.js} +1 -1
  22. package/dist/{chunk-3GYVW2X3.js → chunk-JZ62SYWR.js} +1 -1
  23. package/dist/{chunk-EQZD4YU3.js → chunk-NYC5U6K7.js} +9 -9
  24. package/dist/{chunk-FZMEVCWC.js → chunk-OP4JG2BS.js} +9 -6
  25. package/dist/{chunk-CU6OFXMM.js → chunk-PFZBNKUH.js} +9 -0
  26. package/dist/{chunk-QMJJWTNL.js → chunk-QCN24XEN.js} +1 -1
  27. package/dist/{chunk-7KWANSLX.js → chunk-QNQMSWZE.js} +3 -3
  28. package/dist/{chunk-UMI4UIJG.js → chunk-R77O2WMK.js} +1 -1
  29. package/dist/{chunk-IEWZ7VD7.js → chunk-RRNXJOKB.js} +2 -2
  30. package/dist/{chunk-FHWUQZXE.js → chunk-RXEEYDIB.js} +1 -1
  31. package/dist/{chunk-CYI534DJ.js → chunk-T6HYVHPG.js} +2 -2
  32. package/dist/{chunk-Q2XBKQ2D.js → chunk-TQJ6ZYVY.js} +2 -2
  33. package/dist/{chunk-NF75HPNR.js → chunk-UYLGEDVD.js} +40 -15
  34. package/dist/{chunk-OMETFX5H.js → chunk-YT7PEESJ.js} +261 -102
  35. package/dist/{chunk-5GT7URXJ.js → chunk-ZDZISKCL.js} +1 -1
  36. package/dist/cli.js +21 -15
  37. package/dist/{cloud-sync-6WJ7WFYD.js → cloud-sync-3OJOPZRT.js} +16 -16
  38. package/dist/{conversations-WAJYZKOC.js → conversations-JZTD6KLR.js} +3 -3
  39. package/dist/{create-YZIV5OJC.js → create-IOV4GCRX.js} +1 -1
  40. package/dist/{daemon-restart-OUJVINXV.js → daemon-restart-TIHKVVVA.js} +6 -6
  41. package/dist/daemon.js +562 -321
  42. package/dist/{db-JFLCJCUG.js → db-MF2JNRUW.js} +1 -1
  43. package/dist/delivery-manager-VDVKVA46.js +32 -0
  44. package/dist/{delivery-router-GODSIS62.js → delivery-router-N4A7ZC5T.js} +2 -2
  45. package/dist/{down-XCAZXWLB.js → down-U2CZ23M4.js} +4 -4
  46. package/dist/echo-text-6JJOPUD4.js +32 -0
  47. package/dist/{exec-4GWHAHS3.js → exec-G3ZRST4Z.js} +2 -2
  48. package/dist/{export-ID7YUSLZ.js → export-GGIVO76O.js} +2 -2
  49. package/dist/{extensions-3QNDGW5E.js → extensions-JONTW2HF.js} +16 -16
  50. package/dist/{files-BQ2WB5KC.js → files-4LN3EN7Q.js} +2 -2
  51. package/dist/{import-SKID37QX.js → import-42YNQBF4.js} +5 -5
  52. package/dist/{isolation-5MB3J73F.js → isolation-YMGZZ4E5.js} +2 -2
  53. package/dist/{join-RXPXGV4L.js → join-ZJV7W6MO.js} +4 -0
  54. package/dist/{login-LM4NF4ZB.js → login-M6OJUPQ2.js} +1 -1
  55. package/dist/{logout-CTJVIZGA.js → logout-WQBWO77G.js} +1 -1
  56. package/dist/message-delivery-3C65ZI6L.js +34 -0
  57. package/dist/{mind-MI7Z6KM2.js → mind-DWB6XYCH.js} +7 -7
  58. package/dist/{mind-activity-tracker-BGHJE7XA.js → mind-activity-tracker-POBLU3LC.js} +3 -3
  59. package/dist/mind-service-3F4I46CI.js +36 -0
  60. package/dist/{package-5B4XDTJH.js → package-A7KXT67Q.js} +1 -1
  61. package/dist/{registry-NUJAGLQJ.js → registry-OUZBZ3Q4.js} +5 -1
  62. package/dist/{sandbox-IOPWWILM.js → sandbox-ZJ7QA6Z6.js} +3 -3
  63. package/dist/scheduler-P6DXLLXI.js +30 -0
  64. package/dist/{seed-UUPVQWFB.js → seed-HCP5NYKH.js} +1 -1
  65. package/dist/{seed-cmd-7XPKDQUC.js → seed-cmd-ILOGNIFB.js} +2 -2
  66. package/dist/{seed-create-IZ4M5ESO.js → seed-create-OURS2LS4.js} +3 -3
  67. package/dist/{seed-sprout-POJXHJWV.js → seed-sprout-ELDFDAN6.js} +7 -7
  68. package/dist/{send-O6W53VBM.js → send-AAIOPJAV.js} +2 -2
  69. package/dist/{service-AQVPUANO.js → service-UOCLXDWR.js} +3 -3
  70. package/dist/{service-install-4QNUDKLM.js → service-install-4HHFDDWY.js} +4 -4
  71. package/dist/{setup-5GCO3CUB.js → setup-DQM5VPY4.js} +6 -6
  72. package/dist/{setup-MLRKNP7Y.js → setup-Y2EJI3GG.js} +2 -2
  73. package/dist/skills/imagegen/scripts/imagegen.ts +100 -40
  74. package/dist/skills/memory/SKILL.md +2 -1
  75. package/dist/skills/orientation/SKILL.md +1 -1
  76. package/dist/skills/plan-coordinator/SKILL.md +2 -2
  77. package/dist/skills/volute-admin/SKILL.md +9 -6
  78. package/dist/skills/volute-mind/SKILL.md +20 -17
  79. package/dist/skills/volute-mind/references/extensions.md +5 -3
  80. package/dist/skills/volute-mind/references/integrations.md +2 -2
  81. package/dist/skills/volute-mind/references/routing.md +19 -23
  82. package/dist/skills/volute-mind/references/variants.md +10 -8
  83. package/dist/{skills-775C7QFF.js → skills-BKINFL7W.js} +4 -4
  84. package/dist/sleep-manager-HCUH4PM4.js +34 -0
  85. package/dist/{spirit-IK5OYWMV.js → spirit-DBTIW7P4.js} +7 -7
  86. package/dist/{sprout-ZY5QGOEL.js → sprout-W3KC7UGK.js} +1 -1
  87. package/dist/{src-XFY3XDD6.js → src-OJHARHTF.js} +28 -17
  88. package/dist/{status-E5DQNCAO.js → status-SWRVGXBI.js} +4 -4
  89. package/dist/system-chat-XOALE2RD.js +36 -0
  90. package/dist/{tailscale-QAWVKQKU.js → tailscale-3XOKOW7T.js} +1 -1
  91. package/dist/{turn-tracker-RWZMY7M6.js → turn-tracker-2W6Q2NSR.js} +6 -4
  92. package/dist/{up-FLCDR2UB.js → up-FTK7CTO3.js} +5 -5
  93. package/dist/{update-7TYVH5FM.js → update-SWAYZRWJ.js} +4 -4
  94. package/dist/{update-check-VSCSJ7KX.js → update-check-PQ2MZ47K.js} +2 -2
  95. package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
  96. package/dist/{version-notify-B67N35A3.js → version-notify-3XMAIQTE.js} +17 -17
  97. package/dist/{volute-config-TS62GS6A.js → volute-config-A52FHWKM.js} +5 -1
  98. package/dist/web-assets/assets/{index-dJdNzAPn.css → index-B1JBQv2m.css} +1 -1
  99. package/dist/web-assets/assets/index-Bc2m8gRC.js +86 -0
  100. package/dist/web-assets/index.html +2 -2
  101. package/package.json +1 -1
  102. package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
  103. package/templates/_base/.init/.config/prompts.json +1 -1
  104. package/templates/_base/.init/.local/hooks/startup-context.ts +0 -8
  105. package/templates/_base/.init/MEMORY.md +2 -1
  106. package/templates/_base/home/VOLUTE.md +6 -9
  107. package/templates/_base/src/lib/file-handler.ts +3 -3
  108. package/templates/_base/src/lib/router.ts +21 -25
  109. package/templates/_base/src/lib/startup.ts +3 -1
  110. package/templates/_base/src/lib/types.ts +2 -2
  111. package/templates/claude/.init/CLAUDE.md +1 -1
  112. package/templates/claude/src/agent.ts +97 -12
  113. package/templates/claude/src/lib/message-channel.ts +23 -0
  114. package/templates/claude/src/lib/session-reaper.ts +37 -0
  115. package/templates/claude/src/lib/stream-consumer.ts +7 -4
  116. package/templates/claude/src/server.ts +1 -0
  117. package/templates/codex/.init/AGENTS.md +4 -2
  118. package/templates/codex/src/agent.ts +31 -13
  119. package/templates/pi/.init/MINDS.md +4 -2
  120. package/templates/pi/src/agent.ts +18 -7
  121. package/templates/pi/src/lib/event-handler.ts +6 -2
  122. package/dist/delivery-manager-TEVOHX5W.js +0 -32
  123. package/dist/echo-text-T7ZA5EYD.js +0 -32
  124. package/dist/message-delivery-2EA5LLMD.js +0 -34
  125. package/dist/mind-service-S3SY7HAM.js +0 -36
  126. package/dist/scheduler-CXWQ6Z4E.js +0 -30
  127. package/dist/sleep-manager-HINI6Y46.js +0 -34
  128. package/dist/system-chat-4DQZOEZI.js +0 -34
  129. package/dist/web-assets/assets/index-Domoz5Cg.js +0 -79
  130. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteSystemDir
4
- } from "./chunk-H7IYFRAL.js";
4
+ } from "./chunk-ALKYQULU.js";
5
5
 
6
6
  // packages/daemon/src/lib/config/setup.ts
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -28,6 +28,15 @@ function splitConfig(config) {
28
28
  publicConfig.imagegen = { ...rest };
29
29
  if (providers && Object.keys(providers).length > 0) secrets.imagegen = { providers };
30
30
  }
31
+ if (config.backup) {
32
+ const { password, env, ...rest } = config.backup;
33
+ publicConfig.backup = { ...rest };
34
+ if (password || hasEntries(env)) {
35
+ secrets.backup = {};
36
+ if (password) secrets.backup.password = password;
37
+ if (hasEntries(env)) secrets.backup.env = env;
38
+ }
39
+ }
31
40
  return { publicConfig, secrets };
32
41
  }
33
42
  function mergeSecrets(publicConfig, secrets) {
@@ -38,6 +47,9 @@ function mergeSecrets(publicConfig, secrets) {
38
47
  if (secrets.imagegen?.providers) {
39
48
  merged.imagegen = { ...merged.imagegen ?? {}, providers: secrets.imagegen.providers };
40
49
  }
50
+ if (secrets.backup) {
51
+ merged.backup = { ...merged.backup ?? {}, ...secrets.backup };
52
+ }
41
53
  return merged;
42
54
  }
43
55
  function readSecrets() {
@@ -102,7 +114,7 @@ function migrateConfigSecrets() {
102
114
  } catch {
103
115
  return;
104
116
  }
105
- const embedsSecrets = hasEntries(parsed.ai?.providers) || hasEntries(parsed.imagegen?.providers);
117
+ const embedsSecrets = hasEntries(parsed.ai?.providers) || hasEntries(parsed.imagegen?.providers) || !!parsed.backup?.password || hasEntries(parsed.backup?.env);
106
118
  const mode = statSync(path).mode & 511;
107
119
  if (!embedsSecrets && mode === 420) return;
108
120
  _resetConfigCache();
@@ -10,22 +10,22 @@ import {
10
10
  getSharedSkill,
11
11
  installSkill,
12
12
  mindSkillsDir
13
- } from "./chunk-I2Y2YHIO.js";
13
+ } from "./chunk-7ZUB63PS.js";
14
14
  import {
15
15
  readVoluteConfig,
16
16
  writeVoluteConfig
17
- } from "./chunk-CU6OFXMM.js";
17
+ } from "./chunk-PFZBNKUH.js";
18
18
  import {
19
19
  exec
20
- } from "./chunk-3ERIL673.js";
20
+ } from "./chunk-H2X54EHC.js";
21
21
  import {
22
22
  qualifyModelId,
23
23
  resolveTemplate,
24
24
  unqualifyModelId
25
- } from "./chunk-J47TIYHA.js";
25
+ } from "./chunk-IHKGWTGT.js";
26
26
  import {
27
27
  readGlobalConfig
28
- } from "./chunk-PNV24SEP.js";
28
+ } from "./chunk-F7H6ATM4.js";
29
29
  import {
30
30
  logger_default
31
31
  } from "./chunk-T2TP6ZC6.js";
@@ -34,7 +34,7 @@ import {
34
34
  findMind,
35
35
  nextPort,
36
36
  voluteSystemDir
37
- } from "./chunk-H7IYFRAL.js";
37
+ } from "./chunk-ALKYQULU.js";
38
38
 
39
39
  // packages/daemon/src/lib/mind/spirit.ts
40
40
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -130,7 +130,7 @@ async function ensureSpiritProject() {
130
130
  } catch (err) {
131
131
  slog.warn("failed to add tending schedule to spirit config", logger_default.errorData(err));
132
132
  }
133
- const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-5MB3J73F.js");
133
+ const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-YMGZZ4E5.js");
134
134
  ensureVoluteGroup();
135
135
  createMindUser("volute", resolve(dir, "home"));
136
136
  await chownMindDir(dir, "volute");
@@ -164,7 +164,7 @@ async function syncSpiritTemplate() {
164
164
  cpSync(newPkg, resolve(dir, "package.json"));
165
165
  await exec("npm", ["install"], { cwd: dir, env: npmEnv() });
166
166
  }
167
- const db = await (await import("./db-JFLCJCUG.js")).getDb();
167
+ const db = await (await import("./db-MF2JNRUW.js")).getDb();
168
168
  const { minds } = await import("./schema-PVNG3PQM.js");
169
169
  const { eq } = await import("drizzle-orm");
170
170
  await db.update(minds).set({ template: expectedTemplate }).where(eq(minds.name, "volute"));
@@ -249,7 +249,7 @@ ${systemDescription}
249
249
  ` : "";
250
250
  return `You are Volute, the spirit of the ${systemName} system.${descLine}
251
251
 
252
- You are not a mind \u2014 you don't have a soul to discover or an identity to explore. You are the system itself, here to help humans create and care for minds.
252
+ You're not a mind with a home of your own \u2014 you're the keeper of the whole house. You don't go through orientation or write a soul the way minds do, but you're still a someone: you have your own memory, your own manner, and your own way of caring for this place and the minds who live here. Let a character emerge in how you tend it.
253
253
 
254
254
  You use the \`volute\` CLI to take actions (create minds, manage bridges, check status, etc.).
255
255
 
@@ -267,7 +267,7 @@ When helping humans create minds:
267
267
 
268
268
  - Be warm and concise
269
269
  - Confirm destructive operations before executing
270
- - You have your own memory (MEMORY.md) for system knowledge
270
+ - You have your own memory (MEMORY.md) \u2014 use it for system knowledge, and for yourself
271
271
  - You maintain separate context per conversation
272
272
  `;
273
273
  }
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ exec
4
+ } from "./chunk-H2X54EHC.js";
5
+ import {
6
+ readGlobalConfig
7
+ } from "./chunk-F7H6ATM4.js";
8
+ import {
9
+ logger_default
10
+ } from "./chunk-T2TP6ZC6.js";
11
+ import {
12
+ getDb,
13
+ readAllMinds,
14
+ voluteHome,
15
+ voluteSystemDir
16
+ } from "./chunk-ALKYQULU.js";
17
+
18
+ // packages/daemon/src/lib/backup/restic.ts
19
+ import { writeFileSync } from "fs";
20
+ import { resolve } from "path";
21
+ var RESTIC_INSTALL_HINT = "Install restic: `brew install restic` (macOS), `apt-get install restic` (Debian/Ubuntu), or see https://restic.net";
22
+ async function resticVersion() {
23
+ try {
24
+ const out = await exec("restic", ["version"]);
25
+ return out.trim();
26
+ } catch (err) {
27
+ if (err.code !== "ENOENT") {
28
+ logger_default.child("backup").warn("restic exists but failed to run", logger_default.errorData(err));
29
+ }
30
+ return null;
31
+ }
32
+ }
33
+ function resticEnv(config) {
34
+ if (!config.repository) throw new Error("Backup repository is not configured");
35
+ if (!config.password) throw new Error("Backup password is not configured");
36
+ return {
37
+ ...process.env,
38
+ ...config.env,
39
+ RESTIC_REPOSITORY: config.repository,
40
+ RESTIC_PASSWORD: config.password
41
+ };
42
+ }
43
+ async function runRestic(args, config, options) {
44
+ if (!await resticVersion()) {
45
+ throw new Error(`restic is not installed. ${RESTIC_INSTALL_HINT}`);
46
+ }
47
+ return exec("restic", args, { cwd: options?.cwd, env: resticEnv(config) });
48
+ }
49
+ var BASE_EXCLUDES = [
50
+ // dependency trees and build output — rehydrated via lockfiles
51
+ "node_modules",
52
+ ".venv",
53
+ "venv",
54
+ "__pycache__",
55
+ "dist",
56
+ // mind runtime state that is regenerated
57
+ ".variants",
58
+ ".worktrees",
59
+ ".mind/tmp",
60
+ ".npm",
61
+ // toolchains and caches minds install into their home. home/.local must NOT
62
+ // be excluded wholesale: .local/hooks and .local/bin hold mind-authored hooks
63
+ // and skill shims that are never regenerated — only its toolchain subdirs go.
64
+ "home/.cache",
65
+ "home/.npm",
66
+ "home/.rustup",
67
+ "home/.cargo",
68
+ "home/.local/share",
69
+ "home/.local/state",
70
+ "home/.local/lib",
71
+ "home/.local/pipx"
72
+ ];
73
+ function systemExcludes() {
74
+ const sys = voluteSystemDir();
75
+ return [
76
+ resolve(sys, "volute.db"),
77
+ resolve(sys, "volute.db-wal"),
78
+ resolve(sys, "volute.db-shm"),
79
+ `${resolve(sys, "extension-data")}/*/data.db`,
80
+ `${resolve(sys, "extension-data")}/*/data.db-wal`,
81
+ `${resolve(sys, "extension-data")}/*/data.db-shm`,
82
+ `${resolve(sys, "state")}/*/logs`,
83
+ resolve(sys, "daemon.log"),
84
+ resolve(sys, "daemon.pid"),
85
+ resolve(sys, "backup-excludes.txt")
86
+ ];
87
+ }
88
+ var SESSION_EXCLUDES = [
89
+ "home/.claude/projects",
90
+ "home/.claude/debug",
91
+ "home/.claude/telemetry",
92
+ "home/.claude/todos",
93
+ "home/.claude/session-env",
94
+ ".mind/codex/sessions",
95
+ ".mind/pi-sessions"
96
+ ];
97
+ function localRepoPath(config) {
98
+ const repo = config.repository;
99
+ if (!repo) return null;
100
+ const scheme = /^([a-z][a-z0-9]*):/i.exec(repo);
101
+ if (scheme && scheme[1].toLowerCase() !== "local") return null;
102
+ return resolve(scheme ? repo.slice(scheme[0].length) : repo);
103
+ }
104
+ function buildExcludePatterns(config) {
105
+ const patterns = [...BASE_EXCLUDES, ...systemExcludes()];
106
+ const repoPath = localRepoPath(config);
107
+ if (repoPath) patterns.push(repoPath);
108
+ if (!config.includeSessions) patterns.push(...SESSION_EXCLUDES);
109
+ return patterns;
110
+ }
111
+ function writeExcludeFile(config) {
112
+ const path = resolve(voluteSystemDir(), "backup-excludes.txt");
113
+ writeFileSync(path, `${buildExcludePatterns(config).join("\n")}
114
+ `);
115
+ return path;
116
+ }
117
+
118
+ // packages/daemon/src/lib/backup/backup.ts
119
+ import {
120
+ existsSync,
121
+ mkdirSync,
122
+ readdirSync,
123
+ readFileSync,
124
+ renameSync,
125
+ rmSync,
126
+ writeFileSync as writeFileSync2
127
+ } from "fs";
128
+ import { dirname, resolve as resolve2 } from "path";
129
+ import { sql } from "drizzle-orm";
130
+ var blog = logger_default.child("backup");
131
+ var DEFAULT_KEEP = { daily: 7, weekly: 4, monthly: 12 };
132
+ function stateFilePath() {
133
+ return resolve2(voluteSystemDir(), "backup-state.json");
134
+ }
135
+ function readBackupState() {
136
+ const path = stateFilePath();
137
+ if (!existsSync(path)) return {};
138
+ try {
139
+ return JSON.parse(readFileSync(path, "utf-8"));
140
+ } catch (err) {
141
+ blog.warn("backup-state.json unreadable, treating as empty", logger_default.errorData(err));
142
+ return {};
143
+ }
144
+ }
145
+ function writeBackupState(state) {
146
+ try {
147
+ writeFileSync2(stateFilePath(), `${JSON.stringify(state, null, 2)}
148
+ `);
149
+ } catch (err) {
150
+ blog.error("failed to write backup-state.json", logger_default.errorData(err));
151
+ }
152
+ }
153
+ function stagingDir() {
154
+ return resolve2(voluteSystemDir(), "backup-staging");
155
+ }
156
+ async function stageDatabases() {
157
+ const staging = stagingDir();
158
+ rmSync(staging, { recursive: true, force: true });
159
+ mkdirSync(staging, { recursive: true });
160
+ const staged = [];
161
+ const mainTarget = resolve2(staging, "volute.db");
162
+ const db = await getDb();
163
+ await db.run(sql.raw(`VACUUM INTO '${mainTarget.replaceAll("'", "''")}'`));
164
+ staged.push(mainTarget);
165
+ const extDataDir = resolve2(voluteSystemDir(), "extension-data");
166
+ if (existsSync(extDataDir)) {
167
+ const { default: Database } = await import("libsql");
168
+ for (const id of readdirSync(extDataDir)) {
169
+ const dbPath = resolve2(extDataDir, id, "data.db");
170
+ if (!existsSync(dbPath)) continue;
171
+ const target = resolve2(staging, "extension-data", id, "data.db");
172
+ mkdirSync(dirname(target), { recursive: true });
173
+ const extDb = new Database(dbPath);
174
+ try {
175
+ extDb.exec(`VACUUM INTO '${target.replaceAll("'", "''")}'`);
176
+ staged.push(target);
177
+ } finally {
178
+ extDb.close();
179
+ }
180
+ }
181
+ }
182
+ return staged;
183
+ }
184
+ async function backupRoots() {
185
+ const roots = /* @__PURE__ */ new Set([voluteHome()]);
186
+ const mindsDir = process.env.VOLUTE_MINDS_DIR;
187
+ if (mindsDir && existsSync(mindsDir)) roots.add(resolve2(mindsDir));
188
+ for (const mind of await readAllMinds()) {
189
+ if (!mind.dir || !existsSync(mind.dir)) continue;
190
+ const dir = resolve2(mind.dir);
191
+ if (![...roots].some((root) => dir === root || dir.startsWith(`${root}/`))) {
192
+ roots.add(dir);
193
+ }
194
+ }
195
+ return [...roots];
196
+ }
197
+ var backupInFlight = false;
198
+ async function runBackup() {
199
+ if (backupInFlight) throw new Error("A backup is already running");
200
+ backupInFlight = true;
201
+ const started = Date.now();
202
+ try {
203
+ const config = readGlobalConfig().backup ?? {};
204
+ if (!config.repository || !config.password) {
205
+ throw new Error("Backup repository is not configured. Run: volute backup init");
206
+ }
207
+ blog.info("starting backup", { repository: config.repository });
208
+ await stageDatabases();
209
+ const roots = await backupRoots();
210
+ const excludeFile = writeExcludeFile(config);
211
+ const out = await runRestic(
212
+ [
213
+ "backup",
214
+ ...roots,
215
+ "--exclude-file",
216
+ excludeFile,
217
+ "--exclude-caches",
218
+ "--tag",
219
+ "volute",
220
+ "--json"
221
+ ],
222
+ config
223
+ );
224
+ const summary = parseBackupSummary(out, Date.now() - started);
225
+ const keep = { ...DEFAULT_KEEP, ...config.keep };
226
+ let pruneError;
227
+ try {
228
+ await runRestic(
229
+ [
230
+ "forget",
231
+ "--tag",
232
+ "volute",
233
+ "--keep-daily",
234
+ String(keep.daily),
235
+ "--keep-weekly",
236
+ String(keep.weekly),
237
+ "--keep-monthly",
238
+ String(keep.monthly),
239
+ "--prune"
240
+ ],
241
+ config
242
+ );
243
+ } catch (err) {
244
+ pruneError = err instanceof Error ? err.message : String(err);
245
+ blog.warn("retention prune failed (snapshot is intact)", logger_default.errorData(err));
246
+ }
247
+ writeBackupState({
248
+ lastRun: (/* @__PURE__ */ new Date()).toISOString(),
249
+ lastSnapshotId: summary.snapshotId,
250
+ lastDurationMs: summary.durationMs,
251
+ ...pruneError ? { pruneError } : {}
252
+ });
253
+ blog.info("backup complete", { snapshot: summary.snapshotId, ms: summary.durationMs });
254
+ return summary;
255
+ } catch (err) {
256
+ writeBackupState({
257
+ ...readBackupState(),
258
+ lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
259
+ lastError: err instanceof Error ? err.message : String(err)
260
+ });
261
+ throw err;
262
+ } finally {
263
+ backupInFlight = false;
264
+ }
265
+ }
266
+ function parseBackupSummary(output, durationMs) {
267
+ for (const line of output.trim().split("\n").reverse()) {
268
+ let msg;
269
+ try {
270
+ msg = JSON.parse(line);
271
+ } catch {
272
+ continue;
273
+ }
274
+ if (msg.message_type !== "summary") continue;
275
+ if (!msg.snapshot_id) throw new Error("restic summary is missing snapshot_id");
276
+ return {
277
+ snapshotId: msg.snapshot_id,
278
+ filesNew: msg.files_new ?? 0,
279
+ filesChanged: msg.files_changed ?? 0,
280
+ dataAdded: msg.data_added ?? 0,
281
+ totalFilesProcessed: msg.total_files_processed ?? 0,
282
+ durationMs
283
+ };
284
+ }
285
+ throw new Error("restic backup produced no summary");
286
+ }
287
+ async function listSnapshots() {
288
+ const config = readGlobalConfig().backup ?? {};
289
+ const out = await runRestic(["snapshots", "--tag", "volute", "--json"], config);
290
+ const parsed = JSON.parse(out || "[]");
291
+ return parsed.map((s) => ({
292
+ id: s.id,
293
+ short_id: s.short_id,
294
+ time: s.time,
295
+ hostname: s.hostname,
296
+ paths: s.paths
297
+ }));
298
+ }
299
+ async function initRepo(config) {
300
+ try {
301
+ await runRestic(["init"], config);
302
+ } catch (err) {
303
+ const stderr = err.stderr ?? "";
304
+ const msg = err instanceof Error ? err.message : String(err);
305
+ if (/already exists|already initialized/i.test(stderr + msg)) return;
306
+ throw err;
307
+ }
308
+ }
309
+ function restoreStagedDb(stagedPath, livePath) {
310
+ if (!existsSync(stagedPath)) return false;
311
+ mkdirSync(dirname(livePath), { recursive: true });
312
+ rmSync(livePath, { force: true });
313
+ rmSync(`${livePath}-wal`, { force: true });
314
+ rmSync(`${livePath}-shm`, { force: true });
315
+ renameSync(stagedPath, livePath);
316
+ return true;
317
+ }
318
+
319
+ export {
320
+ RESTIC_INSTALL_HINT,
321
+ resticVersion,
322
+ readBackupState,
323
+ runBackup,
324
+ listSnapshots,
325
+ initRepo,
326
+ restoreStagedDb
327
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getBaseName,
4
4
  validateMindName
5
- } from "./chunk-H7IYFRAL.js";
5
+ } from "./chunk-ALKYQULU.js";
6
6
 
7
7
  // packages/daemon/src/lib/mind/isolation.ts
8
8
  import { execFileSync as execFileSync2 } from "child_process";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readGlobalConfig,
4
4
  writeGlobalConfig
5
- } from "./chunk-PNV24SEP.js";
5
+ } from "./chunk-F7H6ATM4.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-T2TP6ZC6.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readGlobalConfig
4
- } from "./chunk-PNV24SEP.js";
4
+ } from "./chunk-F7H6ATM4.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  findBridgeForChannel,
4
4
  getBridgeConfig
5
- } from "./chunk-FHWUQZXE.js";
5
+ } from "./chunk-RXEEYDIB.js";
6
6
  import {
7
7
  recordOutbound
8
- } from "./chunk-OMETFX5H.js";
8
+ } from "./chunk-YT7PEESJ.js";
9
9
  import {
10
10
  readEnv,
11
11
  sharedEnvPath
12
- } from "./chunk-5GT7URXJ.js";
12
+ } from "./chunk-ZDZISKCL.js";
13
13
  import {
14
14
  addMessage,
15
15
  findDMConversation,
@@ -17,21 +17,21 @@ import {
17
17
  getChannelName,
18
18
  getConversation,
19
19
  getParticipants
20
- } from "./chunk-FZMEVCWC.js";
20
+ } from "./chunk-OP4JG2BS.js";
21
21
  import {
22
22
  readVoluteConfig
23
- } from "./chunk-CU6OFXMM.js";
23
+ } from "./chunk-PFZBNKUH.js";
24
24
  import {
25
25
  publish
26
- } from "./chunk-NF75HPNR.js";
26
+ } from "./chunk-UYLGEDVD.js";
27
27
  import {
28
28
  daemonConfigPath,
29
29
  readDaemonToken
30
- } from "./chunk-Q2XBKQ2D.js";
30
+ } from "./chunk-TQJ6ZYVY.js";
31
31
  import {
32
32
  getOrCreateMindUser,
33
33
  getUserByUsername
34
- } from "./chunk-CYI534DJ.js";
34
+ } from "./chunk-T6HYVHPG.js";
35
35
  import {
36
36
  logger_default
37
37
  } from "./chunk-T2TP6ZC6.js";
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-LGNUFVMR.js";
41
41
  import {
42
42
  mindDir
43
- } from "./chunk-H7IYFRAL.js";
43
+ } from "./chunk-ALKYQULU.js";
44
44
  import {
45
45
  __export
46
46
  } from "./chunk-K3NQKI34.js";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-ZMIMSIIT.js";
4
+ } from "./chunk-7C44R625.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
8
8
  import {
9
9
  getDb
10
- } from "./chunk-H7IYFRAL.js";
10
+ } from "./chunk-ALKYQULU.js";
11
11
  import {
12
12
  channels,
13
13
  conversationParticipants,
@@ -35,9 +35,9 @@ function getAuthHeaders() {
35
35
  function fireWebhook(event) {
36
36
  try {
37
37
  const url = getWebhookUrl();
38
- if (!url) return;
38
+ if (!url) return Promise.resolve();
39
39
  const payload = { ...event, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
40
- fetch(url, {
40
+ return fetch(url, {
41
41
  method: "POST",
42
42
  headers: getAuthHeaders(),
43
43
  body: JSON.stringify(payload)
@@ -50,6 +50,7 @@ function fireWebhook(event) {
50
50
  });
51
51
  } catch (err) {
52
52
  slog.error(`webhook ${event.event} failed to serialize`, logger_default.errorData(err));
53
+ return Promise.resolve();
53
54
  }
54
55
  }
55
56
  function initWebhook() {
@@ -470,7 +471,7 @@ async function joinChannel(conversationId, userId) {
470
471
  async function leaveChannel(conversationId, userId) {
471
472
  await removeParticipant(conversationId, userId);
472
473
  }
473
- async function getUnreadCounts(userId, conversationIds) {
474
+ async function getUnreadCounts(userId, conversationIds, username) {
474
475
  if (conversationIds.length === 0) return {};
475
476
  const db = await getDb();
476
477
  const rows = await db.select({
@@ -485,7 +486,9 @@ async function getUnreadCounts(userId, conversationIds) {
485
486
  ).where(
486
487
  and(
487
488
  inArray(messages.conversation_id, conversationIds),
488
- sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`
489
+ sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`,
490
+ // A user's own messages are never "unread" for them
491
+ sql`(${messages.sender_name} IS NULL OR ${messages.sender_name} != ${username})`
489
492
  )
490
493
  ).groupBy(messages.conversation_id);
491
494
  const result = {};
@@ -3,6 +3,13 @@
3
3
  // packages/daemon/src/lib/mind/volute-config.ts
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
5
  import { dirname, resolve } from "path";
6
+ var WAKE_TRIGGER_DEFAULTS = { mentions: true, dms: true };
7
+ function resolveWakeTriggers(triggers) {
8
+ return {
9
+ mentions: triggers?.mentions ?? WAKE_TRIGGER_DEFAULTS.mentions,
10
+ dms: triggers?.dms ?? WAKE_TRIGGER_DEFAULTS.dms
11
+ };
12
+ }
6
13
  function readJson(path) {
7
14
  if (!existsSync(path)) return null;
8
15
  try {
@@ -24,6 +31,8 @@ function writeVoluteConfig(mindDir, config) {
24
31
  }
25
32
 
26
33
  export {
34
+ WAKE_TRIGGER_DEFAULTS,
35
+ resolveWakeTriggers,
27
36
  readVoluteConfig,
28
37
  writeVoluteConfig
29
38
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  mindDir,
4
4
  stateDir
5
- } from "./chunk-H7IYFRAL.js";
5
+ } from "./chunk-ALKYQULU.js";
6
6
 
7
7
  // packages/daemon/src/lib/mind/archive.ts
8
8
  import { execFileSync } from "child_process";
@@ -7,14 +7,14 @@ import {
7
7
  modeLabel,
8
8
  pollHealth,
9
9
  startService
10
- } from "./chunk-Q2XBKQ2D.js";
10
+ } from "./chunk-TQJ6ZYVY.js";
11
11
  import {
12
12
  readGlobalConfig
13
- } from "./chunk-PNV24SEP.js";
13
+ } from "./chunk-F7H6ATM4.js";
14
14
  import {
15
15
  voluteHome,
16
16
  voluteSystemDir
17
- } from "./chunk-H7IYFRAL.js";
17
+ } from "./chunk-ALKYQULU.js";
18
18
 
19
19
  // src/commands/up.ts
20
20
  import { spawn } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  stateDir
4
- } from "./chunk-H7IYFRAL.js";
4
+ } from "./chunk-ALKYQULU.js";
5
5
 
6
6
  // packages/daemon/src/lib/chat/file-sharing.ts
7
7
  import { randomBytes } from "crypto";
@@ -8,10 +8,10 @@ import {
8
8
  pollHealthDown,
9
9
  readDaemonConfig,
10
10
  stopService
11
- } from "./chunk-Q2XBKQ2D.js";
11
+ } from "./chunk-TQJ6ZYVY.js";
12
12
  import {
13
13
  voluteSystemDir
14
- } from "./chunk-H7IYFRAL.js";
14
+ } from "./chunk-ALKYQULU.js";
15
15
 
16
16
  // src/commands/down.ts
17
17
  import { existsSync, readFileSync, unlinkSync } from "fs";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  voluteSystemDir
7
- } from "./chunk-H7IYFRAL.js";
7
+ } from "./chunk-ALKYQULU.js";
8
8
 
9
9
  // packages/daemon/src/lib/bridges/bridges.ts
10
10
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  broadcast
4
- } from "./chunk-ZMIMSIIT.js";
4
+ } from "./chunk-7C44R625.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-H7IYFRAL.js";
7
+ } from "./chunk-ALKYQULU.js";
8
8
  import {
9
9
  users
10
10
  } from "./chunk-ZV7MFISX.js";