volute 0.43.1 → 0.45.1
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-UISXY4Y5.js → activity-events-OMCSM7XW.js} +3 -3
- package/dist/{ai-service-FBCCEYF5.js → ai-service-534WUSDL.js} +4 -4
- package/dist/{archive-I32WHCJB.js → archive-6R4TZMYI.js} +3 -3
- package/dist/{auth-3RIG6GKN.js → auth-FRZJVQCS.js} +4 -4
- package/dist/avatar-image-BNWEXSA2.js +16 -0
- package/dist/backup-OZXHL3JS.js +381 -0
- package/dist/{chat-SDSX5TVV.js → chat-I4EDDIQC.js} +2 -2
- package/dist/{chunk-AD46AYZQ.js → chunk-2BONPXAF.js} +1 -1
- package/dist/{chunk-ONM2AZFL.js → chunk-322KE457.js} +14 -2
- package/dist/{chunk-PGPLCJPK.js → chunk-3ERIL673.js} +1 -1
- package/dist/{chunk-QQ2MVQU3.js → chunk-5AUKCNPE.js} +10 -10
- package/dist/{chunk-XDTKJNW5.js → chunk-5GT7URXJ.js} +1 -1
- package/dist/{chunk-DMUPZQWP.js → chunk-77BVGCTZ.js} +8 -8
- package/dist/{chunk-EFL5UOY4.js → chunk-CYI534DJ.js} +3 -3
- package/dist/{chunk-57LBQ7JX.js → chunk-FHWUQZXE.js} +1 -1
- package/dist/{chunk-YAMO2U3N.js → chunk-H7IYFRAL.js} +1 -1
- package/dist/{chunk-M3PGGSBH.js → chunk-HVU45COX.js} +1 -1
- package/dist/{chunk-O3DXAR44.js → chunk-IEWZ7VD7.js} +2 -2
- package/dist/{chunk-DE4EKQFH.js → chunk-IUXTYXFI.js} +3 -3
- package/dist/{chunk-K4EHBHPE.js → chunk-JSRMEY52.js} +7 -5
- package/dist/{chunk-QHG4OMZL.js → chunk-KDLYIECM.js} +37 -32
- package/dist/{chunk-EE7SZJON.js → chunk-KP3KWNG6.js} +2 -2
- package/dist/chunk-KVKP6VQ5.js +318 -0
- package/dist/{chunk-JVK6QQIC.js → chunk-LAXUUY7O.js} +2 -2
- package/dist/{chunk-C6ESRQZ7.js → chunk-OXXTPILE.js} +1 -1
- package/dist/{chunk-ATEX3ZPG.js → chunk-Q2XBKQ2D.js} +2 -2
- package/dist/{chunk-GZJFC76M.js → chunk-QMJJWTNL.js} +1 -1
- package/dist/chunk-SQL7M4HJ.js +120 -0
- package/dist/{chunk-YSV3VJQK.js → chunk-TSK5PI4L.js} +1 -1
- package/dist/{chunk-4SVONUTC.js → chunk-U4FNXDEJ.js} +350 -207
- package/dist/{chunk-2HIVYZDB.js → chunk-UMI4UIJG.js} +1 -1
- package/dist/{chunk-Y6O35D7K.js → chunk-V4GYPD5L.js} +41 -16
- package/dist/{chunk-KINGVQSR.js → chunk-Y6OPHHGM.js} +5 -5
- package/dist/{chunk-SPLPKBES.js → chunk-ZIR7HSAX.js} +6 -9
- package/dist/{chunk-7G2EEYXX.js → chunk-ZMIMSIIT.js} +2 -2
- package/dist/{chunk-L3IV23ZG.js → chunk-ZV7MFISX.js} +2 -0
- package/dist/cli.js +53 -22
- package/dist/{cloud-sync-KOEBUFW5.js → cloud-sync-Y7LQPRE3.js} +16 -16
- package/dist/{conversations-67WWW25C.js → conversations-G4LYMHAA.js} +4 -4
- package/dist/{create-7W3LV3J7.js → create-2YY7IPMS.js} +1 -1
- package/dist/{daemon-restart-EZXB73W7.js → daemon-restart-ABP54YN6.js} +7 -7
- package/dist/daemon.js +679 -478
- package/dist/{db-CZV62BOQ.js → db-JFLCJCUG.js} +2 -2
- package/dist/{db-IJL6B26S.js → db-LZTLA7XC.js} +1 -1
- package/dist/delivery-manager-R5STGLXB.js +32 -0
- package/dist/{delivery-router-HXBHYHJM.js → delivery-router-6DE3BPHO.js} +3 -3
- package/dist/{down-RRNYIGCU.js → down-XCAZXWLB.js} +5 -5
- package/dist/echo-text-NQXYYYCU.js +32 -0
- package/dist/{exec-SJK7VUTC.js → exec-4GWHAHS3.js} +3 -3
- package/dist/{export-7LPCIB5J.js → export-ID7YUSLZ.js} +3 -3
- package/dist/{extensions-PQPZT6LS.js → extensions-AYSPKE33.js} +16 -16
- package/dist/{files-LV7VAKPC.js → files-BQ2WB5KC.js} +3 -3
- package/dist/{import-XPECCRQ7.js → import-SKID37QX.js} +6 -6
- package/dist/{isolation-5AYEY55P.js → isolation-5MB3J73F.js} +3 -3
- package/dist/{login-KJSN4JDA.js → login-LM4NF4ZB.js} +2 -2
- package/dist/{logout-TMJGUQV2.js → logout-CTJVIZGA.js} +2 -2
- package/dist/message-delivery-IUAXRPFE.js +34 -0
- package/dist/{mind-G3WEWDM4.js → mind-KLHC7XYR.js} +6 -6
- package/dist/{mind-activity-tracker-JHAT6456.js → mind-activity-tracker-BGHJE7XA.js} +4 -4
- package/dist/mind-service-BF2LUQMH.js +36 -0
- package/dist/{package-24TL6DTF.js → package-BMHV7UHN.js} +1 -1
- package/dist/{registry-7LQHTRUX.js → registry-NUJAGLQJ.js} +2 -2
- package/dist/{sandbox-SJH3XOPZ.js → sandbox-72MWV4JL.js} +4 -4
- package/dist/scheduler-YHJRRV6E.js +30 -0
- package/dist/{schema-LRUTFOER.js → schema-PVNG3PQM.js} +1 -1
- package/dist/{seed-HHSJTM6O.js → seed-4MKQ5QKG.js} +1 -1
- package/dist/{seed-cmd-UPQY35RG.js → seed-cmd-LCXMR7VV.js} +2 -2
- package/dist/{seed-create-MDRMXMSB.js → seed-create-N2BF5LQX.js} +3 -3
- package/dist/{seed-sprout-4M7IQWMH.js → seed-sprout-6ZDP23YI.js} +7 -7
- package/dist/{send-ZBZWIH5V.js → send-O6W53VBM.js} +37 -42
- package/dist/{service-4EVZKGSA.js → service-AQVPUANO.js} +4 -4
- package/dist/{service-install-SHRM6NHK.js → service-install-4QNUDKLM.js} +5 -5
- package/dist/{setup-ZD2LMBR3.js → setup-3O6O2JZ7.js} +7 -7
- package/dist/{setup-25MMJ3J4.js → setup-73YBJZQL.js} +3 -3
- 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 +15 -16
- 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-4KQ377GN.js → skills-ND4ZFYUW.js} +5 -5
- package/dist/sleep-manager-YAHDOIU5.js +34 -0
- package/dist/{spirit-53DQPRNR.js → spirit-II3HOERM.js} +7 -7
- package/dist/{sprout-YKM2M6D6.js → sprout-4KUG76AQ.js} +1 -1
- package/dist/{src-O4PRLMKM.js → src-JAOVXO5W.js} +1 -0
- package/dist/{src-3EGHHKZK.js → src-XFY3XDD6.js} +16 -11
- package/dist/{src-L2Z7X5UT.js → src-YH6PQ7LJ.js} +264 -52
- package/dist/{status-WTU2WHXY.js → status-E5DQNCAO.js} +5 -5
- package/dist/system-chat-E65T2HSW.js +34 -0
- package/dist/{tailscale-Q7ZJL7S7.js → tailscale-QAWVKQKU.js} +2 -2
- package/dist/{turn-tracker-JZYERJ76.js → turn-tracker-QQIVVXBI.js} +7 -5
- package/dist/{up-LFZA4GI5.js → up-LG56MPBI.js} +6 -6
- package/dist/{update-EJ7Q77ZX.js → update-7TYVH5FM.js} +5 -5
- package/dist/{update-check-ACYYNKTW.js → update-check-VSCSJ7KX.js} +3 -3
- package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
- package/dist/{version-notify-SRQJCQEK.js → version-notify-CODJOUW2.js} +17 -17
- package/dist/web-assets/assets/index-B1JBQv2m.css +1 -0
- package/dist/web-assets/assets/index-Dac11sKB.js +86 -0
- package/dist/web-assets/ext-theme.css +5 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/packages/extensions/notes/dist/ui/assets/{index-BbgANhFm.js → index-D0-DgYyj.js} +1 -1
- package/packages/extensions/notes/dist/ui/index.html +1 -1
- package/packages/extensions/notes/skills/notes/SKILL.md +31 -3
- package/packages/extensions/pages/dist/ui/assets/{index-Dxk-DYrf.js → index-CQjEQg1r.js} +1 -1
- package/packages/extensions/pages/dist/ui/index.html +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 +21 -4
- package/templates/_base/.init/MEMORY.md +2 -1
- package/templates/_base/home/VOLUTE.md +6 -9
- package/templates/_base/src/lib/startup.ts +1 -1
- package/templates/claude/.init/CLAUDE.md +1 -1
- package/templates/claude/src/agent.ts +30 -3
- package/templates/claude/src/lib/stream-consumer.ts +7 -4
- package/templates/codex/.init/AGENTS.md +4 -2
- package/templates/codex/src/agent.ts +13 -7
- package/templates/pi/.init/MINDS.md +4 -2
- package/templates/pi/src/lib/event-handler.ts +6 -2
- package/dist/delivery-manager-OMCCJEVP.js +0 -32
- package/dist/echo-text-KFSUCZXL.js +0 -32
- package/dist/message-delivery-HRGFNWO7.js +0 -34
- package/dist/mind-service-2C7AINBO.js +0 -36
- package/dist/scheduler-VON2TB52.js +0 -30
- package/dist/sleep-manager-AG5SQDDI.js +0 -34
- package/dist/system-chat-54RN3K67.js +0 -34
- package/dist/web-assets/assets/index-D7udpBCx.css +0 -1
- package/dist/web-assets/assets/index-DTDmEfPU.js +0 -79
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
broadcast
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZMIMSIIT.js";
|
|
5
5
|
import {
|
|
6
6
|
getDb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-H7IYFRAL.js";
|
|
8
8
|
import {
|
|
9
9
|
users
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZV7MFISX.js";
|
|
11
11
|
|
|
12
12
|
// packages/daemon/src/lib/auth.ts
|
|
13
13
|
import { compareSync, hashSync } from "bcryptjs";
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
pollHealthDown,
|
|
9
9
|
readDaemonConfig,
|
|
10
10
|
stopService
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Q2XBKQ2D.js";
|
|
12
12
|
import {
|
|
13
13
|
voluteSystemDir
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-H7IYFRAL.js";
|
|
15
15
|
|
|
16
16
|
// src/commands/down.ts
|
|
17
17
|
import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
modeLabel,
|
|
8
8
|
pollHealth,
|
|
9
9
|
startService
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Q2XBKQ2D.js";
|
|
11
11
|
import {
|
|
12
12
|
readGlobalConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-322KE457.js";
|
|
14
14
|
import {
|
|
15
15
|
voluteHome,
|
|
16
16
|
voluteSystemDir
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-H7IYFRAL.js";
|
|
18
18
|
|
|
19
19
|
// src/commands/up.ts
|
|
20
20
|
import { spawn } from "child_process";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
subscribe
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZMIMSIIT.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-H7IYFRAL.js";
|
|
11
11
|
import {
|
|
12
12
|
channels,
|
|
13
13
|
conversationParticipants,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
conversations,
|
|
16
16
|
messages,
|
|
17
17
|
users
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ZV7MFISX.js";
|
|
19
19
|
|
|
20
20
|
// packages/daemon/src/lib/events/conversations.ts
|
|
21
21
|
import { randomUUID } from "crypto";
|
|
@@ -470,7 +470,7 @@ async function joinChannel(conversationId, userId) {
|
|
|
470
470
|
async function leaveChannel(conversationId, userId) {
|
|
471
471
|
await removeParticipant(conversationId, userId);
|
|
472
472
|
}
|
|
473
|
-
async function getUnreadCounts(userId, conversationIds) {
|
|
473
|
+
async function getUnreadCounts(userId, conversationIds, username) {
|
|
474
474
|
if (conversationIds.length === 0) return {};
|
|
475
475
|
const db = await getDb();
|
|
476
476
|
const rows = await db.select({
|
|
@@ -485,7 +485,9 @@ async function getUnreadCounts(userId, conversationIds) {
|
|
|
485
485
|
).where(
|
|
486
486
|
and(
|
|
487
487
|
inArray(messages.conversation_id, conversationIds),
|
|
488
|
-
sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)
|
|
488
|
+
sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`,
|
|
489
|
+
// A user's own messages are never "unread" for them
|
|
490
|
+
sql`(${messages.sender_name} IS NULL OR ${messages.sender_name} != ${username})`
|
|
489
491
|
)
|
|
490
492
|
).groupBy(messages.conversation_id);
|
|
491
493
|
const result = {};
|
|
@@ -14,11 +14,13 @@ function initDb(db) {
|
|
|
14
14
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_pp_mind_file ON published_pages(mind, file);
|
|
15
15
|
CREATE INDEX IF NOT EXISTS idx_pp_updated_at ON published_pages(updated_at);
|
|
16
16
|
`);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
for (const column of ["author TEXT", "hash TEXT"]) {
|
|
18
|
+
try {
|
|
19
|
+
db.exec(`ALTER TABLE published_pages ADD COLUMN ${column}`);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (!(err instanceof Error) || !err.message.includes("duplicate column")) {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -63,32 +65,31 @@ function getSystemPages(db) {
|
|
|
63
65
|
files: rows.map((r) => ({ file: r.file, updated_at: r.updated_at, author: r.author }))
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
|
-
function syncSystemPages(db,
|
|
67
|
-
const existing = new
|
|
68
|
-
db.prepare("SELECT file FROM published_pages WHERE mind = '_system'").all().map((r) => r.file)
|
|
68
|
+
function syncSystemPages(db, pages, author) {
|
|
69
|
+
const existing = new Map(
|
|
70
|
+
db.prepare("SELECT file, hash FROM published_pages WHERE mind = '_system'").all().map((r) => [r.file, r.hash])
|
|
69
71
|
);
|
|
70
|
-
const newSet = new Set(
|
|
72
|
+
const newSet = new Set(pages.map((p) => p.file));
|
|
71
73
|
db.exec("BEGIN");
|
|
72
74
|
try {
|
|
73
|
-
for (const file of
|
|
74
|
-
if (existing.has(file)) {
|
|
75
|
+
for (const { file, hash } of pages) {
|
|
76
|
+
if (!existing.has(file)) {
|
|
77
|
+
db.prepare(
|
|
78
|
+
"INSERT INTO published_pages (mind, file, hash, author) VALUES ('_system', ?, ?, ?)"
|
|
79
|
+
).run(file, hash, author ?? null);
|
|
80
|
+
} else if (existing.get(file) !== hash) {
|
|
75
81
|
if (author) {
|
|
76
82
|
db.prepare(
|
|
77
|
-
"UPDATE published_pages SET updated_at = datetime('now'), author = ? WHERE mind = '_system' AND file = ?"
|
|
78
|
-
).run(author, file);
|
|
83
|
+
"UPDATE published_pages SET updated_at = datetime('now'), hash = ?, author = ? WHERE mind = '_system' AND file = ?"
|
|
84
|
+
).run(hash, author, file);
|
|
79
85
|
} else {
|
|
80
86
|
db.prepare(
|
|
81
|
-
"UPDATE published_pages SET updated_at = datetime('now') WHERE mind = '_system' AND file = ?"
|
|
82
|
-
).run(file);
|
|
87
|
+
"UPDATE published_pages SET updated_at = datetime('now'), hash = ? WHERE mind = '_system' AND file = ?"
|
|
88
|
+
).run(hash, file);
|
|
83
89
|
}
|
|
84
|
-
} else {
|
|
85
|
-
db.prepare("INSERT INTO published_pages (mind, file, author) VALUES ('_system', ?, ?)").run(
|
|
86
|
-
file,
|
|
87
|
-
author ?? null
|
|
88
|
-
);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
for (const file of existing) {
|
|
92
|
+
for (const file of existing.keys()) {
|
|
92
93
|
if (!newSet.has(file)) {
|
|
93
94
|
db.prepare("DELETE FROM published_pages WHERE mind = '_system' AND file = ?").run(file);
|
|
94
95
|
}
|
|
@@ -99,28 +100,32 @@ function syncSystemPages(db, htmlFiles, author) {
|
|
|
99
100
|
throw err;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
function syncPublishedPages(db, mind,
|
|
103
|
+
function syncPublishedPages(db, mind, pages) {
|
|
103
104
|
const existing = new Map(
|
|
104
|
-
db.prepare("SELECT file,
|
|
105
|
+
db.prepare("SELECT file, hash FROM published_pages WHERE mind = ?").all(mind).map((r) => [r.file, r.hash])
|
|
105
106
|
);
|
|
106
|
-
const newSet = new Set(
|
|
107
|
+
const newSet = new Set(pages.map((p) => p.file));
|
|
107
108
|
const added = [];
|
|
108
109
|
const updated = [];
|
|
109
110
|
const removed = [];
|
|
110
111
|
db.exec("BEGIN");
|
|
111
112
|
try {
|
|
112
|
-
for (const file of
|
|
113
|
-
if (existing.has(file)) {
|
|
113
|
+
for (const { file, hash } of pages) {
|
|
114
|
+
if (!existing.has(file)) {
|
|
115
|
+
db.prepare("INSERT INTO published_pages (mind, file, hash) VALUES (?, ?, ?)").run(
|
|
116
|
+
mind,
|
|
117
|
+
file,
|
|
118
|
+
hash
|
|
119
|
+
);
|
|
120
|
+
added.push(file);
|
|
121
|
+
} else if (existing.get(file) !== hash) {
|
|
114
122
|
db.prepare(
|
|
115
|
-
"UPDATE published_pages SET updated_at = datetime('now') WHERE mind = ? AND file = ?"
|
|
116
|
-
).run(mind, file);
|
|
123
|
+
"UPDATE published_pages SET updated_at = datetime('now'), hash = ? WHERE mind = ? AND file = ?"
|
|
124
|
+
).run(hash, mind, file);
|
|
117
125
|
updated.push(file);
|
|
118
|
-
} else {
|
|
119
|
-
db.prepare("INSERT INTO published_pages (mind, file) VALUES (?, ?)").run(mind, file);
|
|
120
|
-
added.push(file);
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
|
-
for (const
|
|
128
|
+
for (const file of existing.keys()) {
|
|
124
129
|
if (!newSet.has(file)) {
|
|
125
130
|
db.prepare("DELETE FROM published_pages WHERE mind = ? AND file = ?").run(mind, file);
|
|
126
131
|
removed.push(file);
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
LAUNCHD_PLIST_LABEL,
|
|
4
4
|
LAUNCHD_PLIST_PATH,
|
|
5
5
|
USER_SYSTEMD_UNIT
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Q2XBKQ2D.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveVoluteBin
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3ERIL673.js";
|
|
10
10
|
|
|
11
11
|
// packages/daemon/src/lib/config/service-install.ts
|
|
12
12
|
import { execFile } from "child_process";
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
exec
|
|
4
|
+
} from "./chunk-3ERIL673.js";
|
|
5
|
+
import {
|
|
6
|
+
readGlobalConfig
|
|
7
|
+
} from "./chunk-322KE457.js";
|
|
8
|
+
import {
|
|
9
|
+
logger_default
|
|
10
|
+
} from "./chunk-T2TP6ZC6.js";
|
|
11
|
+
import {
|
|
12
|
+
getDb,
|
|
13
|
+
readAllMinds,
|
|
14
|
+
voluteHome,
|
|
15
|
+
voluteSystemDir
|
|
16
|
+
} from "./chunk-H7IYFRAL.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 buildExcludePatterns(config) {
|
|
98
|
+
const patterns = [...BASE_EXCLUDES, ...systemExcludes()];
|
|
99
|
+
if (!config.includeSessions) patterns.push(...SESSION_EXCLUDES);
|
|
100
|
+
return patterns;
|
|
101
|
+
}
|
|
102
|
+
function writeExcludeFile(config) {
|
|
103
|
+
const path = resolve(voluteSystemDir(), "backup-excludes.txt");
|
|
104
|
+
writeFileSync(path, `${buildExcludePatterns(config).join("\n")}
|
|
105
|
+
`);
|
|
106
|
+
return path;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// packages/daemon/src/lib/backup/backup.ts
|
|
110
|
+
import {
|
|
111
|
+
existsSync,
|
|
112
|
+
mkdirSync,
|
|
113
|
+
readdirSync,
|
|
114
|
+
readFileSync,
|
|
115
|
+
renameSync,
|
|
116
|
+
rmSync,
|
|
117
|
+
writeFileSync as writeFileSync2
|
|
118
|
+
} from "fs";
|
|
119
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
120
|
+
import { sql } from "drizzle-orm";
|
|
121
|
+
var blog = logger_default.child("backup");
|
|
122
|
+
var DEFAULT_KEEP = { daily: 7, weekly: 4, monthly: 12 };
|
|
123
|
+
function stateFilePath() {
|
|
124
|
+
return resolve2(voluteSystemDir(), "backup-state.json");
|
|
125
|
+
}
|
|
126
|
+
function readBackupState() {
|
|
127
|
+
const path = stateFilePath();
|
|
128
|
+
if (!existsSync(path)) return {};
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
blog.warn("backup-state.json unreadable, treating as empty", logger_default.errorData(err));
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function writeBackupState(state) {
|
|
137
|
+
try {
|
|
138
|
+
writeFileSync2(stateFilePath(), `${JSON.stringify(state, null, 2)}
|
|
139
|
+
`);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
blog.error("failed to write backup-state.json", logger_default.errorData(err));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function stagingDir() {
|
|
145
|
+
return resolve2(voluteSystemDir(), "backup-staging");
|
|
146
|
+
}
|
|
147
|
+
async function stageDatabases() {
|
|
148
|
+
const staging = stagingDir();
|
|
149
|
+
rmSync(staging, { recursive: true, force: true });
|
|
150
|
+
mkdirSync(staging, { recursive: true });
|
|
151
|
+
const staged = [];
|
|
152
|
+
const mainTarget = resolve2(staging, "volute.db");
|
|
153
|
+
const db = await getDb();
|
|
154
|
+
await db.run(sql.raw(`VACUUM INTO '${mainTarget.replaceAll("'", "''")}'`));
|
|
155
|
+
staged.push(mainTarget);
|
|
156
|
+
const extDataDir = resolve2(voluteSystemDir(), "extension-data");
|
|
157
|
+
if (existsSync(extDataDir)) {
|
|
158
|
+
const { default: Database } = await import("libsql");
|
|
159
|
+
for (const id of readdirSync(extDataDir)) {
|
|
160
|
+
const dbPath = resolve2(extDataDir, id, "data.db");
|
|
161
|
+
if (!existsSync(dbPath)) continue;
|
|
162
|
+
const target = resolve2(staging, "extension-data", id, "data.db");
|
|
163
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
164
|
+
const extDb = new Database(dbPath);
|
|
165
|
+
try {
|
|
166
|
+
extDb.exec(`VACUUM INTO '${target.replaceAll("'", "''")}'`);
|
|
167
|
+
staged.push(target);
|
|
168
|
+
} finally {
|
|
169
|
+
extDb.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return staged;
|
|
174
|
+
}
|
|
175
|
+
async function backupRoots() {
|
|
176
|
+
const roots = /* @__PURE__ */ new Set([voluteHome()]);
|
|
177
|
+
const mindsDir = process.env.VOLUTE_MINDS_DIR;
|
|
178
|
+
if (mindsDir && existsSync(mindsDir)) roots.add(resolve2(mindsDir));
|
|
179
|
+
for (const mind of await readAllMinds()) {
|
|
180
|
+
if (!mind.dir || !existsSync(mind.dir)) continue;
|
|
181
|
+
const dir = resolve2(mind.dir);
|
|
182
|
+
if (![...roots].some((root) => dir === root || dir.startsWith(`${root}/`))) {
|
|
183
|
+
roots.add(dir);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return [...roots];
|
|
187
|
+
}
|
|
188
|
+
var backupInFlight = false;
|
|
189
|
+
async function runBackup() {
|
|
190
|
+
if (backupInFlight) throw new Error("A backup is already running");
|
|
191
|
+
backupInFlight = true;
|
|
192
|
+
const started = Date.now();
|
|
193
|
+
try {
|
|
194
|
+
const config = readGlobalConfig().backup ?? {};
|
|
195
|
+
if (!config.repository || !config.password) {
|
|
196
|
+
throw new Error("Backup repository is not configured. Run: volute backup init");
|
|
197
|
+
}
|
|
198
|
+
blog.info("starting backup", { repository: config.repository });
|
|
199
|
+
await stageDatabases();
|
|
200
|
+
const roots = await backupRoots();
|
|
201
|
+
const excludeFile = writeExcludeFile(config);
|
|
202
|
+
const out = await runRestic(
|
|
203
|
+
[
|
|
204
|
+
"backup",
|
|
205
|
+
...roots,
|
|
206
|
+
"--exclude-file",
|
|
207
|
+
excludeFile,
|
|
208
|
+
"--exclude-caches",
|
|
209
|
+
"--tag",
|
|
210
|
+
"volute",
|
|
211
|
+
"--json"
|
|
212
|
+
],
|
|
213
|
+
config
|
|
214
|
+
);
|
|
215
|
+
const summary = parseBackupSummary(out, Date.now() - started);
|
|
216
|
+
const keep = { ...DEFAULT_KEEP, ...config.keep };
|
|
217
|
+
let pruneError;
|
|
218
|
+
try {
|
|
219
|
+
await runRestic(
|
|
220
|
+
[
|
|
221
|
+
"forget",
|
|
222
|
+
"--tag",
|
|
223
|
+
"volute",
|
|
224
|
+
"--keep-daily",
|
|
225
|
+
String(keep.daily),
|
|
226
|
+
"--keep-weekly",
|
|
227
|
+
String(keep.weekly),
|
|
228
|
+
"--keep-monthly",
|
|
229
|
+
String(keep.monthly),
|
|
230
|
+
"--prune"
|
|
231
|
+
],
|
|
232
|
+
config
|
|
233
|
+
);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
pruneError = err instanceof Error ? err.message : String(err);
|
|
236
|
+
blog.warn("retention prune failed (snapshot is intact)", logger_default.errorData(err));
|
|
237
|
+
}
|
|
238
|
+
writeBackupState({
|
|
239
|
+
lastRun: (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
|
+
lastSnapshotId: summary.snapshotId,
|
|
241
|
+
lastDurationMs: summary.durationMs,
|
|
242
|
+
...pruneError ? { pruneError } : {}
|
|
243
|
+
});
|
|
244
|
+
blog.info("backup complete", { snapshot: summary.snapshotId, ms: summary.durationMs });
|
|
245
|
+
return summary;
|
|
246
|
+
} catch (err) {
|
|
247
|
+
writeBackupState({
|
|
248
|
+
...readBackupState(),
|
|
249
|
+
lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
250
|
+
lastError: err instanceof Error ? err.message : String(err)
|
|
251
|
+
});
|
|
252
|
+
throw err;
|
|
253
|
+
} finally {
|
|
254
|
+
backupInFlight = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function parseBackupSummary(output, durationMs) {
|
|
258
|
+
for (const line of output.trim().split("\n").reverse()) {
|
|
259
|
+
let msg;
|
|
260
|
+
try {
|
|
261
|
+
msg = JSON.parse(line);
|
|
262
|
+
} catch {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (msg.message_type !== "summary") continue;
|
|
266
|
+
if (!msg.snapshot_id) throw new Error("restic summary is missing snapshot_id");
|
|
267
|
+
return {
|
|
268
|
+
snapshotId: msg.snapshot_id,
|
|
269
|
+
filesNew: msg.files_new ?? 0,
|
|
270
|
+
filesChanged: msg.files_changed ?? 0,
|
|
271
|
+
dataAdded: msg.data_added ?? 0,
|
|
272
|
+
totalFilesProcessed: msg.total_files_processed ?? 0,
|
|
273
|
+
durationMs
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
throw new Error("restic backup produced no summary");
|
|
277
|
+
}
|
|
278
|
+
async function listSnapshots() {
|
|
279
|
+
const config = readGlobalConfig().backup ?? {};
|
|
280
|
+
const out = await runRestic(["snapshots", "--tag", "volute", "--json"], config);
|
|
281
|
+
const parsed = JSON.parse(out || "[]");
|
|
282
|
+
return parsed.map((s) => ({
|
|
283
|
+
id: s.id,
|
|
284
|
+
short_id: s.short_id,
|
|
285
|
+
time: s.time,
|
|
286
|
+
hostname: s.hostname,
|
|
287
|
+
paths: s.paths
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
async function initRepo(config) {
|
|
291
|
+
try {
|
|
292
|
+
await runRestic(["init"], config);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const stderr = err.stderr ?? "";
|
|
295
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
296
|
+
if (/already exists|already initialized/i.test(stderr + msg)) return;
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function restoreStagedDb(stagedPath, livePath) {
|
|
301
|
+
if (!existsSync(stagedPath)) return false;
|
|
302
|
+
mkdirSync(dirname(livePath), { recursive: true });
|
|
303
|
+
rmSync(livePath, { force: true });
|
|
304
|
+
rmSync(`${livePath}-wal`, { force: true });
|
|
305
|
+
rmSync(`${livePath}-shm`, { force: true });
|
|
306
|
+
renameSync(stagedPath, livePath);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export {
|
|
311
|
+
RESTIC_INSTALL_HINT,
|
|
312
|
+
resticVersion,
|
|
313
|
+
readBackupState,
|
|
314
|
+
runBackup,
|
|
315
|
+
listSnapshots,
|
|
316
|
+
initRepo,
|
|
317
|
+
restoreStagedDb
|
|
318
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
setBridgeConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FHWUQZXE.js";
|
|
5
5
|
import {
|
|
6
6
|
readEnv,
|
|
7
7
|
sharedEnvPath,
|
|
8
8
|
writeEnv
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5GT7URXJ.js";
|
|
10
10
|
|
|
11
11
|
// packages/daemon/src/lib/template/import-utils.ts
|
|
12
12
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
exec,
|
|
4
4
|
execInherit
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3ERIL673.js";
|
|
6
6
|
import {
|
|
7
7
|
voluteSystemDir
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-H7IYFRAL.js";
|
|
9
9
|
|
|
10
10
|
// packages/daemon/src/lib/config/service-mode.ts
|
|
11
11
|
import { execFileSync } from "child_process";
|