volute 0.30.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -22
- package/dist/{accept-E3PAH3QJ.js → accept-74M7I4RZ.js} +5 -4
- package/dist/{activity-events-BKBPPUBP.js → activity-events-HETAODOK.js} +3 -2
- package/dist/{ai-service-VAJT5UBS.js → ai-service-ZIPCV3MX.js} +20 -5
- package/dist/api.d.ts +341 -397
- package/dist/{archive-WWDBWYN2.js → archive-INXYFVCW.js} +3 -2
- package/dist/auth-6DMGES3I.js +44 -0
- package/dist/{bridge-RO37CUFM.js → bridge-BVCBTGPF.js} +5 -4
- package/dist/{chat-TCUNPFGO.js → chat-XT4OBJBU.js} +8 -8
- package/dist/{chunk-P7VFDSSG.js → chunk-2FLJ63GU.js} +2 -2
- package/dist/{chunk-ZWKTUQEL.js → chunk-2NGTS5UU.js} +1 -1
- package/dist/{chunk-JGFRDMR6.js → chunk-ALEF47VT.js} +1 -1
- package/dist/{chunk-MDPCSXZ4.js → chunk-D5G5YOPL.js} +163 -15
- package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
- package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
- package/dist/{chunk-DTC6EH5I.js → chunk-I5KY25PQ.js} +1 -9
- package/dist/{chunk-NSBFETWP.js → chunk-IYDIE3HG.js} +64 -26
- package/dist/{chunk-W5OOPLNP.js → chunk-JJ7W6WSB.js} +3 -3
- package/dist/{chunk-G3GBKZGG.js → chunk-LGB6JBHI.js} +54 -2
- package/dist/chunk-LRCG2JLP.js +251 -0
- package/dist/{chunk-FXHXHI2A.js → chunk-LSGWR54X.js} +3 -6
- package/dist/{chunk-S5LR3XYJ.js → chunk-M7UL5S3Q.js} +1 -1
- package/dist/chunk-PB65JZK2.js +85 -0
- package/dist/chunk-PVY5W6QN.js +41 -0
- package/dist/{chunk-QVAQ5454.js → chunk-QBQ424EM.js} +3007 -2126
- package/dist/{chunk-P27RV5WM.js → chunk-QZANELPX.js} +6 -2
- package/dist/{chunk-FSM45XD5.js → chunk-R7E6CRVQ.js} +1 -1
- package/dist/{chunk-HHTXM4JT.js → chunk-RPZZSXV3.js} +39 -195
- package/dist/{chunk-UPA6COHU.js → chunk-RSX4OPZY.js} +5 -5
- package/dist/{chunk-2C2VXEBB.js → chunk-S6NFERDC.js} +21 -57
- package/dist/chunk-SKLSMHXO.js +208 -0
- package/dist/{chunk-IKHDUZRH.js → chunk-SX5TKJBZ.js} +2 -2
- package/dist/chunk-TDRYEPH4.js +185 -0
- package/dist/chunk-TSXLLQZW.js +46 -0
- package/dist/{chunk-EFVHR7KH.js → chunk-UKVWJRKN.js} +24 -5
- package/dist/{chunk-2NDZC3S7.js → chunk-WKF5FEFK.js} +688 -389
- package/dist/cli.js +93 -24
- package/dist/{clock-G3ALCMLJ.js → clock-2UOZ6JPU.js} +11 -8
- package/dist/{cloud-sync-JV4LJOK3.js → cloud-sync-JN3NWKEM.js} +16 -14
- package/dist/config-H2H4UIF7.js +72 -0
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-7KVQV7EZ.js → conversations-3O5O6AS3.js} +8 -7
- package/dist/{create-JTLS7GX3.js → create-RNLNCORE.js} +5 -4
- package/dist/{create-VQSQHJQW.js → create-WBBYI6V7.js} +6 -2
- package/dist/daemon-client-6QXHZ7US.js +12 -0
- package/dist/{daemon-restart-4JGBHEJ4.js → daemon-restart-NGFHFAUF.js} +7 -7
- package/dist/daemon.js +2446 -1999
- package/dist/{db-HMFPIRO2.js → db-F34YLV7D.js} +2 -1
- package/dist/db-RA45JBFG.js +16 -0
- package/dist/{delete-JESHKE7F.js → delete-QTGWEDBI.js} +1 -1
- package/dist/delivery-manager-SDVXFD4W.js +28 -0
- package/dist/delivery-router-FL45JL7N.js +21 -0
- package/dist/down-TB3ESMNP.js +14 -0
- package/dist/{env-CLXXT7M2.js → env-RLYQBOOP.js} +5 -4
- package/dist/{export-EGA5M5PB.js → export-SUYRLI5Q.js} +4 -3
- package/dist/{extension-WZ4SUPJB.js → extension-FQ5D3NCC.js} +6 -6
- package/dist/{extensions-ECO4RPFQ.js → extensions-GDYWQXC4.js} +9 -7
- package/dist/{files-4VEJDASH.js → files-EAMPO2SJ.js} +6 -5
- package/dist/{history-EJMMLXDO.js → history-FO5PHBQ5.js} +9 -4
- package/dist/{import-YCGPMBSI.js → import-DDUFE7AY.js} +4 -3
- package/dist/{join-2GBJKZEN.js → join-I5QEE3LG.js} +1 -1
- package/dist/{list-Q6O7FGAN.js → list-DW2VRTOZ.js} +5 -4
- package/dist/{login-RL6AU2SM.js → login-7CHPW2PN.js} +5 -4
- package/dist/{login-RET5WESK.js → login-RIJF2F4G.js} +3 -2
- package/dist/{logout-CGAGJN3L.js → logout-5MLHZALK.js} +3 -2
- package/dist/{logout-JRPBEMMR.js → logout-UZJRGY4Z.js} +3 -2
- package/dist/message-delivery-2FIM7QKO.js +32 -0
- package/dist/{mind-LUWRQUQ5.js → mind-2B6M7Y25.js} +18 -18
- package/dist/{mind-activity-tracker-VYN2ZZ2M.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
- package/dist/{mind-list-V5WW5DUA.js → mind-list-WUPMQDYQ.js} +3 -2
- package/dist/mind-manager-BNCMGYXW.js +28 -0
- package/dist/mind-service-AV273WT4.js +34 -0
- package/dist/{mind-sleep-R6PTNNW4.js → mind-sleep-B7BHJLH7.js} +5 -4
- package/dist/{mind-status-I4ISFJ6I.js → mind-status-L3EFFRPR.js} +3 -2
- package/dist/{mind-wake-67ZQEWAV.js → mind-wake-GY3RFX7Y.js} +5 -4
- package/dist/{package-OYUD4ZJ4.js → package-PK6JUFL3.js} +3 -3
- package/dist/read-5AMJRO3D.js +75 -0
- package/dist/{register-NZDSTLP3.js → register-V2JZZKFK.js} +5 -4
- package/dist/{registry-ODSALQQL.js → registry-PJ4S5PHQ.js} +8 -1
- package/dist/{reject-2HZOJEIJ.js → reject-33HEZMZ4.js} +5 -4
- package/dist/{restart-QHS3NT64.js → restart-3UCMRUVC.js} +5 -4
- package/dist/{sandbox-O5FUSF43.js → sandbox-JANNTX6U.js} +4 -3
- package/dist/schema-PA3M5ZKH.js +32 -0
- package/dist/seed-ALUQ55FF.js +112 -0
- package/dist/{send-OAN3RYYY.js → send-3MI36LEF.js} +58 -69
- package/dist/{setup-QMDK5RZX.js → setup-SZIARWI6.js} +5 -4
- package/dist/{setup-XJH3E7YM.js → setup-WENLVPVP.js} +9 -9
- package/dist/{skill-FZIN4W4Q.js → skill-TUVOTW4Z.js} +5 -4
- package/dist/skills/dreaming/SKILL.md +6 -4
- package/dist/skills/dreaming/references/INSTALL.md +4 -5
- package/dist/skills/dreaming/scripts/dream.ts +5 -27
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
- package/dist/skills/imagegen/SKILL.md +6 -5
- package/dist/skills/imagegen/references/INSTALL.md +1 -1
- package/dist/skills/resonance/SKILL.md +4 -1
- package/dist/skills/resonance/references/INSTALL.md +2 -2
- package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
- package/dist/skills/resonance/scripts/resonance.ts +35 -5
- package/dist/skills/volute-admin/SKILL.md +83 -0
- package/dist/skills/volute-mind/SKILL.md +12 -12
- package/dist/skills-XNZK6P4K.js +61 -0
- package/dist/sleep-manager-53DZOWW7.js +32 -0
- package/dist/spirit-N4W4UQRH.js +217 -0
- package/dist/{split-EXYGGGQN.js → split-STOROBYJ.js} +1 -1
- package/dist/{sprout-AXQ6H5DB.js → sprout-L2GFOVF7.js} +9 -8
- package/dist/{start-MTOVL6SY.js → start-K2NCUUCG.js} +5 -4
- package/dist/{status-ZRO37MWR.js → status-TCUMUO6M.js} +5 -5
- package/dist/{stop-OK5WEPVC.js → stop-H26JZDXF.js} +5 -4
- package/dist/system-chat-NPYFYZVI.js +32 -0
- package/dist/{systems-W3BBMSOZ.js → systems-DHBKVYEY.js} +6 -5
- package/dist/{tailscale-BM72RXCJ.js → tailscale-XHQBZROW.js} +2 -1
- package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
- package/dist/up-6I6BHRTO.js +17 -0
- package/dist/{update-PLPHMMZ2.js → update-QVPRF6GR.js} +5 -5
- package/dist/{update-check-CVCN7MF6.js → update-check-ZD6OOIYQ.js} +3 -2
- package/dist/{upgrade-I6NPCYUU.js → upgrade-O4Q7WJM3.js} +12 -14
- package/dist/{version-notify-2NTWVEHL.js → version-notify-TCKWBZZG.js} +22 -23
- package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
- package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
- package/dist/web-assets/ext-theme.css +94 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0000_baseline.sql +152 -0
- package/drizzle/0001_add_conversation_private.sql +1 -0
- package/drizzle/0002_turns.sql +21 -0
- package/drizzle/0003_turn_feed_links.sql +11 -0
- package/drizzle/0004_spirits.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +3 -223
- package/drizzle/meta/0001_snapshot.json +3 -294
- package/drizzle/meta/0002_snapshot.json +3 -335
- package/drizzle/meta/0003_snapshot.json +3 -413
- package/drizzle/meta/0004_snapshot.json +3 -406
- package/drizzle/meta/_journal.json +10 -101
- package/package.json +3 -3
- package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
- package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
- package/packages/extensions/notes/dist/ui/index.html +2 -2
- package/packages/extensions/notes/skills/notes/SKILL.md +8 -8
- package/packages/extensions/pages/skills/pages/SKILL.md +17 -44
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/bin/volute +27 -0
- package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
- package/templates/_base/home/.config/routes.json +1 -1
- package/templates/_base/src/lib/auto-commit.ts +82 -43
- package/templates/_base/src/lib/daemon-client.ts +40 -36
- package/templates/_base/src/lib/format-prefix.ts +1 -0
- package/templates/_base/src/lib/hook-loader.ts +155 -0
- package/templates/_base/src/lib/router.ts +17 -1
- package/templates/_base/src/lib/startup.ts +17 -12
- package/templates/_base/src/lib/transparency.ts +2 -2
- package/templates/_base/src/lib/volute-server.ts +2 -5
- package/templates/claude/.init/.claude/settings.json +1 -1
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/claude/src/agent.ts +97 -14
- package/templates/claude/src/lib/hooks/auto-commit.ts +7 -3
- package/templates/claude/src/lib/message-channel.ts +7 -2
- package/templates/claude/src/server.ts +0 -9
- package/templates/codex/.init/.config/routes.json +11 -0
- package/templates/codex/.init/AGENTS.md +29 -0
- package/templates/codex/home/.config/config.json.tmpl +7 -0
- package/templates/codex/package.json.tmpl +20 -0
- package/templates/codex/src/agent.ts +553 -0
- package/templates/codex/src/lib/content.ts +16 -0
- package/templates/codex/src/lib/session-store.ts +56 -0
- package/templates/codex/src/server.ts +59 -0
- package/templates/codex/volute-template.json +8 -0
- package/templates/pi/.init/.config/routes.json +2 -2
- package/templates/pi/package.json.tmpl +1 -1
- package/templates/pi/src/agent.ts +63 -9
- package/templates/pi/src/lib/event-handler.ts +6 -4
- package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
- package/dist/chunk-7D47T4RB.js +0 -84
- package/dist/chunk-CVH6Y2YG.js +0 -59
- package/dist/chunk-EFP3PE6C.js +0 -232
- package/dist/chunk-LIRWLNAK.js +0 -729
- package/dist/daemon-client-BCTFGVCZ.js +0 -9
- package/dist/down-NGBMGORS.js +0 -14
- package/dist/message-delivery-6YMVNOEC.js +0 -28
- package/dist/migrate-registry-to-db-FK35IPEH.js +0 -110
- package/dist/mind-manager-YFCOIAAX.js +0 -18
- package/dist/pages-watcher-Z3PKNROC.js +0 -21
- package/dist/read-WQMPTSN2.js +0 -46
- package/dist/seed-WUQMPLDM.js +0 -71
- package/dist/skills/sessions/SKILL.md +0 -49
- package/dist/sleep-manager-O7YQFCV5.js +0 -30
- package/dist/up-BXUAIDXB.js +0 -17
- package/dist/web-assets/assets/index--kREqKl9.js +0 -72
- package/dist/web-assets/assets/index-BXYTG0nJ.css +0 -1
- package/drizzle/0000_flaky_mariko_yashida.sql +0 -34
- package/drizzle/0001_careless_warpath.sql +0 -12
- package/drizzle/0002_wealthy_the_call.sql +0 -6
- package/drizzle/0003_clean_ego.sql +0 -12
- package/drizzle/0004_magical_silverclaw.sql +0 -1
- package/drizzle/0005_rename_agents_to_minds.sql +0 -11
- package/drizzle/0006_mind_history.sql +0 -20
- package/drizzle/0007_system_prompts.sql +0 -5
- package/drizzle/0008_volute_channels.sql +0 -24
- package/drizzle/0009_shared_skills.sql +0 -9
- package/drizzle/0010_delivery_queue.sql +0 -12
- package/drizzle/0011_rename_human_to_brain.sql +0 -1
- package/drizzle/0012_activity.sql +0 -11
- package/drizzle/0013_user_profiles.sql +0 -3
- package/drizzle/0014_conversation_reads.sql +0 -7
- package/drizzle/0015_notes.sql +0 -23
- package/drizzle/0016_note_reactions_and_replies.sql +0 -15
- package/drizzle/0017_minds.sql +0 -16
- package/drizzle/meta/0005_snapshot.json +0 -410
- package/drizzle/meta/0006_snapshot.json +0 -7
- package/drizzle/meta/0007_snapshot.json +0 -7
- package/drizzle/meta/0008_snapshot.json +0 -7
- package/drizzle/meta/0009_snapshot.json +0 -7
- package/drizzle/meta/0010_snapshot.json +0 -7
- package/drizzle/meta/0011_snapshot.json +0 -7
- package/drizzle/meta/0012_snapshot.json +0 -7
- package/drizzle/meta/0013_snapshot.json +0 -7
- package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +0 -2
- package/packages/extensions/notes/skills/notes/scripts/notes.mjs +0 -185
- package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
- package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
- package/templates/_base/home/public/.gitkeep +0 -0
- package/templates/_base/src/lib/session-monitor.ts +0 -400
- package/templates/claude/src/lib/hooks/session-context.ts +0 -32
- package/templates/pi/src/lib/session-context-extension.ts +0 -35
- /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger_default
|
|
4
|
+
} from "./chunk-YUIHSKR6.js";
|
|
5
|
+
import {
|
|
6
|
+
mindDir
|
|
7
|
+
} from "./chunk-LRCG2JLP.js";
|
|
8
|
+
|
|
9
|
+
// src/lib/delivery/delivery-router.ts
|
|
10
|
+
import { readFileSync, statSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
function extractTextContent(content) {
|
|
13
|
+
if (typeof content === "string") return content;
|
|
14
|
+
if (Array.isArray(content)) {
|
|
15
|
+
return content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(content);
|
|
18
|
+
}
|
|
19
|
+
var configCache = /* @__PURE__ */ new Map();
|
|
20
|
+
var statCheckCache = /* @__PURE__ */ new Map();
|
|
21
|
+
var STAT_TTL_MS = 5e3;
|
|
22
|
+
var dlog = logger_default.child("delivery-router");
|
|
23
|
+
var dirOverrides = /* @__PURE__ */ new Map();
|
|
24
|
+
function registerMindDir(name, dir) {
|
|
25
|
+
dirOverrides.set(name, dir);
|
|
26
|
+
}
|
|
27
|
+
function configPath(mindName) {
|
|
28
|
+
const dir = dirOverrides.get(mindName) ?? mindDir(mindName);
|
|
29
|
+
return resolve(dir, "home/.config/routes.json");
|
|
30
|
+
}
|
|
31
|
+
function getRoutingConfig(mindName) {
|
|
32
|
+
const path = configPath(mindName);
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const statCached = statCheckCache.get(mindName);
|
|
35
|
+
const cached = configCache.get(mindName);
|
|
36
|
+
if (statCached && cached && now - statCached.checkedAt < STAT_TTL_MS) {
|
|
37
|
+
return cached.config;
|
|
38
|
+
}
|
|
39
|
+
let mtime;
|
|
40
|
+
try {
|
|
41
|
+
mtime = statSync(path).mtimeMs;
|
|
42
|
+
} catch {
|
|
43
|
+
configCache.delete(mindName);
|
|
44
|
+
statCheckCache.delete(mindName);
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
statCheckCache.set(mindName, { mtime, checkedAt: now });
|
|
48
|
+
if (cached && cached.mtime === mtime) {
|
|
49
|
+
return cached.config;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const config = JSON.parse(readFileSync(path, "utf-8"));
|
|
53
|
+
configCache.set(mindName, { config, mtime });
|
|
54
|
+
return config;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
dlog.warn(`failed to load routes.json for ${mindName}`, logger_default.errorData(err));
|
|
57
|
+
configCache.delete(mindName);
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
var globRegexCache = /* @__PURE__ */ new Map();
|
|
62
|
+
function clearConfigCache(mindName) {
|
|
63
|
+
if (mindName) {
|
|
64
|
+
configCache.delete(mindName);
|
|
65
|
+
statCheckCache.delete(mindName);
|
|
66
|
+
} else {
|
|
67
|
+
configCache.clear();
|
|
68
|
+
statCheckCache.clear();
|
|
69
|
+
globRegexCache.clear();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function globMatch(pattern, value) {
|
|
73
|
+
let regex = globRegexCache.get(pattern);
|
|
74
|
+
if (!regex) {
|
|
75
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
76
|
+
regex = new RegExp(`^${escaped}$`);
|
|
77
|
+
globRegexCache.set(pattern, regex);
|
|
78
|
+
}
|
|
79
|
+
return regex.test(value);
|
|
80
|
+
}
|
|
81
|
+
var GLOB_MATCH_KEYS = /* @__PURE__ */ new Set(["channel", "sender"]);
|
|
82
|
+
var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["session", "destination", "path", "mode", "batch"]);
|
|
83
|
+
function ruleMatches(rule, meta) {
|
|
84
|
+
for (const [key, pattern] of Object.entries(rule)) {
|
|
85
|
+
if (NON_MATCH_KEYS.has(key)) continue;
|
|
86
|
+
if (key === "isDM") {
|
|
87
|
+
if (typeof pattern !== "boolean") return false;
|
|
88
|
+
if ((meta.isDM ?? false) !== pattern) return false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (key === "participants") {
|
|
92
|
+
if (typeof pattern !== "number") return false;
|
|
93
|
+
if ((meta.participantCount ?? 0) !== pattern) return false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (typeof pattern !== "string") return false;
|
|
97
|
+
if (!GLOB_MATCH_KEYS.has(key)) return false;
|
|
98
|
+
const value = meta[key] ?? "";
|
|
99
|
+
if (!globMatch(pattern, value)) return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function expandTemplate(template, meta) {
|
|
104
|
+
return template.replace(/\$\{sender\}/g, meta.sender ?? "unknown").replace(/\$\{channel\}/g, meta.channel ?? "unknown");
|
|
105
|
+
}
|
|
106
|
+
function sanitizeSessionName(name) {
|
|
107
|
+
return name.replace(/\0/g, "").replace(/[/\\]/g, "-").replace(/\.\./g, "-").slice(0, 100);
|
|
108
|
+
}
|
|
109
|
+
function resolveRoute(config, meta) {
|
|
110
|
+
const fallback = config.default ?? "main";
|
|
111
|
+
if (!config.rules) {
|
|
112
|
+
return { destination: "mind", session: fallback, matched: false };
|
|
113
|
+
}
|
|
114
|
+
for (const rule of config.rules) {
|
|
115
|
+
if (ruleMatches(rule, meta)) {
|
|
116
|
+
if (rule.destination === "file") {
|
|
117
|
+
if (!rule.path) {
|
|
118
|
+
dlog.warn("file destination rule missing path \u2014 falling through");
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
return { destination: "file", path: rule.path, matched: true };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
destination: "mind",
|
|
125
|
+
session: sanitizeSessionName(expandTemplate(rule.session ?? fallback, meta)),
|
|
126
|
+
matched: true,
|
|
127
|
+
mode: rule.mode,
|
|
128
|
+
rule
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { destination: "mind", session: fallback, matched: false };
|
|
133
|
+
}
|
|
134
|
+
var DEFAULT_BATCH_DEBOUNCE = 5;
|
|
135
|
+
var DEFAULT_BATCH_MAX_WAIT = 120;
|
|
136
|
+
function normalizeBatchConfig(batch) {
|
|
137
|
+
if (typeof batch === "number") return { maxWait: batch * 60 };
|
|
138
|
+
return batch;
|
|
139
|
+
}
|
|
140
|
+
function resolveDeliveryMode(config, sessionName, rule) {
|
|
141
|
+
const ruleBatch = rule?.batch;
|
|
142
|
+
const defaults = {
|
|
143
|
+
delivery: { mode: "immediate" },
|
|
144
|
+
interrupt: true
|
|
145
|
+
};
|
|
146
|
+
if (!config.sessions) {
|
|
147
|
+
if (ruleBatch != null) {
|
|
148
|
+
const batch = normalizeBatchConfig(ruleBatch);
|
|
149
|
+
return {
|
|
150
|
+
delivery: {
|
|
151
|
+
mode: "batch",
|
|
152
|
+
debounce: batch.debounce ?? DEFAULT_BATCH_DEBOUNCE,
|
|
153
|
+
maxWait: batch.maxWait ?? DEFAULT_BATCH_MAX_WAIT,
|
|
154
|
+
triggers: batch.triggers
|
|
155
|
+
},
|
|
156
|
+
interrupt: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return defaults;
|
|
160
|
+
}
|
|
161
|
+
for (const [pattern, sessionConfig] of Object.entries(config.sessions)) {
|
|
162
|
+
if (globMatch(pattern, sessionName)) {
|
|
163
|
+
let delivery;
|
|
164
|
+
if (sessionConfig.delivery == null || sessionConfig.delivery === "immediate") {
|
|
165
|
+
delivery = { mode: "immediate" };
|
|
166
|
+
} else if (sessionConfig.delivery === "batch") {
|
|
167
|
+
delivery = {
|
|
168
|
+
mode: "batch",
|
|
169
|
+
debounce: DEFAULT_BATCH_DEBOUNCE,
|
|
170
|
+
maxWait: DEFAULT_BATCH_MAX_WAIT
|
|
171
|
+
};
|
|
172
|
+
} else {
|
|
173
|
+
delivery = {
|
|
174
|
+
mode: "batch",
|
|
175
|
+
debounce: sessionConfig.delivery.debounce ?? DEFAULT_BATCH_DEBOUNCE,
|
|
176
|
+
maxWait: sessionConfig.delivery.maxWait ?? DEFAULT_BATCH_MAX_WAIT
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
delivery,
|
|
181
|
+
interrupt: true,
|
|
182
|
+
instructions: sessionConfig.instructions
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (ruleBatch != null) {
|
|
187
|
+
const batch = normalizeBatchConfig(ruleBatch);
|
|
188
|
+
return {
|
|
189
|
+
delivery: {
|
|
190
|
+
mode: "batch",
|
|
191
|
+
debounce: batch.debounce ?? DEFAULT_BATCH_DEBOUNCE,
|
|
192
|
+
maxWait: batch.maxWait ?? DEFAULT_BATCH_MAX_WAIT,
|
|
193
|
+
triggers: batch.triggers
|
|
194
|
+
},
|
|
195
|
+
interrupt: true
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return defaults;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
extractTextContent,
|
|
203
|
+
registerMindDir,
|
|
204
|
+
getRoutingConfig,
|
|
205
|
+
clearConfigCache,
|
|
206
|
+
resolveRoute,
|
|
207
|
+
resolveDeliveryMode
|
|
208
|
+
};
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
} from "./chunk-YUIHSKR6.js";
|
|
5
5
|
import {
|
|
6
6
|
readGlobalConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TSXLLQZW.js";
|
|
8
8
|
import {
|
|
9
9
|
getBaseName,
|
|
10
10
|
readRegistry,
|
|
11
11
|
voluteHome,
|
|
12
12
|
voluteSystemDir,
|
|
13
13
|
voluteUserHome
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LRCG2JLP.js";
|
|
15
15
|
|
|
16
16
|
// src/lib/sandbox.ts
|
|
17
17
|
import { resolve } from "path";
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
broadcast
|
|
4
|
+
} from "./chunk-QZANELPX.js";
|
|
5
|
+
import {
|
|
6
|
+
getDb
|
|
7
|
+
} from "./chunk-LRCG2JLP.js";
|
|
8
|
+
import {
|
|
9
|
+
users
|
|
10
|
+
} from "./chunk-RPZZSXV3.js";
|
|
11
|
+
|
|
12
|
+
// src/lib/auth.ts
|
|
13
|
+
import { compareSync, hashSync } from "bcryptjs";
|
|
14
|
+
import { and, count, eq, or } from "drizzle-orm";
|
|
15
|
+
var userSelectFields = {
|
|
16
|
+
id: users.id,
|
|
17
|
+
username: users.username,
|
|
18
|
+
role: users.role,
|
|
19
|
+
user_type: users.user_type,
|
|
20
|
+
display_name: users.display_name,
|
|
21
|
+
description: users.description,
|
|
22
|
+
avatar: users.avatar,
|
|
23
|
+
created_at: users.created_at
|
|
24
|
+
};
|
|
25
|
+
async function createUser(username, password) {
|
|
26
|
+
const db = await getDb();
|
|
27
|
+
const hash = hashSync(password, 10);
|
|
28
|
+
const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
|
|
29
|
+
const role = value === 0 ? "admin" : "pending";
|
|
30
|
+
const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning(userSelectFields);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
async function verifyUser(username, password) {
|
|
34
|
+
const db = await getDb();
|
|
35
|
+
const row = await db.select().from(users).where(eq(users.username, username)).get();
|
|
36
|
+
if (!row) return null;
|
|
37
|
+
if (row.user_type === "mind" || row.user_type === "system") return null;
|
|
38
|
+
if (!compareSync(password, row.password_hash)) return null;
|
|
39
|
+
const { password_hash: _, ...user } = row;
|
|
40
|
+
return user;
|
|
41
|
+
}
|
|
42
|
+
async function getUser(id) {
|
|
43
|
+
const db = await getDb();
|
|
44
|
+
const row = await db.select(userSelectFields).from(users).where(eq(users.id, id)).get();
|
|
45
|
+
return row ?? null;
|
|
46
|
+
}
|
|
47
|
+
async function getUserByUsername(username) {
|
|
48
|
+
const db = await getDb();
|
|
49
|
+
const row = await db.select(userSelectFields).from(users).where(eq(users.username, username)).get();
|
|
50
|
+
return row ?? null;
|
|
51
|
+
}
|
|
52
|
+
async function listUsers() {
|
|
53
|
+
const db = await getDb();
|
|
54
|
+
return db.select(userSelectFields).from(users).orderBy(users.created_at).all();
|
|
55
|
+
}
|
|
56
|
+
async function listPendingUsers() {
|
|
57
|
+
const db = await getDb();
|
|
58
|
+
return db.select(userSelectFields).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
|
|
59
|
+
}
|
|
60
|
+
async function listUsersByType(userType) {
|
|
61
|
+
const db = await getDb();
|
|
62
|
+
return db.select(userSelectFields).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
|
|
63
|
+
}
|
|
64
|
+
async function getOrCreateMindUser(mindName) {
|
|
65
|
+
const db = await getDb();
|
|
66
|
+
const existing = await db.select(userSelectFields).from(users).where(
|
|
67
|
+
and(
|
|
68
|
+
eq(users.username, mindName),
|
|
69
|
+
or(eq(users.user_type, "mind"), eq(users.user_type, "system"))
|
|
70
|
+
)
|
|
71
|
+
).get();
|
|
72
|
+
if (existing) return existing;
|
|
73
|
+
try {
|
|
74
|
+
const [result] = await db.insert(users).values({
|
|
75
|
+
username: mindName,
|
|
76
|
+
password_hash: "!mind",
|
|
77
|
+
role: "user",
|
|
78
|
+
user_type: "mind"
|
|
79
|
+
}).returning(userSelectFields);
|
|
80
|
+
return result;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const retried = await db.select(userSelectFields).from(users).where(
|
|
83
|
+
and(
|
|
84
|
+
eq(users.username, mindName),
|
|
85
|
+
or(eq(users.user_type, "mind"), eq(users.user_type, "system"))
|
|
86
|
+
)
|
|
87
|
+
).get();
|
|
88
|
+
if (retried) return retried;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function getOrCreateSystemUser() {
|
|
93
|
+
const db = await getDb();
|
|
94
|
+
const existing = await db.select(userSelectFields).from(users).where(and(eq(users.username, "volute"), eq(users.user_type, "system"))).get();
|
|
95
|
+
if (existing) return existing;
|
|
96
|
+
try {
|
|
97
|
+
const [result] = await db.insert(users).values({
|
|
98
|
+
username: "volute",
|
|
99
|
+
password_hash: "!system",
|
|
100
|
+
role: "system",
|
|
101
|
+
user_type: "system",
|
|
102
|
+
display_name: "Volute"
|
|
103
|
+
}).returning(userSelectFields);
|
|
104
|
+
return result;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
107
|
+
const retried = await db.select(userSelectFields).from(users).where(and(eq(users.username, "volute"), eq(users.user_type, "system"))).get();
|
|
108
|
+
if (retried) return retried;
|
|
109
|
+
}
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function deleteMindUser(mindName) {
|
|
114
|
+
const db = await getDb();
|
|
115
|
+
await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
|
|
116
|
+
}
|
|
117
|
+
async function changePassword(userId, currentPassword, newPassword) {
|
|
118
|
+
const db = await getDb();
|
|
119
|
+
const row = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
120
|
+
if (!row) return false;
|
|
121
|
+
if (!compareSync(currentPassword, row.password_hash)) return false;
|
|
122
|
+
const hash = hashSync(newPassword, 10);
|
|
123
|
+
await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
async function approveUser(id) {
|
|
127
|
+
const db = await getDb();
|
|
128
|
+
await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
|
|
129
|
+
}
|
|
130
|
+
async function countAdmins() {
|
|
131
|
+
const db = await getDb();
|
|
132
|
+
const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.role, "admin"));
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
async function setUserRole(id, role) {
|
|
136
|
+
const db = await getDb();
|
|
137
|
+
const target = await db.select({ id: users.id }).from(users).where(eq(users.id, id)).get();
|
|
138
|
+
if (!target) throw new Error("User not found");
|
|
139
|
+
await db.update(users).set({ role }).where(eq(users.id, id));
|
|
140
|
+
}
|
|
141
|
+
async function deleteUser(id) {
|
|
142
|
+
const db = await getDb();
|
|
143
|
+
const target = await db.select({ id: users.id }).from(users).where(and(eq(users.id, id), eq(users.user_type, "brain"))).get();
|
|
144
|
+
if (!target) throw new Error("User not found");
|
|
145
|
+
await db.delete(users).where(and(eq(users.id, id), eq(users.user_type, "brain")));
|
|
146
|
+
}
|
|
147
|
+
async function updateUserProfile(userId, profile) {
|
|
148
|
+
const db = await getDb();
|
|
149
|
+
const target = await db.select({ id: users.id }).from(users).where(eq(users.id, userId)).get();
|
|
150
|
+
if (!target) throw new Error("User not found");
|
|
151
|
+
await db.update(users).set(profile).where(eq(users.id, userId));
|
|
152
|
+
}
|
|
153
|
+
async function syncMindProfile(mindName, config) {
|
|
154
|
+
const user = await getOrCreateMindUser(mindName);
|
|
155
|
+
const newProfile = {
|
|
156
|
+
display_name: config.displayName ?? null,
|
|
157
|
+
description: config.description ?? null,
|
|
158
|
+
avatar: config.avatar ?? null
|
|
159
|
+
};
|
|
160
|
+
const changed = user.display_name !== newProfile.display_name || user.description !== newProfile.description || user.avatar !== newProfile.avatar;
|
|
161
|
+
if (!changed) return;
|
|
162
|
+
const db = await getDb();
|
|
163
|
+
await db.update(users).set(newProfile).where(eq(users.id, user.id));
|
|
164
|
+
broadcast({ type: "profile_updated", mind: mindName, summary: `${mindName} profile updated` });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
createUser,
|
|
169
|
+
verifyUser,
|
|
170
|
+
getUser,
|
|
171
|
+
getUserByUsername,
|
|
172
|
+
listUsers,
|
|
173
|
+
listPendingUsers,
|
|
174
|
+
listUsersByType,
|
|
175
|
+
getOrCreateMindUser,
|
|
176
|
+
getOrCreateSystemUser,
|
|
177
|
+
deleteMindUser,
|
|
178
|
+
changePassword,
|
|
179
|
+
approveUser,
|
|
180
|
+
countAdmins,
|
|
181
|
+
setUserRole,
|
|
182
|
+
deleteUser,
|
|
183
|
+
updateUserProfile,
|
|
184
|
+
syncMindProfile
|
|
185
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
voluteSystemDir
|
|
4
|
+
} from "./chunk-LRCG2JLP.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/setup.ts
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
function configPath() {
|
|
10
|
+
return resolve(voluteSystemDir(), "config.json");
|
|
11
|
+
}
|
|
12
|
+
function readGlobalConfig() {
|
|
13
|
+
const path = configPath();
|
|
14
|
+
if (!existsSync(path)) return {};
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeGlobalConfig(config) {
|
|
23
|
+
const path = configPath();
|
|
24
|
+
mkdirSync(voluteSystemDir(), { recursive: true });
|
|
25
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
function isSetupComplete() {
|
|
29
|
+
const config = readGlobalConfig();
|
|
30
|
+
return config.setupCompleted === true;
|
|
31
|
+
}
|
|
32
|
+
function migrateSetupCompleted() {
|
|
33
|
+
const config = readGlobalConfig();
|
|
34
|
+
if (config.setup != null && config.setupCompleted == null) {
|
|
35
|
+
config.setupCompleted = true;
|
|
36
|
+
writeGlobalConfig(config);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
configPath,
|
|
42
|
+
readGlobalConfig,
|
|
43
|
+
writeGlobalConfig,
|
|
44
|
+
isSetupComplete,
|
|
45
|
+
migrateSetupCompleted
|
|
46
|
+
};
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
voluteHome,
|
|
4
3
|
voluteSystemDir,
|
|
5
4
|
voluteUserHome
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-LRCG2JLP.js";
|
|
7
6
|
|
|
8
7
|
// src/lib/daemon-client.ts
|
|
9
8
|
import { existsSync, readFileSync } from "fs";
|
|
10
9
|
import { resolve } from "path";
|
|
10
|
+
function readSessionFile(mindDir) {
|
|
11
|
+
try {
|
|
12
|
+
const p = resolve(mindDir, ".mind", "current-session");
|
|
13
|
+
if (existsSync(p)) return readFileSync(p, "utf-8").trim() || void 0;
|
|
14
|
+
} catch (err) {
|
|
15
|
+
const code = err.code;
|
|
16
|
+
if (code !== "ENOENT") {
|
|
17
|
+
console.error(`[volute] failed to read session file: ${code ?? err}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
function readMindSessionFile() {
|
|
23
|
+
const mindDir = process.env.VOLUTE_MIND_DIR;
|
|
24
|
+
if (!mindDir) return void 0;
|
|
25
|
+
return readSessionFile(mindDir);
|
|
26
|
+
}
|
|
11
27
|
function readCliSession() {
|
|
12
28
|
const sessionPath = resolve(voluteUserHome(), "cli-session.json");
|
|
13
29
|
if (!existsSync(sessionPath)) return null;
|
|
@@ -18,9 +34,7 @@ function readCliSession() {
|
|
|
18
34
|
}
|
|
19
35
|
}
|
|
20
36
|
function readDaemonConfig() {
|
|
21
|
-
const
|
|
22
|
-
const legacyPath = resolve(voluteHome(), "daemon.json");
|
|
23
|
-
const configPath = existsSync(newPath) ? newPath : legacyPath;
|
|
37
|
+
const configPath = resolve(voluteSystemDir(), "daemon.json");
|
|
24
38
|
if (!existsSync(configPath)) {
|
|
25
39
|
if (existsSync("/etc/systemd/system/volute.service") && !process.env.VOLUTE_HOME) {
|
|
26
40
|
console.error("Volute is running as a system service but VOLUTE_HOME is not set.");
|
|
@@ -68,6 +82,10 @@ async function daemonFetch(path, options) {
|
|
|
68
82
|
headers.set("Authorization", `Bearer ${cliSession.sessionId}`);
|
|
69
83
|
}
|
|
70
84
|
headers.set("Origin", url);
|
|
85
|
+
const voluteSession = process.env.VOLUTE_SESSION ?? readMindSessionFile();
|
|
86
|
+
if (voluteSession) {
|
|
87
|
+
headers.set("X-Volute-Session", voluteSession);
|
|
88
|
+
}
|
|
71
89
|
try {
|
|
72
90
|
const res = await fetch(`${url}${path}`, { ...options, headers });
|
|
73
91
|
if (res.status === 401 && !path.startsWith("/api/auth/")) {
|
|
@@ -89,5 +107,6 @@ async function daemonFetch(path, options) {
|
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
export {
|
|
110
|
+
readSessionFile,
|
|
92
111
|
daemonFetch
|
|
93
112
|
};
|