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.
- package/dist/{activity-events-OMCSM7XW.js → activity-events-BUR2RYTJ.js} +2 -2
- package/dist/{ai-service-7PHSRDMO.js → ai-service-UODCCE4O.js} +3 -3
- package/dist/{archive-6R4TZMYI.js → archive-WS2L2VIQ.js} +2 -2
- package/dist/{auth-FRZJVQCS.js → auth-FLAEXXZK.js} +3 -3
- package/dist/{avatar-image-BNWEXSA2.js → avatar-image-6KY6UU4D.js} +3 -3
- package/dist/backup-TST5C2YN.js +381 -0
- package/dist/{chat-I4EDDIQC.js → chat-76MRWWQX.js} +2 -2
- package/dist/{chunk-5AHYQHNT.js → chunk-4JOQZPCE.js} +3 -2
- package/dist/{chunk-2BONPXAF.js → chunk-6EMWJZBK.js} +1 -1
- package/dist/{chunk-ZMIMSIIT.js → chunk-7C44R625.js} +1 -1
- package/dist/{chunk-I2Y2YHIO.js → chunk-7ZUB63PS.js} +4 -4
- package/dist/{chunk-LAXUUY7O.js → chunk-A7WQZKOU.js} +2 -2
- package/dist/{chunk-H7IYFRAL.js → chunk-ALKYQULU.js} +10 -5
- package/dist/{chunk-KP3KWNG6.js → chunk-CDPBTTZO.js} +2 -2
- package/dist/{chunk-SQL7M4HJ.js → chunk-DRJ24GXK.js} +2 -2
- package/dist/{chunk-HVU45COX.js → chunk-EJVPTVTI.js} +1 -1
- package/dist/{chunk-PNV24SEP.js → chunk-F7H6ATM4.js} +14 -2
- package/dist/{chunk-7FT24JBQ.js → chunk-FR6S2KNN.js} +10 -10
- package/dist/chunk-FXORA5JG.js +327 -0
- package/dist/{chunk-3ERIL673.js → chunk-H2X54EHC.js} +1 -1
- package/dist/{chunk-J47TIYHA.js → chunk-IHKGWTGT.js} +1 -1
- package/dist/{chunk-3GYVW2X3.js → chunk-JZ62SYWR.js} +1 -1
- package/dist/{chunk-EQZD4YU3.js → chunk-NYC5U6K7.js} +9 -9
- package/dist/{chunk-FZMEVCWC.js → chunk-OP4JG2BS.js} +9 -6
- package/dist/{chunk-CU6OFXMM.js → chunk-PFZBNKUH.js} +9 -0
- package/dist/{chunk-QMJJWTNL.js → chunk-QCN24XEN.js} +1 -1
- package/dist/{chunk-7KWANSLX.js → chunk-QNQMSWZE.js} +3 -3
- package/dist/{chunk-UMI4UIJG.js → chunk-R77O2WMK.js} +1 -1
- package/dist/{chunk-IEWZ7VD7.js → chunk-RRNXJOKB.js} +2 -2
- package/dist/{chunk-FHWUQZXE.js → chunk-RXEEYDIB.js} +1 -1
- package/dist/{chunk-CYI534DJ.js → chunk-T6HYVHPG.js} +2 -2
- package/dist/{chunk-Q2XBKQ2D.js → chunk-TQJ6ZYVY.js} +2 -2
- package/dist/{chunk-NF75HPNR.js → chunk-UYLGEDVD.js} +40 -15
- package/dist/{chunk-OMETFX5H.js → chunk-YT7PEESJ.js} +261 -102
- package/dist/{chunk-5GT7URXJ.js → chunk-ZDZISKCL.js} +1 -1
- package/dist/cli.js +21 -15
- package/dist/{cloud-sync-6WJ7WFYD.js → cloud-sync-3OJOPZRT.js} +16 -16
- package/dist/{conversations-WAJYZKOC.js → conversations-JZTD6KLR.js} +3 -3
- package/dist/{create-YZIV5OJC.js → create-IOV4GCRX.js} +1 -1
- package/dist/{daemon-restart-OUJVINXV.js → daemon-restart-TIHKVVVA.js} +6 -6
- package/dist/daemon.js +562 -321
- package/dist/{db-JFLCJCUG.js → db-MF2JNRUW.js} +1 -1
- package/dist/delivery-manager-VDVKVA46.js +32 -0
- package/dist/{delivery-router-GODSIS62.js → delivery-router-N4A7ZC5T.js} +2 -2
- package/dist/{down-XCAZXWLB.js → down-U2CZ23M4.js} +4 -4
- package/dist/echo-text-6JJOPUD4.js +32 -0
- package/dist/{exec-4GWHAHS3.js → exec-G3ZRST4Z.js} +2 -2
- package/dist/{export-ID7YUSLZ.js → export-GGIVO76O.js} +2 -2
- package/dist/{extensions-3QNDGW5E.js → extensions-JONTW2HF.js} +16 -16
- package/dist/{files-BQ2WB5KC.js → files-4LN3EN7Q.js} +2 -2
- package/dist/{import-SKID37QX.js → import-42YNQBF4.js} +5 -5
- package/dist/{isolation-5MB3J73F.js → isolation-YMGZZ4E5.js} +2 -2
- package/dist/{join-RXPXGV4L.js → join-ZJV7W6MO.js} +4 -0
- package/dist/{login-LM4NF4ZB.js → login-M6OJUPQ2.js} +1 -1
- package/dist/{logout-CTJVIZGA.js → logout-WQBWO77G.js} +1 -1
- package/dist/message-delivery-3C65ZI6L.js +34 -0
- package/dist/{mind-MI7Z6KM2.js → mind-DWB6XYCH.js} +7 -7
- package/dist/{mind-activity-tracker-BGHJE7XA.js → mind-activity-tracker-POBLU3LC.js} +3 -3
- package/dist/mind-service-3F4I46CI.js +36 -0
- package/dist/{package-5B4XDTJH.js → package-A7KXT67Q.js} +1 -1
- package/dist/{registry-NUJAGLQJ.js → registry-OUZBZ3Q4.js} +5 -1
- package/dist/{sandbox-IOPWWILM.js → sandbox-ZJ7QA6Z6.js} +3 -3
- package/dist/scheduler-P6DXLLXI.js +30 -0
- package/dist/{seed-UUPVQWFB.js → seed-HCP5NYKH.js} +1 -1
- package/dist/{seed-cmd-7XPKDQUC.js → seed-cmd-ILOGNIFB.js} +2 -2
- package/dist/{seed-create-IZ4M5ESO.js → seed-create-OURS2LS4.js} +3 -3
- package/dist/{seed-sprout-POJXHJWV.js → seed-sprout-ELDFDAN6.js} +7 -7
- package/dist/{send-O6W53VBM.js → send-AAIOPJAV.js} +2 -2
- package/dist/{service-AQVPUANO.js → service-UOCLXDWR.js} +3 -3
- package/dist/{service-install-4QNUDKLM.js → service-install-4HHFDDWY.js} +4 -4
- package/dist/{setup-5GCO3CUB.js → setup-DQM5VPY4.js} +6 -6
- package/dist/{setup-MLRKNP7Y.js → setup-Y2EJI3GG.js} +2 -2
- package/dist/skills/imagegen/scripts/imagegen.ts +100 -40
- package/dist/skills/memory/SKILL.md +2 -1
- package/dist/skills/orientation/SKILL.md +1 -1
- package/dist/skills/plan-coordinator/SKILL.md +2 -2
- package/dist/skills/volute-admin/SKILL.md +9 -6
- package/dist/skills/volute-mind/SKILL.md +20 -17
- package/dist/skills/volute-mind/references/extensions.md +5 -3
- package/dist/skills/volute-mind/references/integrations.md +2 -2
- package/dist/skills/volute-mind/references/routing.md +19 -23
- package/dist/skills/volute-mind/references/variants.md +10 -8
- package/dist/{skills-775C7QFF.js → skills-BKINFL7W.js} +4 -4
- package/dist/sleep-manager-HCUH4PM4.js +34 -0
- package/dist/{spirit-IK5OYWMV.js → spirit-DBTIW7P4.js} +7 -7
- package/dist/{sprout-ZY5QGOEL.js → sprout-W3KC7UGK.js} +1 -1
- package/dist/{src-XFY3XDD6.js → src-OJHARHTF.js} +28 -17
- package/dist/{status-E5DQNCAO.js → status-SWRVGXBI.js} +4 -4
- package/dist/system-chat-XOALE2RD.js +36 -0
- package/dist/{tailscale-QAWVKQKU.js → tailscale-3XOKOW7T.js} +1 -1
- package/dist/{turn-tracker-RWZMY7M6.js → turn-tracker-2W6Q2NSR.js} +6 -4
- package/dist/{up-FLCDR2UB.js → up-FTK7CTO3.js} +5 -5
- package/dist/{update-7TYVH5FM.js → update-SWAYZRWJ.js} +4 -4
- package/dist/{update-check-VSCSJ7KX.js → update-check-PQ2MZ47K.js} +2 -2
- package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
- package/dist/{version-notify-B67N35A3.js → version-notify-3XMAIQTE.js} +17 -17
- package/dist/{volute-config-TS62GS6A.js → volute-config-A52FHWKM.js} +5 -1
- package/dist/web-assets/assets/{index-dJdNzAPn.css → index-B1JBQv2m.css} +1 -1
- package/dist/web-assets/assets/index-Bc2m8gRC.js +86 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
- package/templates/_base/.init/.config/prompts.json +1 -1
- package/templates/_base/.init/.local/hooks/startup-context.ts +0 -8
- package/templates/_base/.init/MEMORY.md +2 -1
- package/templates/_base/home/VOLUTE.md +6 -9
- package/templates/_base/src/lib/file-handler.ts +3 -3
- package/templates/_base/src/lib/router.ts +21 -25
- package/templates/_base/src/lib/startup.ts +3 -1
- package/templates/_base/src/lib/types.ts +2 -2
- package/templates/claude/.init/CLAUDE.md +1 -1
- package/templates/claude/src/agent.ts +97 -12
- package/templates/claude/src/lib/message-channel.ts +23 -0
- package/templates/claude/src/lib/session-reaper.ts +37 -0
- package/templates/claude/src/lib/stream-consumer.ts +7 -4
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/.init/AGENTS.md +4 -2
- package/templates/codex/src/agent.ts +31 -13
- package/templates/pi/.init/MINDS.md +4 -2
- package/templates/pi/src/agent.ts +18 -7
- package/templates/pi/src/lib/event-handler.ts +6 -2
- package/dist/delivery-manager-TEVOHX5W.js +0 -32
- package/dist/echo-text-T7ZA5EYD.js +0 -32
- package/dist/message-delivery-2EA5LLMD.js +0 -34
- package/dist/mind-service-S3SY7HAM.js +0 -36
- package/dist/scheduler-CXWQ6Z4E.js +0 -30
- package/dist/sleep-manager-HINI6Y46.js +0 -34
- package/dist/system-chat-4DQZOEZI.js +0 -34
- package/dist/web-assets/assets/index-Domoz5Cg.js +0 -79
- 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-
|
|
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-
|
|
13
|
+
} from "./chunk-7ZUB63PS.js";
|
|
14
14
|
import {
|
|
15
15
|
readVoluteConfig,
|
|
16
16
|
writeVoluteConfig
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-PFZBNKUH.js";
|
|
18
18
|
import {
|
|
19
19
|
exec
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-H2X54EHC.js";
|
|
21
21
|
import {
|
|
22
22
|
qualifyModelId,
|
|
23
23
|
resolveTemplate,
|
|
24
24
|
unqualifyModelId
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-IHKGWTGT.js";
|
|
26
26
|
import {
|
|
27
27
|
readGlobalConfig
|
|
28
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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,14 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
findBridgeForChannel,
|
|
4
4
|
getBridgeConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RXEEYDIB.js";
|
|
6
6
|
import {
|
|
7
7
|
recordOutbound
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YT7PEESJ.js";
|
|
9
9
|
import {
|
|
10
10
|
readEnv,
|
|
11
11
|
sharedEnvPath
|
|
12
|
-
} from "./chunk-
|
|
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-
|
|
20
|
+
} from "./chunk-OP4JG2BS.js";
|
|
21
21
|
import {
|
|
22
22
|
readVoluteConfig
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PFZBNKUH.js";
|
|
24
24
|
import {
|
|
25
25
|
publish
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-UYLGEDVD.js";
|
|
27
27
|
import {
|
|
28
28
|
daemonConfigPath,
|
|
29
29
|
readDaemonToken
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TQJ6ZYVY.js";
|
|
31
31
|
import {
|
|
32
32
|
getOrCreateMindUser,
|
|
33
33
|
getUserByUsername
|
|
34
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
};
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
modeLabel,
|
|
8
8
|
pollHealth,
|
|
9
9
|
startService
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TQJ6ZYVY.js";
|
|
11
11
|
import {
|
|
12
12
|
readGlobalConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-F7H6ATM4.js";
|
|
14
14
|
import {
|
|
15
15
|
voluteHome,
|
|
16
16
|
voluteSystemDir
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ALKYQULU.js";
|
|
18
18
|
|
|
19
19
|
// src/commands/up.ts
|
|
20
20
|
import { spawn } from "child_process";
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
pollHealthDown,
|
|
9
9
|
readDaemonConfig,
|
|
10
10
|
stopService
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-TQJ6ZYVY.js";
|
|
12
12
|
import {
|
|
13
13
|
voluteSystemDir
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ALKYQULU.js";
|
|
15
15
|
|
|
16
16
|
// src/commands/down.ts
|
|
17
17
|
import { existsSync, readFileSync, unlinkSync } from "fs";
|