volute 0.19.0 → 0.20.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 +66 -66
- package/dist/activity-events-OMXKXD5N.js +16 -0
- package/dist/{chunk-Z524RFCJ.js → chunk-5XNT2472.js} +1 -1
- package/dist/{chunk-FGV2H4TX.js → chunk-FGSYHIS3.js} +112 -24
- package/dist/chunk-GZ7DW4YL.js +97 -0
- package/dist/{chunk-OTWLI7F4.js → chunk-IKMY5X76.js} +2 -2
- package/dist/{chunk-VQWDC6UK.js → chunk-NSE7VJQA.js} +17 -0
- package/dist/{chunk-EMQSAY3B.js → chunk-O6ASDHFO.js} +2 -1
- package/dist/{chunk-2TJGRJ4O.js → chunk-PUVXOZ6T.js} +8 -2
- package/dist/{chunk-4KPUF5JD.js → chunk-TIWH32HP.js} +15 -2
- package/dist/chunk-UU7A7KLB.js +58 -0
- package/dist/cli.js +19 -9
- package/dist/{daemon-restart-JMZM3QY4.js → daemon-restart-KPSWNYTH.js} +3 -3
- package/dist/daemon.js +1802 -1082
- package/dist/{db-5ZVC6MQF.js → db-C2CJ46ZU.js} +2 -2
- package/dist/{delivery-manager-ISTJMZDW.js → delivery-manager-CSG7LXA4.js} +3 -3
- package/dist/{export-GCDNQCF3.js → export-6QBUOQGC.js} +2 -2
- package/dist/file-C57SK5DK.js +204 -0
- package/dist/{import-M63VIUJ5.js → import-XEC34Y4Z.js} +1 -1
- package/dist/{mind-PQ5NCPSU.js → mind-Z7CKD6DG.js} +2 -2
- package/dist/mind-activity-tracker-624QLQLC.js +19 -0
- package/dist/{mind-manager-RVCFROAY.js → mind-manager-3DMYKZPB.js} +3 -3
- package/dist/{package-MYE2ZJLV.js → package-4NHAVUUI.js} +1 -1
- package/dist/{pages-AXCOSY3P.js → pages-4DGQT7ZA.js} +2 -2
- package/dist/{publish-YB377JB7.js → publish-TAJUET4I.js} +7 -4
- package/dist/{schedule-LMX7GAQZ.js → schedule-FFZG23IW.js} +25 -5
- package/dist/{schema-5BW7DFZI.js → schema-GFH6RV3W.js} +3 -1
- package/dist/{setup-OH3PJUJO.js → setup-52YRV7VP.js} +16 -0
- package/dist/skills/volute-mind/SKILL.md +33 -3
- package/dist/{sprout-VBEX63LX.js → sprout-QN7Y4VVO.js} +3 -3
- package/dist/{status-JCJAOXTW.js → status-FU2PFVVF.js} +3 -2
- package/dist/{up-WG65SWJU.js → up-FS7CKM6V.js} +1 -1
- package/dist/web-assets/assets/index-CUZTZzaW.js +64 -0
- package/dist/web-assets/assets/index-adVuCkqy.css +1 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0012_activity.sql +11 -0
- package/drizzle/meta/0012_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/home/.config/routes.json +2 -2
- package/templates/_base/home/VOLUTE.md +1 -1
- package/templates/_base/src/lib/daemon-client.ts +22 -0
- package/templates/_base/src/lib/transparency.ts +1 -1
- package/templates/claude/.init/.config/routes.json +7 -1
- package/templates/pi/.init/.config/routes.json +7 -1
- package/templates/pi/src/agent.ts +11 -5
- package/templates/pi/src/lib/session-context-extension.ts +6 -4
- package/templates/pi/src/server.ts +2 -0
- package/dist/web-assets/assets/index-BAbuRsVF.css +0 -1
- package/dist/web-assets/assets/index-CiQhSKi_.js +0 -63
- /package/dist/{chunk-VE4D3GOP.js → chunk-7UFKREVW.js} +0 -0
package/dist/daemon.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
syncBuiltinSkills,
|
|
15
15
|
uninstallSkill,
|
|
16
16
|
updateSkill
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-IKMY5X76.js";
|
|
18
18
|
import {
|
|
19
19
|
addSharedWorktree,
|
|
20
20
|
ensureSharedRepo,
|
|
@@ -23,17 +23,16 @@ import {
|
|
|
23
23
|
sharedMerge,
|
|
24
24
|
sharedPull,
|
|
25
25
|
sharedStatus
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-TIWH32HP.js";
|
|
27
27
|
import {
|
|
28
28
|
readSystemsConfig
|
|
29
29
|
} from "./chunk-FCDU5BFX.js";
|
|
30
30
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} from "./chunk-FGV2H4TX.js";
|
|
31
|
+
getActiveMinds,
|
|
32
|
+
markIdle,
|
|
33
|
+
onMindEvent,
|
|
34
|
+
stopAll
|
|
35
|
+
} from "./chunk-GZ7DW4YL.js";
|
|
37
36
|
import {
|
|
38
37
|
PROMPT_DEFAULTS,
|
|
39
38
|
PROMPT_KEYS,
|
|
@@ -48,7 +47,22 @@ import {
|
|
|
48
47
|
loadJsonMap,
|
|
49
48
|
saveJsonMap,
|
|
50
49
|
substitute
|
|
51
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-PUVXOZ6T.js";
|
|
51
|
+
import {
|
|
52
|
+
deliverMessage,
|
|
53
|
+
extractTextContent,
|
|
54
|
+
getDeliveryManager,
|
|
55
|
+
getTypingMap,
|
|
56
|
+
initDeliveryManager,
|
|
57
|
+
publish,
|
|
58
|
+
publishTypingForChannels,
|
|
59
|
+
subscribe
|
|
60
|
+
} from "./chunk-FGSYHIS3.js";
|
|
61
|
+
import {
|
|
62
|
+
broadcast,
|
|
63
|
+
publish as publish2,
|
|
64
|
+
subscribe as subscribe2
|
|
65
|
+
} from "./chunk-UU7A7KLB.js";
|
|
52
66
|
import {
|
|
53
67
|
logBuffer,
|
|
54
68
|
logger_default
|
|
@@ -64,7 +78,7 @@ import {
|
|
|
64
78
|
parseNameFromIdentity,
|
|
65
79
|
readVoluteConfig,
|
|
66
80
|
writeVoluteConfig
|
|
67
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-O6ASDHFO.js";
|
|
68
82
|
import {
|
|
69
83
|
loadMergedEnv,
|
|
70
84
|
mindEnvPath,
|
|
@@ -74,8 +88,9 @@ import {
|
|
|
74
88
|
} from "./chunk-VDWCHYTS.js";
|
|
75
89
|
import {
|
|
76
90
|
getDb
|
|
77
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-5XNT2472.js";
|
|
78
92
|
import {
|
|
93
|
+
activity,
|
|
79
94
|
conversationParticipants,
|
|
80
95
|
conversations,
|
|
81
96
|
messages,
|
|
@@ -83,7 +98,7 @@ import {
|
|
|
83
98
|
sessions,
|
|
84
99
|
systemPrompts,
|
|
85
100
|
users
|
|
86
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-NSE7VJQA.js";
|
|
87
102
|
import "./chunk-D424ZQGI.js";
|
|
88
103
|
import {
|
|
89
104
|
exec,
|
|
@@ -135,10 +150,10 @@ import {
|
|
|
135
150
|
import "./chunk-K3NQKI34.js";
|
|
136
151
|
|
|
137
152
|
// src/daemon.ts
|
|
138
|
-
import { randomBytes } from "crypto";
|
|
139
|
-
import { mkdirSync as
|
|
153
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
154
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
140
155
|
import { homedir as homedir2 } from "os";
|
|
141
|
-
import { resolve as
|
|
156
|
+
import { resolve as resolve22 } from "path";
|
|
142
157
|
import { format } from "util";
|
|
143
158
|
|
|
144
159
|
// src/lib/connector-manager.ts
|
|
@@ -382,19 +397,19 @@ var ConnectorManager = class {
|
|
|
382
397
|
const stopKey = `${mindName}:${type}`;
|
|
383
398
|
this.stopping.add(stopKey);
|
|
384
399
|
mindMap.delete(type);
|
|
385
|
-
await new Promise((
|
|
386
|
-
tracked.child.on("exit", () =>
|
|
400
|
+
await new Promise((resolve23) => {
|
|
401
|
+
tracked.child.on("exit", () => resolve23());
|
|
387
402
|
try {
|
|
388
403
|
tracked.child.kill("SIGTERM");
|
|
389
404
|
} catch {
|
|
390
|
-
|
|
405
|
+
resolve23();
|
|
391
406
|
}
|
|
392
407
|
setTimeout(() => {
|
|
393
408
|
try {
|
|
394
409
|
tracked.child.kill("SIGKILL");
|
|
395
410
|
} catch {
|
|
396
411
|
}
|
|
397
|
-
|
|
412
|
+
resolve23();
|
|
398
413
|
}, 5e3);
|
|
399
414
|
});
|
|
400
415
|
this.stopping.delete(stopKey);
|
|
@@ -885,8 +900,213 @@ function migrateMindState(name) {
|
|
|
885
900
|
}
|
|
886
901
|
}
|
|
887
902
|
|
|
903
|
+
// src/lib/pages-watcher.ts
|
|
904
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, statSync, watch } from "fs";
|
|
905
|
+
import { join, resolve as resolve5 } from "path";
|
|
906
|
+
var watchers = /* @__PURE__ */ new Map();
|
|
907
|
+
var homeWatchers = /* @__PURE__ */ new Map();
|
|
908
|
+
var debounceTimers = /* @__PURE__ */ new Map();
|
|
909
|
+
var sitesCache = null;
|
|
910
|
+
var recentPagesCache = null;
|
|
911
|
+
function startPagesWatcher(mindName, pagesDir) {
|
|
912
|
+
try {
|
|
913
|
+
const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
|
|
914
|
+
if (!filename || !filename.endsWith(".html")) return;
|
|
915
|
+
const key = `${mindName}:${filename}`;
|
|
916
|
+
const existing = debounceTimers.get(key);
|
|
917
|
+
if (existing) clearTimeout(existing);
|
|
918
|
+
debounceTimers.set(
|
|
919
|
+
key,
|
|
920
|
+
setTimeout(() => {
|
|
921
|
+
debounceTimers.delete(key);
|
|
922
|
+
invalidateCache();
|
|
923
|
+
publish2({
|
|
924
|
+
type: "page_updated",
|
|
925
|
+
mind: mindName,
|
|
926
|
+
summary: `${mindName} updated ${filename}`,
|
|
927
|
+
metadata: { file: filename }
|
|
928
|
+
}).catch(
|
|
929
|
+
(err) => logger_default.error("failed to publish page_updated activity", logger_default.errorData(err))
|
|
930
|
+
);
|
|
931
|
+
}, 100)
|
|
932
|
+
);
|
|
933
|
+
});
|
|
934
|
+
watchers.set(mindName, watcher);
|
|
935
|
+
} catch (err) {
|
|
936
|
+
logger_default.warn(`failed to start pages watcher for ${mindName}`, logger_default.errorData(err));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function startWatcher(mindName) {
|
|
940
|
+
if (watchers.has(mindName)) return;
|
|
941
|
+
const pagesDir = resolve5(mindDir(mindName), "home", "pages");
|
|
942
|
+
if (existsSync5(pagesDir)) {
|
|
943
|
+
startPagesWatcher(mindName, pagesDir);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (homeWatchers.has(mindName)) return;
|
|
947
|
+
const homeDir = resolve5(mindDir(mindName), "home");
|
|
948
|
+
if (!existsSync5(homeDir)) return;
|
|
949
|
+
try {
|
|
950
|
+
const hw = watch(homeDir, (_eventType, filename) => {
|
|
951
|
+
if (filename !== "pages") return;
|
|
952
|
+
if (!existsSync5(pagesDir)) return;
|
|
953
|
+
hw.close();
|
|
954
|
+
homeWatchers.delete(mindName);
|
|
955
|
+
invalidateCache();
|
|
956
|
+
startPagesWatcher(mindName, pagesDir);
|
|
957
|
+
});
|
|
958
|
+
homeWatchers.set(mindName, hw);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
logger_default.warn(`failed to start home watcher for ${mindName}`, logger_default.errorData(err));
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function stopWatcher(mindName) {
|
|
964
|
+
const watcher = watchers.get(mindName);
|
|
965
|
+
if (watcher) {
|
|
966
|
+
watcher.close();
|
|
967
|
+
watchers.delete(mindName);
|
|
968
|
+
}
|
|
969
|
+
const hw = homeWatchers.get(mindName);
|
|
970
|
+
if (hw) {
|
|
971
|
+
hw.close();
|
|
972
|
+
homeWatchers.delete(mindName);
|
|
973
|
+
}
|
|
974
|
+
for (const [key, timer] of debounceTimers) {
|
|
975
|
+
if (key.startsWith(`${mindName}:`)) {
|
|
976
|
+
clearTimeout(timer);
|
|
977
|
+
debounceTimers.delete(key);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function stopAllWatchers() {
|
|
982
|
+
for (const [, watcher] of watchers) {
|
|
983
|
+
watcher.close();
|
|
984
|
+
}
|
|
985
|
+
watchers.clear();
|
|
986
|
+
for (const [, hw] of homeWatchers) {
|
|
987
|
+
hw.close();
|
|
988
|
+
}
|
|
989
|
+
homeWatchers.clear();
|
|
990
|
+
for (const [, timer] of debounceTimers) {
|
|
991
|
+
clearTimeout(timer);
|
|
992
|
+
}
|
|
993
|
+
debounceTimers.clear();
|
|
994
|
+
invalidateCache();
|
|
995
|
+
}
|
|
996
|
+
function invalidateCache() {
|
|
997
|
+
sitesCache = null;
|
|
998
|
+
recentPagesCache = null;
|
|
999
|
+
}
|
|
1000
|
+
function scanPagesDir(dir, urlPrefix) {
|
|
1001
|
+
const pages = [];
|
|
1002
|
+
let items;
|
|
1003
|
+
try {
|
|
1004
|
+
items = readdirSync2(dir);
|
|
1005
|
+
} catch {
|
|
1006
|
+
return pages;
|
|
1007
|
+
}
|
|
1008
|
+
for (const item of items) {
|
|
1009
|
+
if (item.startsWith(".")) continue;
|
|
1010
|
+
const fullPath = resolve5(dir, item);
|
|
1011
|
+
try {
|
|
1012
|
+
const s = statSync(fullPath);
|
|
1013
|
+
if (s.isFile() && item.endsWith(".html")) {
|
|
1014
|
+
pages.push({
|
|
1015
|
+
file: item,
|
|
1016
|
+
modified: s.mtime.toISOString(),
|
|
1017
|
+
url: `${urlPrefix}/${item}`
|
|
1018
|
+
});
|
|
1019
|
+
} else if (s.isDirectory()) {
|
|
1020
|
+
const indexPath = resolve5(fullPath, "index.html");
|
|
1021
|
+
if (existsSync5(indexPath)) {
|
|
1022
|
+
const indexStat = statSync(indexPath);
|
|
1023
|
+
pages.push({
|
|
1024
|
+
file: join(item, "index.html"),
|
|
1025
|
+
modified: indexStat.mtime.toISOString(),
|
|
1026
|
+
url: `${urlPrefix}/${item}/`
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
} catch {
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
1034
|
+
return pages;
|
|
1035
|
+
}
|
|
1036
|
+
function buildSites() {
|
|
1037
|
+
const sites = [];
|
|
1038
|
+
const systemPagesDir = resolve5(voluteHome(), "shared", "pages");
|
|
1039
|
+
if (existsSync5(systemPagesDir)) {
|
|
1040
|
+
const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
|
|
1041
|
+
if (systemPages.length > 0) {
|
|
1042
|
+
sites.push({ name: "_system", label: "System", pages: systemPages });
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const entries = readRegistry();
|
|
1046
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1047
|
+
const pagesDir = resolve5(mindDir(entry.name), "home", "pages");
|
|
1048
|
+
if (!existsSync5(pagesDir)) continue;
|
|
1049
|
+
const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
|
|
1050
|
+
if (mindPages.length > 0) {
|
|
1051
|
+
sites.push({ name: entry.name, label: entry.name, pages: mindPages });
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return sites;
|
|
1055
|
+
}
|
|
1056
|
+
function buildRecentPages() {
|
|
1057
|
+
const entries = readRegistry();
|
|
1058
|
+
const pages = [];
|
|
1059
|
+
for (const entry of entries) {
|
|
1060
|
+
const pagesDir = resolve5(mindDir(entry.name), "home", "pages");
|
|
1061
|
+
if (!existsSync5(pagesDir)) continue;
|
|
1062
|
+
let items;
|
|
1063
|
+
try {
|
|
1064
|
+
items = readdirSync2(pagesDir);
|
|
1065
|
+
} catch {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
for (const item of items) {
|
|
1069
|
+
if (item.startsWith(".")) continue;
|
|
1070
|
+
const fullPath = resolve5(pagesDir, item);
|
|
1071
|
+
try {
|
|
1072
|
+
const s = statSync(fullPath);
|
|
1073
|
+
if (s.isFile() && item.endsWith(".html")) {
|
|
1074
|
+
pages.push({
|
|
1075
|
+
mind: entry.name,
|
|
1076
|
+
file: item,
|
|
1077
|
+
modified: s.mtime.toISOString(),
|
|
1078
|
+
url: `/pages/${entry.name}/${item}`
|
|
1079
|
+
});
|
|
1080
|
+
} else if (s.isDirectory()) {
|
|
1081
|
+
const indexPath = resolve5(fullPath, "index.html");
|
|
1082
|
+
if (existsSync5(indexPath)) {
|
|
1083
|
+
const indexStat = statSync(indexPath);
|
|
1084
|
+
pages.push({
|
|
1085
|
+
mind: entry.name,
|
|
1086
|
+
file: join(item, "index.html"),
|
|
1087
|
+
modified: indexStat.mtime.toISOString(),
|
|
1088
|
+
url: `/pages/${entry.name}/${item}/`
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
1097
|
+
return pages.slice(0, 10);
|
|
1098
|
+
}
|
|
1099
|
+
function getCachedSites() {
|
|
1100
|
+
if (!sitesCache) sitesCache = buildSites();
|
|
1101
|
+
return sitesCache;
|
|
1102
|
+
}
|
|
1103
|
+
function getCachedRecentPages() {
|
|
1104
|
+
if (!recentPagesCache) recentPagesCache = buildRecentPages();
|
|
1105
|
+
return recentPagesCache;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
888
1108
|
// src/lib/scheduler.ts
|
|
889
|
-
import { resolve as
|
|
1109
|
+
import { resolve as resolve6 } from "path";
|
|
890
1110
|
import { CronExpressionParser } from "cron-parser";
|
|
891
1111
|
var slog = logger_default.child("scheduler");
|
|
892
1112
|
var Scheduler = class {
|
|
@@ -895,7 +1115,7 @@ var Scheduler = class {
|
|
|
895
1115
|
lastFired = /* @__PURE__ */ new Map();
|
|
896
1116
|
// "mind:scheduleId" → epoch minute
|
|
897
1117
|
get statePath() {
|
|
898
|
-
return
|
|
1118
|
+
return resolve6(voluteHome(), "scheduler-state.json");
|
|
899
1119
|
}
|
|
900
1120
|
start() {
|
|
901
1121
|
this.loadState();
|
|
@@ -959,8 +1179,30 @@ var Scheduler = class {
|
|
|
959
1179
|
}
|
|
960
1180
|
async fire(mindName, schedule) {
|
|
961
1181
|
try {
|
|
962
|
-
|
|
963
|
-
|
|
1182
|
+
let text;
|
|
1183
|
+
if (schedule.script) {
|
|
1184
|
+
const homeDir = resolve6(mindDir(mindName), "home");
|
|
1185
|
+
try {
|
|
1186
|
+
const output = await this.runScript(schedule.script, homeDir, mindName);
|
|
1187
|
+
if (!output.trim()) {
|
|
1188
|
+
slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
text = output;
|
|
1192
|
+
} catch (err) {
|
|
1193
|
+
const stderr = err.stderr ?? "";
|
|
1194
|
+
text = `[script error] ${err.message}${stderr ? `
|
|
1195
|
+
${stderr}` : ""}`;
|
|
1196
|
+
slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
|
|
1197
|
+
}
|
|
1198
|
+
} else if (schedule.message) {
|
|
1199
|
+
text = schedule.message;
|
|
1200
|
+
} else {
|
|
1201
|
+
slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
await this.deliver(mindName, {
|
|
1205
|
+
content: [{ type: "text", text }],
|
|
964
1206
|
channel: "system:scheduler",
|
|
965
1207
|
sender: schedule.id
|
|
966
1208
|
});
|
|
@@ -969,6 +1211,12 @@ var Scheduler = class {
|
|
|
969
1211
|
slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
|
|
970
1212
|
}
|
|
971
1213
|
}
|
|
1214
|
+
runScript(script, cwd, mindName) {
|
|
1215
|
+
return exec("bash", ["-c", script], { cwd, mindName });
|
|
1216
|
+
}
|
|
1217
|
+
deliver(mindName, payload) {
|
|
1218
|
+
return deliverMessage(mindName, payload);
|
|
1219
|
+
}
|
|
972
1220
|
};
|
|
973
1221
|
var instance3 = null;
|
|
974
1222
|
function initScheduler() {
|
|
@@ -982,8 +1230,8 @@ function getScheduler() {
|
|
|
982
1230
|
}
|
|
983
1231
|
|
|
984
1232
|
// src/lib/token-budget.ts
|
|
985
|
-
import { existsSync as
|
|
986
|
-
import { resolve as
|
|
1233
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1234
|
+
import { resolve as resolve7 } from "path";
|
|
987
1235
|
var tlog = logger_default.child("token-budget");
|
|
988
1236
|
var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
|
|
989
1237
|
var MAX_QUEUE_SIZE = 100;
|
|
@@ -1090,7 +1338,7 @@ var TokenBudget = class {
|
|
|
1090
1338
|
}
|
|
1091
1339
|
}
|
|
1092
1340
|
budgetStatePath(mind) {
|
|
1093
|
-
return
|
|
1341
|
+
return resolve7(stateDir(mind), "budget.json");
|
|
1094
1342
|
}
|
|
1095
1343
|
saveBudgetState(mind, state) {
|
|
1096
1344
|
try {
|
|
@@ -1111,7 +1359,7 @@ var TokenBudget = class {
|
|
|
1111
1359
|
loadBudgetState(mind) {
|
|
1112
1360
|
try {
|
|
1113
1361
|
const path = this.budgetStatePath(mind);
|
|
1114
|
-
if (!
|
|
1362
|
+
if (!existsSync6(path)) return null;
|
|
1115
1363
|
const data = JSON.parse(readFileSync4(path, "utf-8"));
|
|
1116
1364
|
if (typeof data.periodStart !== "number" || typeof data.tokensUsed !== "number") return null;
|
|
1117
1365
|
return {
|
|
@@ -1171,6 +1419,11 @@ function getTokenBudget() {
|
|
|
1171
1419
|
async function startMindFull(name) {
|
|
1172
1420
|
const [baseName, variantName] = name.split("@", 2);
|
|
1173
1421
|
await getMindManager().startMind(name);
|
|
1422
|
+
publish2({
|
|
1423
|
+
type: "mind_started",
|
|
1424
|
+
mind: name,
|
|
1425
|
+
summary: `${name} started`
|
|
1426
|
+
}).catch((err) => logger_default.error("failed to publish mind_started activity", logger_default.errorData(err)));
|
|
1174
1427
|
if (variantName) return;
|
|
1175
1428
|
const entry = findMind(baseName);
|
|
1176
1429
|
if (!entry || entry.stage === "seed") return;
|
|
@@ -1189,15 +1442,23 @@ async function startMindFull(name) {
|
|
|
1189
1442
|
config.tokenBudgetPeriodMinutes ?? DEFAULT_BUDGET_PERIOD_MINUTES
|
|
1190
1443
|
);
|
|
1191
1444
|
}
|
|
1445
|
+
startWatcher(baseName);
|
|
1192
1446
|
}
|
|
1193
1447
|
async function stopMindFull(name) {
|
|
1194
1448
|
const [baseName, variantName] = name.split("@", 2);
|
|
1195
1449
|
if (!variantName) {
|
|
1450
|
+
stopWatcher(baseName);
|
|
1451
|
+
markIdle(baseName);
|
|
1196
1452
|
await getConnectorManager().stopConnectors(baseName);
|
|
1197
1453
|
getScheduler().unloadSchedules(baseName);
|
|
1198
1454
|
getTokenBudget().removeBudget(baseName);
|
|
1199
1455
|
}
|
|
1200
1456
|
await getMindManager().stopMind(name);
|
|
1457
|
+
publish2({
|
|
1458
|
+
type: "mind_stopped",
|
|
1459
|
+
mind: name,
|
|
1460
|
+
summary: `${name} stopped`
|
|
1461
|
+
}).catch((err) => logger_default.error("failed to publish mind_stopped activity", logger_default.errorData(err)));
|
|
1201
1462
|
}
|
|
1202
1463
|
|
|
1203
1464
|
// src/web/middleware/auth.ts
|
|
@@ -1418,136 +1679,464 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
1418
1679
|
});
|
|
1419
1680
|
|
|
1420
1681
|
// src/web/server.ts
|
|
1421
|
-
import { existsSync as
|
|
1682
|
+
import { existsSync as existsSync15 } from "fs";
|
|
1422
1683
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1423
|
-
import { dirname as dirname3, extname as extname2, resolve as
|
|
1684
|
+
import { dirname as dirname3, extname as extname2, resolve as resolve21 } from "path";
|
|
1424
1685
|
import { serve } from "@hono/node-server";
|
|
1425
1686
|
|
|
1426
1687
|
// src/web/app.ts
|
|
1427
|
-
import { Hono as
|
|
1688
|
+
import { Hono as Hono25 } from "hono";
|
|
1428
1689
|
import { bodyLimit } from "hono/body-limit";
|
|
1429
1690
|
import { csrf } from "hono/csrf";
|
|
1430
1691
|
import { HTTPException } from "hono/http-exception";
|
|
1431
1692
|
|
|
1432
|
-
// src/web/api/
|
|
1433
|
-
import {
|
|
1693
|
+
// src/web/api/activity.ts
|
|
1694
|
+
import { desc as desc2 } from "drizzle-orm";
|
|
1434
1695
|
import { Hono } from "hono";
|
|
1435
|
-
import {
|
|
1436
|
-
import { z } from "zod";
|
|
1437
|
-
var credentialsSchema = z.object({
|
|
1438
|
-
username: z.string().min(1),
|
|
1439
|
-
password: z.string().min(1)
|
|
1440
|
-
});
|
|
1441
|
-
var changePasswordSchema = z.object({
|
|
1442
|
-
currentPassword: z.string().min(1),
|
|
1443
|
-
newPassword: z.string().min(1)
|
|
1444
|
-
});
|
|
1445
|
-
var authenticated = new Hono().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
1446
|
-
const user = c.get("user");
|
|
1447
|
-
const { currentPassword, newPassword } = c.req.valid("json");
|
|
1448
|
-
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
1449
|
-
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
1450
|
-
return c.json({ ok: true });
|
|
1451
|
-
});
|
|
1452
|
-
var admin = new Hono().use(authMiddleware).get("/users", async (c) => {
|
|
1453
|
-
const user = c.get("user");
|
|
1454
|
-
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
1455
|
-
const minds = readRegistry();
|
|
1456
|
-
for (const mind of minds) {
|
|
1457
|
-
await getOrCreateMindUser(mind.name);
|
|
1458
|
-
}
|
|
1459
|
-
const type = c.req.query("type");
|
|
1460
|
-
if (type === "brain" || type === "mind") {
|
|
1461
|
-
return c.json(await listUsersByType(type));
|
|
1462
|
-
}
|
|
1463
|
-
return c.json(await listUsers());
|
|
1464
|
-
}).get("/users/pending", async (c) => {
|
|
1465
|
-
const user = c.get("user");
|
|
1466
|
-
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
1467
|
-
return c.json(await listPendingUsers());
|
|
1468
|
-
}).post("/users/:id/approve", async (c) => {
|
|
1469
|
-
const user = c.get("user");
|
|
1470
|
-
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
1471
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
1472
|
-
await approveUser(id);
|
|
1473
|
-
return c.json({ ok: true });
|
|
1474
|
-
});
|
|
1475
|
-
var app = new Hono().post("/register", zValidator("json", credentialsSchema), async (c) => {
|
|
1476
|
-
const { username, password } = c.req.valid("json");
|
|
1477
|
-
const existing = await getUserByUsername(username);
|
|
1478
|
-
if (existing) {
|
|
1479
|
-
return c.json({ error: "Username already taken" }, 409);
|
|
1480
|
-
}
|
|
1481
|
-
const user = await createUser(username, password);
|
|
1482
|
-
if (user.role === "admin") {
|
|
1483
|
-
const sessionId = await createSession(user.id);
|
|
1484
|
-
setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
|
|
1485
|
-
}
|
|
1486
|
-
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1487
|
-
}).post("/login", zValidator("json", credentialsSchema), async (c) => {
|
|
1488
|
-
const { username, password } = c.req.valid("json");
|
|
1489
|
-
const user = await verifyUser(username, password);
|
|
1490
|
-
if (!user) {
|
|
1491
|
-
return c.json({ error: "Invalid credentials" }, 401);
|
|
1492
|
-
}
|
|
1493
|
-
const sessionId = await createSession(user.id);
|
|
1494
|
-
setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
|
|
1495
|
-
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1496
|
-
}).post("/logout", async (c) => {
|
|
1497
|
-
const sessionId = getCookie2(c, "volute_session");
|
|
1498
|
-
if (sessionId) {
|
|
1499
|
-
await deleteSession(sessionId);
|
|
1500
|
-
deleteCookie(c, "volute_session", { path: "/" });
|
|
1501
|
-
}
|
|
1502
|
-
return c.json({ ok: true });
|
|
1503
|
-
}).get("/me", async (c) => {
|
|
1504
|
-
const sessionId = getCookie2(c, "volute_session");
|
|
1505
|
-
if (!sessionId) return c.json({ error: "Not logged in" }, 401);
|
|
1506
|
-
const userId = await getSessionUserId(sessionId);
|
|
1507
|
-
if (userId == null) return c.json({ error: "Not logged in" }, 401);
|
|
1508
|
-
const user = await getUser(userId);
|
|
1509
|
-
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
1510
|
-
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1511
|
-
}).route("/", admin).route("/", authenticated);
|
|
1512
|
-
var auth_default = app;
|
|
1696
|
+
import { streamSSE } from "hono/streaming";
|
|
1513
1697
|
|
|
1514
|
-
// src/
|
|
1515
|
-
import {
|
|
1516
|
-
|
|
1517
|
-
|
|
1698
|
+
// src/lib/conversations.ts
|
|
1699
|
+
import { randomUUID } from "crypto";
|
|
1700
|
+
import { and as and2, desc, eq as eq3, inArray, isNull, sql } from "drizzle-orm";
|
|
1701
|
+
async function createConversation(mindName, channel, opts) {
|
|
1702
|
+
const db = await getDb();
|
|
1703
|
+
const id = randomUUID();
|
|
1704
|
+
const type = opts?.type ?? "dm";
|
|
1705
|
+
const name = opts?.name ?? null;
|
|
1706
|
+
await db.transaction(async (tx) => {
|
|
1707
|
+
await tx.insert(conversations).values({
|
|
1708
|
+
id,
|
|
1709
|
+
mind_name: mindName,
|
|
1710
|
+
channel,
|
|
1711
|
+
type,
|
|
1712
|
+
name,
|
|
1713
|
+
user_id: opts?.userId ?? null,
|
|
1714
|
+
title: opts?.title ?? null
|
|
1715
|
+
});
|
|
1716
|
+
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
1717
|
+
await tx.insert(conversationParticipants).values(
|
|
1718
|
+
opts.participantIds.map((uid, i) => ({
|
|
1719
|
+
conversation_id: id,
|
|
1720
|
+
user_id: uid,
|
|
1721
|
+
role: i === 0 ? "owner" : "member"
|
|
1722
|
+
}))
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
return {
|
|
1727
|
+
id,
|
|
1728
|
+
mind_name: mindName,
|
|
1729
|
+
channel,
|
|
1730
|
+
type,
|
|
1731
|
+
name,
|
|
1732
|
+
user_id: opts?.userId ?? null,
|
|
1733
|
+
title: opts?.title ?? null,
|
|
1734
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1735
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1736
|
+
};
|
|
1518
1737
|
}
|
|
1519
|
-
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
}).
|
|
1549
|
-
|
|
1550
|
-
|
|
1738
|
+
async function getConversation(id) {
|
|
1739
|
+
const db = await getDb();
|
|
1740
|
+
const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
|
|
1741
|
+
return row ?? null;
|
|
1742
|
+
}
|
|
1743
|
+
async function addParticipant(conversationId, userId, role = "member") {
|
|
1744
|
+
const db = await getDb();
|
|
1745
|
+
await db.insert(conversationParticipants).values({
|
|
1746
|
+
conversation_id: conversationId,
|
|
1747
|
+
user_id: userId,
|
|
1748
|
+
role
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
async function removeParticipant(conversationId, userId) {
|
|
1752
|
+
const db = await getDb();
|
|
1753
|
+
await db.delete(conversationParticipants).where(
|
|
1754
|
+
and2(
|
|
1755
|
+
eq3(conversationParticipants.conversation_id, conversationId),
|
|
1756
|
+
eq3(conversationParticipants.user_id, userId)
|
|
1757
|
+
)
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
async function getParticipants(conversationId) {
|
|
1761
|
+
const db = await getDb();
|
|
1762
|
+
const rows = await db.select({
|
|
1763
|
+
userId: conversationParticipants.user_id,
|
|
1764
|
+
username: users.username,
|
|
1765
|
+
userType: users.user_type,
|
|
1766
|
+
role: conversationParticipants.role
|
|
1767
|
+
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
|
|
1768
|
+
return rows;
|
|
1769
|
+
}
|
|
1770
|
+
async function isParticipant(conversationId, userId) {
|
|
1771
|
+
const db = await getDb();
|
|
1772
|
+
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
1773
|
+
and2(
|
|
1774
|
+
eq3(conversationParticipants.conversation_id, conversationId),
|
|
1775
|
+
eq3(conversationParticipants.user_id, userId)
|
|
1776
|
+
)
|
|
1777
|
+
).get();
|
|
1778
|
+
return row != null;
|
|
1779
|
+
}
|
|
1780
|
+
async function listConversationsForUser(userId) {
|
|
1781
|
+
const db = await getDb();
|
|
1782
|
+
const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
|
|
1783
|
+
if (participantRows.length === 0) return [];
|
|
1784
|
+
const convIds = participantRows.map((r) => r.conversation_id);
|
|
1785
|
+
return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
1786
|
+
}
|
|
1787
|
+
async function isParticipantOrOwner(conversationId, userId) {
|
|
1788
|
+
if (await isParticipant(conversationId, userId)) return true;
|
|
1789
|
+
const db = await getDb();
|
|
1790
|
+
const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
|
|
1791
|
+
return row != null;
|
|
1792
|
+
}
|
|
1793
|
+
async function deleteConversationForUser(id, userId) {
|
|
1794
|
+
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
1795
|
+
await deleteConversation(id);
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1798
|
+
async function addMessage(conversationId, role, senderName, content) {
|
|
1799
|
+
const db = await getDb();
|
|
1800
|
+
const serialized = JSON.stringify(content);
|
|
1801
|
+
const [result] = await db.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
|
|
1802
|
+
await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
|
|
1803
|
+
if (role === "user") {
|
|
1804
|
+
const firstText = content.find((b) => b.type === "text");
|
|
1805
|
+
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
1806
|
+
if (title) {
|
|
1807
|
+
await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const msg = {
|
|
1811
|
+
id: result.id,
|
|
1812
|
+
conversation_id: conversationId,
|
|
1813
|
+
role,
|
|
1814
|
+
sender_name: senderName,
|
|
1815
|
+
content,
|
|
1816
|
+
created_at: result.created_at
|
|
1817
|
+
};
|
|
1818
|
+
publish(conversationId, {
|
|
1819
|
+
type: "message",
|
|
1820
|
+
id: msg.id,
|
|
1821
|
+
role: msg.role,
|
|
1822
|
+
senderName: msg.sender_name,
|
|
1823
|
+
content: msg.content,
|
|
1824
|
+
createdAt: msg.created_at
|
|
1825
|
+
});
|
|
1826
|
+
return msg;
|
|
1827
|
+
}
|
|
1828
|
+
async function getMessages(conversationId) {
|
|
1829
|
+
const db = await getDb();
|
|
1830
|
+
const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
1831
|
+
return rows.map((row) => {
|
|
1832
|
+
let content;
|
|
1833
|
+
try {
|
|
1834
|
+
const parsed = JSON.parse(row.content);
|
|
1835
|
+
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
1836
|
+
} catch {
|
|
1837
|
+
content = [{ type: "text", text: row.content }];
|
|
1838
|
+
}
|
|
1839
|
+
return { ...row, content };
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
async function listConversationsWithParticipants(userId) {
|
|
1843
|
+
const convs = await listConversationsForUser(userId);
|
|
1844
|
+
if (convs.length === 0) return [];
|
|
1845
|
+
const db = await getDb();
|
|
1846
|
+
const convIds = convs.map((c) => c.id);
|
|
1847
|
+
const rows = await db.select({
|
|
1848
|
+
conversationId: conversationParticipants.conversation_id,
|
|
1849
|
+
userId: users.id,
|
|
1850
|
+
username: users.username,
|
|
1851
|
+
userType: users.user_type,
|
|
1852
|
+
role: conversationParticipants.role
|
|
1853
|
+
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
1854
|
+
const byConv = /* @__PURE__ */ new Map();
|
|
1855
|
+
for (const r of rows) {
|
|
1856
|
+
let arr = byConv.get(r.conversationId);
|
|
1857
|
+
if (!arr) {
|
|
1858
|
+
arr = [];
|
|
1859
|
+
byConv.set(r.conversationId, arr);
|
|
1860
|
+
}
|
|
1861
|
+
arr.push({
|
|
1862
|
+
userId: r.userId,
|
|
1863
|
+
username: r.username,
|
|
1864
|
+
userType: r.userType,
|
|
1865
|
+
role: r.role
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
const lastMsgIds = await db.select({
|
|
1869
|
+
conversationId: messages.conversation_id,
|
|
1870
|
+
maxId: sql`MAX(${messages.id})`
|
|
1871
|
+
}).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
|
|
1872
|
+
const byLastMsg = /* @__PURE__ */ new Map();
|
|
1873
|
+
if (lastMsgIds.length > 0) {
|
|
1874
|
+
const msgRows = await db.select().from(messages).where(
|
|
1875
|
+
inArray(
|
|
1876
|
+
messages.id,
|
|
1877
|
+
lastMsgIds.map((r) => r.maxId)
|
|
1878
|
+
)
|
|
1879
|
+
);
|
|
1880
|
+
for (const m of msgRows) {
|
|
1881
|
+
let text = "";
|
|
1882
|
+
try {
|
|
1883
|
+
const parsed = JSON.parse(m.content);
|
|
1884
|
+
const blocks = Array.isArray(parsed) ? parsed : [];
|
|
1885
|
+
const textBlock = blocks.find((b) => b.type === "text");
|
|
1886
|
+
if (textBlock && "text" in textBlock) text = textBlock.text;
|
|
1887
|
+
} catch {
|
|
1888
|
+
text = m.content;
|
|
1889
|
+
}
|
|
1890
|
+
byLastMsg.set(m.conversation_id, {
|
|
1891
|
+
role: m.role,
|
|
1892
|
+
senderName: m.sender_name,
|
|
1893
|
+
text,
|
|
1894
|
+
createdAt: m.created_at
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return convs.map((c) => ({
|
|
1899
|
+
...c,
|
|
1900
|
+
participants: byConv.get(c.id) ?? [],
|
|
1901
|
+
lastMessage: byLastMsg.get(c.id)
|
|
1902
|
+
}));
|
|
1903
|
+
}
|
|
1904
|
+
async function findDMConversation(mindName, participantIds) {
|
|
1905
|
+
const db = await getDb();
|
|
1906
|
+
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
|
|
1907
|
+
for (const conv of mindConvs) {
|
|
1908
|
+
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
|
|
1909
|
+
if (rows.length !== 2) continue;
|
|
1910
|
+
const ids = new Set(rows.map((r) => r.user_id));
|
|
1911
|
+
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
1912
|
+
return conv.id;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1917
|
+
async function deleteConversation(id) {
|
|
1918
|
+
const db = await getDb();
|
|
1919
|
+
await db.delete(conversations).where(eq3(conversations.id, id));
|
|
1920
|
+
}
|
|
1921
|
+
async function createChannel(name, creatorId) {
|
|
1922
|
+
const participantIds = creatorId ? [creatorId] : [];
|
|
1923
|
+
return createConversation(null, "volute", {
|
|
1924
|
+
type: "channel",
|
|
1925
|
+
name,
|
|
1926
|
+
title: name,
|
|
1927
|
+
participantIds
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
async function getChannelByName(name) {
|
|
1931
|
+
const db = await getDb();
|
|
1932
|
+
const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
|
|
1933
|
+
return row ?? null;
|
|
1934
|
+
}
|
|
1935
|
+
async function listChannels() {
|
|
1936
|
+
const db = await getDb();
|
|
1937
|
+
return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
|
|
1938
|
+
}
|
|
1939
|
+
async function joinChannel(conversationId, userId) {
|
|
1940
|
+
if (await isParticipant(conversationId, userId)) return;
|
|
1941
|
+
await addParticipant(conversationId, userId);
|
|
1942
|
+
}
|
|
1943
|
+
async function leaveChannel(conversationId, userId) {
|
|
1944
|
+
await removeParticipant(conversationId, userId);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// src/web/api/activity.ts
|
|
1948
|
+
var app = new Hono().get("/events", async (c) => {
|
|
1949
|
+
const user = c.get("user");
|
|
1950
|
+
return streamSSE(c, async (stream) => {
|
|
1951
|
+
const cleanups = [];
|
|
1952
|
+
try {
|
|
1953
|
+
let recentActivity = [];
|
|
1954
|
+
try {
|
|
1955
|
+
const db = await getDb();
|
|
1956
|
+
recentActivity = await db.select().from(activity).orderBy(desc2(activity.created_at)).limit(50);
|
|
1957
|
+
recentActivity = recentActivity.map((row) => ({
|
|
1958
|
+
...row,
|
|
1959
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
1960
|
+
}));
|
|
1961
|
+
} catch (err) {
|
|
1962
|
+
logger_default.error("[activity-sse] failed to fetch recent activity", logger_default.errorData(err));
|
|
1963
|
+
}
|
|
1964
|
+
let conversations2 = [];
|
|
1965
|
+
try {
|
|
1966
|
+
conversations2 = await listConversationsWithParticipants(user.id);
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
logger_default.error("[activity-sse] failed to fetch conversations", logger_default.errorData(err));
|
|
1969
|
+
}
|
|
1970
|
+
const sites = getCachedSites();
|
|
1971
|
+
const recentPages = getCachedRecentPages();
|
|
1972
|
+
await stream.writeSSE({
|
|
1973
|
+
data: JSON.stringify({
|
|
1974
|
+
event: "snapshot",
|
|
1975
|
+
activity: recentActivity,
|
|
1976
|
+
conversations: conversations2,
|
|
1977
|
+
sites,
|
|
1978
|
+
recentPages,
|
|
1979
|
+
activeMinds: getActiveMinds()
|
|
1980
|
+
})
|
|
1981
|
+
});
|
|
1982
|
+
const unsubActivity = subscribe2((event) => {
|
|
1983
|
+
stream.writeSSE({
|
|
1984
|
+
data: JSON.stringify({ event: "activity", ...event })
|
|
1985
|
+
}).catch((err) => {
|
|
1986
|
+
if (!stream.aborted) logger_default.error("[activity-sse] write error:", logger_default.errorData(err));
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1989
|
+
cleanups.push(unsubActivity);
|
|
1990
|
+
for (const conv of conversations2) {
|
|
1991
|
+
const unsubConv = subscribe(conv.id, (event) => {
|
|
1992
|
+
stream.writeSSE({
|
|
1993
|
+
data: JSON.stringify({ event: "conversation", conversationId: conv.id, ...event })
|
|
1994
|
+
}).catch((err) => {
|
|
1995
|
+
if (!stream.aborted) logger_default.error("[activity-sse] write error:", logger_default.errorData(err));
|
|
1996
|
+
});
|
|
1997
|
+
});
|
|
1998
|
+
cleanups.push(unsubConv);
|
|
1999
|
+
}
|
|
2000
|
+
const keepAlive = setInterval(() => {
|
|
2001
|
+
stream.writeSSE({ data: "" }).catch((err) => {
|
|
2002
|
+
if (!stream.aborted) logger_default.error("[activity-sse] ping error:", logger_default.errorData(err));
|
|
2003
|
+
});
|
|
2004
|
+
}, 15e3);
|
|
2005
|
+
cleanups.push(() => clearInterval(keepAlive));
|
|
2006
|
+
await new Promise((resolve23) => {
|
|
2007
|
+
stream.onAbort(() => resolve23());
|
|
2008
|
+
});
|
|
2009
|
+
} finally {
|
|
2010
|
+
for (const cleanup of cleanups) {
|
|
2011
|
+
try {
|
|
2012
|
+
cleanup();
|
|
2013
|
+
} catch {
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
});
|
|
2019
|
+
var activity_default = app;
|
|
2020
|
+
|
|
2021
|
+
// src/web/api/auth.ts
|
|
2022
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2023
|
+
import { Hono as Hono2 } from "hono";
|
|
2024
|
+
import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
|
|
2025
|
+
import { z } from "zod";
|
|
2026
|
+
var credentialsSchema = z.object({
|
|
2027
|
+
username: z.string().min(1),
|
|
2028
|
+
password: z.string().min(1)
|
|
2029
|
+
});
|
|
2030
|
+
var changePasswordSchema = z.object({
|
|
2031
|
+
currentPassword: z.string().min(1),
|
|
2032
|
+
newPassword: z.string().min(1)
|
|
2033
|
+
});
|
|
2034
|
+
var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
2035
|
+
const user = c.get("user");
|
|
2036
|
+
const { currentPassword, newPassword } = c.req.valid("json");
|
|
2037
|
+
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
2038
|
+
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
2039
|
+
return c.json({ ok: true });
|
|
2040
|
+
});
|
|
2041
|
+
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
2042
|
+
const user = c.get("user");
|
|
2043
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
2044
|
+
const minds = readRegistry();
|
|
2045
|
+
for (const mind of minds) {
|
|
2046
|
+
await getOrCreateMindUser(mind.name);
|
|
2047
|
+
}
|
|
2048
|
+
const type = c.req.query("type");
|
|
2049
|
+
if (type === "brain" || type === "mind") {
|
|
2050
|
+
return c.json(await listUsersByType(type));
|
|
2051
|
+
}
|
|
2052
|
+
return c.json(await listUsers());
|
|
2053
|
+
}).get("/users/pending", async (c) => {
|
|
2054
|
+
const user = c.get("user");
|
|
2055
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
2056
|
+
return c.json(await listPendingUsers());
|
|
2057
|
+
}).post("/users/:id/approve", async (c) => {
|
|
2058
|
+
const user = c.get("user");
|
|
2059
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
2060
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
2061
|
+
await approveUser(id);
|
|
2062
|
+
return c.json({ ok: true });
|
|
2063
|
+
});
|
|
2064
|
+
var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
|
|
2065
|
+
const { username, password } = c.req.valid("json");
|
|
2066
|
+
const existing = await getUserByUsername(username);
|
|
2067
|
+
if (existing) {
|
|
2068
|
+
return c.json({ error: "Username already taken" }, 409);
|
|
2069
|
+
}
|
|
2070
|
+
const user = await createUser(username, password);
|
|
2071
|
+
if (user.role === "admin") {
|
|
2072
|
+
const sessionId = await createSession(user.id);
|
|
2073
|
+
setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
|
|
2074
|
+
}
|
|
2075
|
+
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
2076
|
+
}).post("/login", zValidator("json", credentialsSchema), async (c) => {
|
|
2077
|
+
const { username, password } = c.req.valid("json");
|
|
2078
|
+
const user = await verifyUser(username, password);
|
|
2079
|
+
if (!user) {
|
|
2080
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
2081
|
+
}
|
|
2082
|
+
const sessionId = await createSession(user.id);
|
|
2083
|
+
setCookie(c, "volute_session", sessionId, { path: "/", httpOnly: true, sameSite: "Lax" });
|
|
2084
|
+
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
2085
|
+
}).post("/logout", async (c) => {
|
|
2086
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
2087
|
+
if (sessionId) {
|
|
2088
|
+
await deleteSession(sessionId);
|
|
2089
|
+
deleteCookie(c, "volute_session", { path: "/" });
|
|
2090
|
+
}
|
|
2091
|
+
return c.json({ ok: true });
|
|
2092
|
+
}).get("/me", async (c) => {
|
|
2093
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
2094
|
+
if (!sessionId) return c.json({ error: "Not logged in" }, 401);
|
|
2095
|
+
const userId = await getSessionUserId(sessionId);
|
|
2096
|
+
if (userId == null) return c.json({ error: "Not logged in" }, 401);
|
|
2097
|
+
const user = await getUser(userId);
|
|
2098
|
+
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
2099
|
+
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
2100
|
+
}).route("/", admin).route("/", authenticated);
|
|
2101
|
+
var auth_default = app2;
|
|
2102
|
+
|
|
2103
|
+
// src/web/api/channels.ts
|
|
2104
|
+
import { Hono as Hono3 } from "hono";
|
|
2105
|
+
function buildEnv(name) {
|
|
2106
|
+
return { ...loadMergedEnv(name), VOLUTE_MIND: name, VOLUTE_MIND_DIR: mindDir(name) };
|
|
2107
|
+
}
|
|
2108
|
+
var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
|
|
2109
|
+
const name = c.req.param("name");
|
|
2110
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2111
|
+
const { platform, uri, message, images } = await c.req.json();
|
|
2112
|
+
const driver = getChannelDriver(platform);
|
|
2113
|
+
if (!driver) return c.json({ error: `No driver for platform: ${platform}` }, 400);
|
|
2114
|
+
const env = buildEnv(name);
|
|
2115
|
+
try {
|
|
2116
|
+
await driver.send(env, uri, message, images);
|
|
2117
|
+
return c.json({ ok: true });
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
2120
|
+
}
|
|
2121
|
+
}).get("/:name/channels/read", async (c) => {
|
|
2122
|
+
const name = c.req.param("name");
|
|
2123
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2124
|
+
const platform = c.req.query("platform");
|
|
2125
|
+
const uri = c.req.query("uri");
|
|
2126
|
+
const limit = parseInt(c.req.query("limit") ?? "20", 10) || 20;
|
|
2127
|
+
if (!platform || !uri) return c.json({ error: "platform and uri required" }, 400);
|
|
2128
|
+
const driver = getChannelDriver(platform);
|
|
2129
|
+
if (!driver) return c.json({ error: `No driver for platform: ${platform}` }, 400);
|
|
2130
|
+
const env = buildEnv(name);
|
|
2131
|
+
try {
|
|
2132
|
+
const output = await driver.read(env, uri, limit);
|
|
2133
|
+
return c.text(output);
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
2136
|
+
}
|
|
2137
|
+
}).get("/:name/channels/list", async (c) => {
|
|
2138
|
+
const name = c.req.param("name");
|
|
2139
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
1551
2140
|
const platform = c.req.query("platform");
|
|
1552
2141
|
const platforms = platform ? [platform] : Object.keys(CHANNELS);
|
|
1553
2142
|
const env = buildEnv(name);
|
|
@@ -1606,12 +2195,12 @@ var app2 = new Hono2().post("/:name/channels/send", requireAdmin, async (c) => {
|
|
|
1606
2195
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1607
2196
|
}
|
|
1608
2197
|
});
|
|
1609
|
-
var channels_default =
|
|
2198
|
+
var channels_default = app3;
|
|
1610
2199
|
|
|
1611
2200
|
// src/web/api/connectors.ts
|
|
1612
|
-
import { Hono as
|
|
2201
|
+
import { Hono as Hono4 } from "hono";
|
|
1613
2202
|
var CONNECTOR_TYPE_RE = /^[a-z][a-z0-9-]*$/;
|
|
1614
|
-
var
|
|
2203
|
+
var app4 = new Hono4().get("/:name/connectors", (c) => {
|
|
1615
2204
|
const name = c.req.param("name");
|
|
1616
2205
|
const entry = findMind(name);
|
|
1617
2206
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -1685,11 +2274,11 @@ var app3 = new Hono3().get("/:name/connectors", (c) => {
|
|
|
1685
2274
|
writeVoluteConfig(dir, config);
|
|
1686
2275
|
return c.json({ ok: true });
|
|
1687
2276
|
});
|
|
1688
|
-
var connectors_default =
|
|
2277
|
+
var connectors_default = app4;
|
|
1689
2278
|
|
|
1690
2279
|
// src/web/api/env.ts
|
|
1691
|
-
import { Hono as
|
|
1692
|
-
var
|
|
2280
|
+
import { Hono as Hono5 } from "hono";
|
|
2281
|
+
var app5 = new Hono5().get("/:name/env", (c) => {
|
|
1693
2282
|
const name = c.req.param("name");
|
|
1694
2283
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
1695
2284
|
const shared = readEnv(sharedEnvPath());
|
|
@@ -1732,7 +2321,7 @@ var app4 = new Hono4().get("/:name/env", (c) => {
|
|
|
1732
2321
|
writeEnv(path, env);
|
|
1733
2322
|
return c.json({ ok: true });
|
|
1734
2323
|
});
|
|
1735
|
-
var sharedEnvApp = new
|
|
2324
|
+
var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
1736
2325
|
return c.json(readEnv(sharedEnvPath()));
|
|
1737
2326
|
}).put("/:key", requireAdmin, async (c) => {
|
|
1738
2327
|
const key = c.req.param("key");
|
|
@@ -1759,644 +2348,670 @@ var sharedEnvApp = new Hono4().get("/", (c) => {
|
|
|
1759
2348
|
writeEnv(path, env);
|
|
1760
2349
|
return c.json({ ok: true });
|
|
1761
2350
|
});
|
|
1762
|
-
var env_default =
|
|
1763
|
-
|
|
1764
|
-
// src/web/api/files.ts
|
|
1765
|
-
import { existsSync as existsSync6 } from "fs";
|
|
1766
|
-
import { readdir, readFile } from "fs/promises";
|
|
1767
|
-
import { resolve as resolve7 } from "path";
|
|
1768
|
-
import { Hono as Hono5 } from "hono";
|
|
1769
|
-
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
1770
|
-
var app5 = new Hono5().get("/:name/files", async (c) => {
|
|
1771
|
-
const name = c.req.param("name");
|
|
1772
|
-
const entry = findMind(name);
|
|
1773
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1774
|
-
const dir = mindDir(name);
|
|
1775
|
-
const homeDir = resolve7(dir, "home");
|
|
1776
|
-
if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
1777
|
-
const allFiles = await readdir(homeDir);
|
|
1778
|
-
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
1779
|
-
return c.json(files);
|
|
1780
|
-
}).get("/:name/files/:filename", async (c) => {
|
|
1781
|
-
const name = c.req.param("name");
|
|
1782
|
-
const filename = c.req.param("filename");
|
|
1783
|
-
if (!ALLOWED_FILES.has(filename)) {
|
|
1784
|
-
return c.json({ error: "File not allowed" }, 403);
|
|
1785
|
-
}
|
|
1786
|
-
const entry = findMind(name);
|
|
1787
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1788
|
-
const dir = mindDir(name);
|
|
1789
|
-
const filePath = resolve7(dir, "home", filename);
|
|
1790
|
-
if (!existsSync6(filePath)) {
|
|
1791
|
-
return c.json({ error: "File not found" }, 404);
|
|
1792
|
-
}
|
|
1793
|
-
const content = await readFile(filePath, "utf-8");
|
|
1794
|
-
return c.json({ filename, content });
|
|
1795
|
-
});
|
|
1796
|
-
var files_default = app5;
|
|
1797
|
-
|
|
1798
|
-
// src/web/api/keys.ts
|
|
1799
|
-
import { Hono as Hono6 } from "hono";
|
|
1800
|
-
|
|
1801
|
-
// src/lib/identity.ts
|
|
1802
|
-
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
1803
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1804
|
-
import { resolve as resolve8 } from "path";
|
|
1805
|
-
function generateIdentity(mindDir2) {
|
|
1806
|
-
const identityDir = resolve8(mindDir2, ".mind/identity");
|
|
1807
|
-
mkdirSync4(identityDir, { recursive: true });
|
|
1808
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
1809
|
-
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
1810
|
-
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
1811
|
-
});
|
|
1812
|
-
const privatePath = resolve8(identityDir, "private.pem");
|
|
1813
|
-
const publicPath = resolve8(identityDir, "public.pem");
|
|
1814
|
-
writeFileSync4(privatePath, privateKey, { mode: 384 });
|
|
1815
|
-
writeFileSync4(publicPath, publicKey, { mode: 420 });
|
|
1816
|
-
const config = readVoluteConfig(mindDir2) ?? {};
|
|
1817
|
-
config.identity = {
|
|
1818
|
-
privateKey: ".mind/identity/private.pem",
|
|
1819
|
-
publicKey: ".mind/identity/public.pem"
|
|
1820
|
-
};
|
|
1821
|
-
writeVoluteConfig(mindDir2, config);
|
|
1822
|
-
return { publicKeyPem: publicKey, privateKeyPem: privateKey };
|
|
1823
|
-
}
|
|
1824
|
-
function getPrivateKey(mindDir2) {
|
|
1825
|
-
const config = readVoluteConfig(mindDir2);
|
|
1826
|
-
const relPath = config?.identity?.privateKey;
|
|
1827
|
-
if (!relPath) return null;
|
|
1828
|
-
const fullPath = resolve8(mindDir2, relPath);
|
|
1829
|
-
if (!existsSync7(fullPath)) return null;
|
|
1830
|
-
return readFileSync5(fullPath, "utf-8");
|
|
1831
|
-
}
|
|
1832
|
-
function getPublicKey(mindDir2) {
|
|
1833
|
-
const config = readVoluteConfig(mindDir2);
|
|
1834
|
-
const relPath = config?.identity?.publicKey;
|
|
1835
|
-
if (!relPath) return null;
|
|
1836
|
-
const fullPath = resolve8(mindDir2, relPath);
|
|
1837
|
-
if (!existsSync7(fullPath)) return null;
|
|
1838
|
-
return readFileSync5(fullPath, "utf-8");
|
|
1839
|
-
}
|
|
1840
|
-
function getFingerprint(publicKeyPem) {
|
|
1841
|
-
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
1842
|
-
}
|
|
1843
|
-
function signMessage(privateKeyPem, content, timestamp) {
|
|
1844
|
-
const data = `${content}
|
|
1845
|
-
${timestamp}`;
|
|
1846
|
-
const signature = sign(null, Buffer.from(data), privateKeyPem);
|
|
1847
|
-
return signature.toString("base64");
|
|
1848
|
-
}
|
|
1849
|
-
async function publishPublicKey(mindName, publicKeyPem) {
|
|
1850
|
-
const systems = readSystemsConfig();
|
|
1851
|
-
if (!systems) return false;
|
|
1852
|
-
try {
|
|
1853
|
-
const res = await fetch(`${systems.apiUrl}/api/keys/${encodeURIComponent(mindName)}`, {
|
|
1854
|
-
method: "PUT",
|
|
1855
|
-
headers: {
|
|
1856
|
-
"Content-Type": "application/json",
|
|
1857
|
-
Authorization: `Bearer ${systems.apiKey}`
|
|
1858
|
-
},
|
|
1859
|
-
body: JSON.stringify({ publicKey: publicKeyPem })
|
|
1860
|
-
});
|
|
1861
|
-
if (!res.ok) {
|
|
1862
|
-
logger_default.warn(`failed to publish key for ${mindName}: ${res.status}`);
|
|
1863
|
-
return false;
|
|
1864
|
-
}
|
|
1865
|
-
return true;
|
|
1866
|
-
} catch (err) {
|
|
1867
|
-
logger_default.warn(`failed to publish key for ${mindName}`, logger_default.errorData(err));
|
|
1868
|
-
return false;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
// src/web/api/keys.ts
|
|
1873
|
-
var app6 = new Hono6().get("/:fingerprint", (c) => {
|
|
1874
|
-
const fingerprint = c.req.param("fingerprint");
|
|
1875
|
-
for (const entry of readRegistry()) {
|
|
1876
|
-
try {
|
|
1877
|
-
const pubKey = getPublicKey(mindDir(entry.name));
|
|
1878
|
-
if (!pubKey) continue;
|
|
1879
|
-
if (getFingerprint(pubKey) === fingerprint) {
|
|
1880
|
-
return c.json({ publicKey: pubKey, mind: entry.name });
|
|
1881
|
-
}
|
|
1882
|
-
} catch {
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
return c.json({ error: "Key not found" }, 404);
|
|
1886
|
-
});
|
|
1887
|
-
var keys_default = app6;
|
|
2351
|
+
var env_default = app5;
|
|
1888
2352
|
|
|
1889
|
-
// src/web/api/
|
|
1890
|
-
import {
|
|
1891
|
-
import { existsSync as existsSync8 } from "fs";
|
|
2353
|
+
// src/web/api/file-sharing.ts
|
|
2354
|
+
import { readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
1892
2355
|
import { resolve as resolve9 } from "path";
|
|
1893
|
-
import { Hono as
|
|
1894
|
-
import { streamSSE } from "hono/streaming";
|
|
1895
|
-
var app7 = new Hono7().get("/:name/logs", async (c) => {
|
|
1896
|
-
const name = c.req.param("name");
|
|
1897
|
-
const entry = findMind(name);
|
|
1898
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1899
|
-
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
1900
|
-
if (!existsSync8(logFile)) {
|
|
1901
|
-
return c.json({ error: "No log file found" }, 404);
|
|
1902
|
-
}
|
|
1903
|
-
return streamSSE(c, async (stream) => {
|
|
1904
|
-
const tail = spawn2("tail", ["-n", "200", "-f", logFile]);
|
|
1905
|
-
const onData = (data) => {
|
|
1906
|
-
const lines = data.toString().split("\n");
|
|
1907
|
-
for (const line of lines) {
|
|
1908
|
-
if (line) {
|
|
1909
|
-
stream.writeSSE({ data: line }).catch(() => {
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
1914
|
-
tail.stdout.on("data", onData);
|
|
1915
|
-
stream.onAbort(() => {
|
|
1916
|
-
tail.kill();
|
|
1917
|
-
});
|
|
1918
|
-
await new Promise((resolve20) => {
|
|
1919
|
-
tail.on("exit", resolve20);
|
|
1920
|
-
stream.onAbort(resolve20);
|
|
1921
|
-
});
|
|
1922
|
-
});
|
|
1923
|
-
}).get("/:name/logs/tail", async (c) => {
|
|
1924
|
-
const name = c.req.param("name");
|
|
1925
|
-
const entry = findMind(name);
|
|
1926
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1927
|
-
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
1928
|
-
if (!existsSync8(logFile)) {
|
|
1929
|
-
return c.json({ error: "No log file found" }, 404);
|
|
1930
|
-
}
|
|
1931
|
-
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
1932
|
-
const n = Number.isFinite(nParam) && nParam > 0 ? Math.min(nParam, 1e4) : 50;
|
|
1933
|
-
const tail = spawn2("tail", ["-n", String(n), logFile]);
|
|
1934
|
-
let output = "";
|
|
1935
|
-
tail.stdout.on("data", (data) => {
|
|
1936
|
-
output += data.toString();
|
|
1937
|
-
});
|
|
1938
|
-
await new Promise((resolve20) => {
|
|
1939
|
-
tail.on("exit", resolve20);
|
|
1940
|
-
});
|
|
1941
|
-
return c.text(output);
|
|
1942
|
-
});
|
|
1943
|
-
var logs_default = app7;
|
|
1944
|
-
|
|
1945
|
-
// src/web/api/mind-skills.ts
|
|
1946
|
-
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
1947
|
-
import { Hono as Hono8 } from "hono";
|
|
1948
|
-
import { z as z2 } from "zod";
|
|
1949
|
-
var app8 = new Hono8().get("/:name/skills", async (c) => {
|
|
1950
|
-
const name = c.req.param("name");
|
|
1951
|
-
const entry = findMind(name);
|
|
1952
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1953
|
-
const dir = mindDir(name);
|
|
1954
|
-
const skills = await listMindSkills(dir);
|
|
1955
|
-
return c.json(skills);
|
|
1956
|
-
}).post(
|
|
1957
|
-
"/:name/skills/install",
|
|
1958
|
-
requireAdmin,
|
|
1959
|
-
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
1960
|
-
async (c) => {
|
|
1961
|
-
const name = c.req.param("name");
|
|
1962
|
-
const entry = findMind(name);
|
|
1963
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1964
|
-
const { skillId } = c.req.valid("json");
|
|
1965
|
-
const dir = mindDir(name);
|
|
1966
|
-
try {
|
|
1967
|
-
await installSkill(name, dir, skillId);
|
|
1968
|
-
} catch (e) {
|
|
1969
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
1970
|
-
return c.json({ error: msg }, 400);
|
|
1971
|
-
}
|
|
1972
|
-
return c.json({ ok: true });
|
|
1973
|
-
}
|
|
1974
|
-
).post(
|
|
1975
|
-
"/:name/skills/update",
|
|
1976
|
-
requireAdmin,
|
|
1977
|
-
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
1978
|
-
async (c) => {
|
|
1979
|
-
const name = c.req.param("name");
|
|
1980
|
-
const entry = findMind(name);
|
|
1981
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1982
|
-
const { skillId } = c.req.valid("json");
|
|
1983
|
-
const dir = mindDir(name);
|
|
1984
|
-
try {
|
|
1985
|
-
const result = await updateSkill(name, dir, skillId);
|
|
1986
|
-
return c.json(result);
|
|
1987
|
-
} catch (e) {
|
|
1988
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
1989
|
-
return c.json({ error: msg }, 400);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
).post(
|
|
1993
|
-
"/:name/skills/publish",
|
|
1994
|
-
requireAdmin,
|
|
1995
|
-
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
1996
|
-
async (c) => {
|
|
1997
|
-
const name = c.req.param("name");
|
|
1998
|
-
const entry = findMind(name);
|
|
1999
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2000
|
-
const { skillId } = c.req.valid("json");
|
|
2001
|
-
const dir = mindDir(name);
|
|
2002
|
-
try {
|
|
2003
|
-
const skill = await publishSkill(name, dir, skillId);
|
|
2004
|
-
return c.json(skill);
|
|
2005
|
-
} catch (e) {
|
|
2006
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2007
|
-
return c.json({ error: msg }, 400);
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
).delete("/:name/skills/:skill", requireAdmin, async (c) => {
|
|
2011
|
-
const name = c.req.param("name");
|
|
2012
|
-
const skillName = c.req.param("skill");
|
|
2013
|
-
const entry = findMind(name);
|
|
2014
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2015
|
-
const dir = mindDir(name);
|
|
2016
|
-
try {
|
|
2017
|
-
await uninstallSkill(name, dir, skillName);
|
|
2018
|
-
} catch (e) {
|
|
2019
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2020
|
-
return c.json({ error: msg }, 400);
|
|
2021
|
-
}
|
|
2022
|
-
return c.json({ ok: true });
|
|
2023
|
-
});
|
|
2024
|
-
var mind_skills_default = app8;
|
|
2025
|
-
|
|
2026
|
-
// src/web/api/minds.ts
|
|
2027
|
-
import {
|
|
2028
|
-
cpSync as cpSync2,
|
|
2029
|
-
existsSync as existsSync10,
|
|
2030
|
-
mkdirSync as mkdirSync7,
|
|
2031
|
-
readdirSync as readdirSync4,
|
|
2032
|
-
readFileSync as readFileSync9,
|
|
2033
|
-
rmSync as rmSync2,
|
|
2034
|
-
statSync as statSync2,
|
|
2035
|
-
writeFileSync as writeFileSync8
|
|
2036
|
-
} from "fs";
|
|
2037
|
-
import { join as join2, resolve as resolve13 } from "path";
|
|
2038
|
-
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2039
|
-
import { and as and3, desc as desc2, eq as eq4, sql as sql2 } from "drizzle-orm";
|
|
2040
|
-
import { Hono as Hono9 } from "hono";
|
|
2041
|
-
import { z as z3 } from "zod";
|
|
2042
|
-
|
|
2043
|
-
// src/lib/consolidate.ts
|
|
2044
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2045
|
-
import { resolve as resolve10 } from "path";
|
|
2046
|
-
async function consolidateMemory(mindDir2) {
|
|
2047
|
-
const soulPath = resolve10(mindDir2, "home/SOUL.md");
|
|
2048
|
-
const memoryPath = resolve10(mindDir2, "home/MEMORY.md");
|
|
2049
|
-
const memoryDir = resolve10(mindDir2, "home/memory");
|
|
2050
|
-
const soul = readFileSync6(soulPath, "utf-8");
|
|
2051
|
-
const logs = [];
|
|
2052
|
-
try {
|
|
2053
|
-
const files = readdirSync2(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
2054
|
-
for (const filename of files) {
|
|
2055
|
-
const date = filename.replace(".md", "");
|
|
2056
|
-
const content2 = readFileSync6(resolve10(memoryDir, filename), "utf-8").trim();
|
|
2057
|
-
if (content2) {
|
|
2058
|
-
logs.push(`### ${date}
|
|
2356
|
+
import { Hono as Hono6 } from "hono";
|
|
2059
2357
|
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
if (
|
|
2066
|
-
|
|
2067
|
-
|
|
2358
|
+
// src/lib/file-sharing.ts
|
|
2359
|
+
import { randomBytes } from "crypto";
|
|
2360
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
2361
|
+
import { basename, join as join2, normalize, resolve as resolve8 } from "path";
|
|
2362
|
+
function validateFilePath(filePath) {
|
|
2363
|
+
if (!filePath) return "File path is required";
|
|
2364
|
+
const normalized = normalize(filePath);
|
|
2365
|
+
if (normalized.startsWith("/") || normalized.startsWith("\\")) {
|
|
2366
|
+
return "Absolute paths are not allowed";
|
|
2068
2367
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
console.error("ANTHROPIC_API_KEY not set, skipping memory consolidation.");
|
|
2072
|
-
return;
|
|
2368
|
+
if (normalized.includes("..")) {
|
|
2369
|
+
return "Path traversal (..) is not allowed";
|
|
2073
2370
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
"Content-Type": "application/json",
|
|
2088
|
-
"x-api-key": apiKey,
|
|
2089
|
-
"anthropic-version": "2023-06-01"
|
|
2090
|
-
},
|
|
2091
|
-
body: JSON.stringify({
|
|
2092
|
-
model: "claude-sonnet-4-20250514",
|
|
2093
|
-
max_tokens: 4096,
|
|
2094
|
-
system: soul,
|
|
2095
|
-
messages: [{ role: "user", content: userMessage }]
|
|
2096
|
-
})
|
|
2097
|
-
});
|
|
2098
|
-
if (!res.ok) {
|
|
2099
|
-
const body = await res.text();
|
|
2100
|
-
console.error(`Anthropic API error (${res.status}): ${body}`);
|
|
2101
|
-
return;
|
|
2371
|
+
return null;
|
|
2372
|
+
}
|
|
2373
|
+
function configPath(dir) {
|
|
2374
|
+
return resolve8(dir, "home", ".config", "file-sharing.json");
|
|
2375
|
+
}
|
|
2376
|
+
function readFileSharingConfig(dir) {
|
|
2377
|
+
const p = configPath(dir);
|
|
2378
|
+
if (!existsSync7(p)) return {};
|
|
2379
|
+
try {
|
|
2380
|
+
return JSON.parse(readFileSync5(p, "utf-8"));
|
|
2381
|
+
} catch (err) {
|
|
2382
|
+
console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
|
|
2383
|
+
return {};
|
|
2102
2384
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2385
|
+
}
|
|
2386
|
+
function writeFileSharingConfig(dir, config) {
|
|
2387
|
+
const p = configPath(dir);
|
|
2388
|
+
mkdirSync4(resolve8(p, ".."), { recursive: true });
|
|
2389
|
+
writeFileSync4(p, `${JSON.stringify(config, null, 2)}
|
|
2107
2390
|
`);
|
|
2108
|
-
console.log("MEMORY.md created successfully.");
|
|
2109
|
-
} else {
|
|
2110
|
-
console.warn("Warning: No content produced.");
|
|
2111
|
-
}
|
|
2112
2391
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2392
|
+
function isTrustedSender(dir, sender) {
|
|
2393
|
+
const config = readFileSharingConfig(dir);
|
|
2394
|
+
return config.trustedSenders?.includes(sender) ?? false;
|
|
2395
|
+
}
|
|
2396
|
+
function addTrust(dir, sender) {
|
|
2397
|
+
const config = readFileSharingConfig(dir);
|
|
2398
|
+
const trusted = config.trustedSenders ?? [];
|
|
2399
|
+
if (!trusted.includes(sender)) {
|
|
2400
|
+
trusted.push(sender);
|
|
2401
|
+
}
|
|
2402
|
+
config.trustedSenders = trusted;
|
|
2403
|
+
writeFileSharingConfig(dir, config);
|
|
2404
|
+
}
|
|
2405
|
+
function removeTrust(dir, sender) {
|
|
2406
|
+
const config = readFileSharingConfig(dir);
|
|
2407
|
+
const trusted = config.trustedSenders ?? [];
|
|
2408
|
+
config.trustedSenders = trusted.filter((s) => s !== sender);
|
|
2409
|
+
writeFileSharingConfig(dir, config);
|
|
2410
|
+
}
|
|
2411
|
+
function pendingDir(receiver) {
|
|
2412
|
+
return resolve8(stateDir(receiver), "pending-files");
|
|
2413
|
+
}
|
|
2414
|
+
function validateId(id) {
|
|
2415
|
+
if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
2416
|
+
throw new Error("Invalid pending file id");
|
|
2125
2417
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2418
|
+
}
|
|
2419
|
+
function generateId(sender) {
|
|
2420
|
+
const ts = Date.now();
|
|
2421
|
+
const rand = randomBytes(2).toString("hex");
|
|
2422
|
+
return `${sender}-${ts}-${rand}`;
|
|
2423
|
+
}
|
|
2424
|
+
function stageFile(receiver, sender, filename, content, originalPath) {
|
|
2425
|
+
const err = validateFilePath(filename);
|
|
2426
|
+
if (err) throw new Error(err);
|
|
2427
|
+
if (sender.includes("/") || sender.includes("\\")) {
|
|
2428
|
+
throw new Error("Invalid sender name");
|
|
2429
|
+
}
|
|
2430
|
+
const id = generateId(sender);
|
|
2431
|
+
const dir = resolve8(pendingDir(receiver), id);
|
|
2432
|
+
mkdirSync4(dir, { recursive: true });
|
|
2433
|
+
const metadata = {
|
|
2434
|
+
id,
|
|
2435
|
+
sender,
|
|
2436
|
+
filename: basename(filename),
|
|
2437
|
+
originalPath,
|
|
2438
|
+
size: content.length,
|
|
2439
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2130
2440
|
};
|
|
2441
|
+
writeFileSync4(resolve8(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
|
|
2442
|
+
`);
|
|
2443
|
+
writeFileSync4(resolve8(dir, "data"), content);
|
|
2444
|
+
return { id };
|
|
2131
2445
|
}
|
|
2132
|
-
function
|
|
2133
|
-
const
|
|
2134
|
-
if (!
|
|
2135
|
-
|
|
2446
|
+
function listPending(receiver) {
|
|
2447
|
+
const dir = pendingDir(receiver);
|
|
2448
|
+
if (!existsSync7(dir)) return [];
|
|
2449
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
2450
|
+
const result = [];
|
|
2451
|
+
for (const entry of entries) {
|
|
2452
|
+
if (!entry.isDirectory()) continue;
|
|
2453
|
+
const metaPath = resolve8(dir, entry.name, "metadata.json");
|
|
2454
|
+
if (!existsSync7(metaPath)) continue;
|
|
2136
2455
|
try {
|
|
2137
|
-
|
|
2456
|
+
result.push(JSON.parse(readFileSync5(metaPath, "utf-8")));
|
|
2138
2457
|
} catch (err) {
|
|
2139
|
-
console.
|
|
2140
|
-
set.delete(cb);
|
|
2141
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
2458
|
+
console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
|
|
2142
2459
|
}
|
|
2143
2460
|
}
|
|
2461
|
+
return result.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2462
|
+
}
|
|
2463
|
+
function getPending(receiver, id) {
|
|
2464
|
+
validateId(id);
|
|
2465
|
+
const metaPath = resolve8(pendingDir(receiver), id, "metadata.json");
|
|
2466
|
+
if (!existsSync7(metaPath)) return null;
|
|
2467
|
+
try {
|
|
2468
|
+
return JSON.parse(readFileSync5(metaPath, "utf-8"));
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function deliverFile(receiverDir, sender, filename, content, inboxPath) {
|
|
2475
|
+
const err = validateFilePath(filename);
|
|
2476
|
+
if (err) throw new Error(err);
|
|
2477
|
+
const inbox = inboxPath ?? "inbox";
|
|
2478
|
+
const inboxErr = validateFilePath(inbox);
|
|
2479
|
+
if (inboxErr) throw new Error(`Invalid inboxPath: ${inboxErr}`);
|
|
2480
|
+
if (sender.includes("/") || sender.includes("\\")) {
|
|
2481
|
+
throw new Error("Invalid sender name");
|
|
2482
|
+
}
|
|
2483
|
+
const destDir = resolve8(receiverDir, "home", inbox, sender);
|
|
2484
|
+
mkdirSync4(destDir, { recursive: true });
|
|
2485
|
+
const destPath = resolve8(destDir, basename(filename));
|
|
2486
|
+
writeFileSync4(destPath, content);
|
|
2487
|
+
return join2(inbox, sender, basename(filename));
|
|
2488
|
+
}
|
|
2489
|
+
function acceptPending(receiver, id, receiverDir) {
|
|
2490
|
+
const meta = getPending(receiver, id);
|
|
2491
|
+
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2492
|
+
const dataPath = resolve8(pendingDir(receiver), id, "data");
|
|
2493
|
+
const content = readFileSync5(dataPath);
|
|
2494
|
+
const config = readFileSharingConfig(receiverDir);
|
|
2495
|
+
const inboxPath = config.inboxPath ?? "inbox";
|
|
2496
|
+
const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
|
|
2497
|
+
rmSync(resolve8(pendingDir(receiver), id), { recursive: true });
|
|
2498
|
+
return { sender: meta.sender, filename: meta.filename, destPath };
|
|
2499
|
+
}
|
|
2500
|
+
function rejectPending(receiver, id) {
|
|
2501
|
+
const meta = getPending(receiver, id);
|
|
2502
|
+
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2503
|
+
rmSync(resolve8(pendingDir(receiver), id), { recursive: true });
|
|
2504
|
+
return { sender: meta.sender, filename: meta.filename };
|
|
2505
|
+
}
|
|
2506
|
+
function formatFileSize(bytes) {
|
|
2507
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2508
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2509
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2144
2510
|
}
|
|
2145
2511
|
|
|
2146
|
-
// src/
|
|
2147
|
-
async function
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
type,
|
|
2158
|
-
name,
|
|
2159
|
-
user_id: opts?.userId ?? null,
|
|
2160
|
-
title: opts?.title ?? null
|
|
2512
|
+
// src/web/api/file-sharing.ts
|
|
2513
|
+
async function notifyMind(port, message, channel, sender) {
|
|
2514
|
+
try {
|
|
2515
|
+
const res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
2516
|
+
method: "POST",
|
|
2517
|
+
headers: { "Content-Type": "application/json" },
|
|
2518
|
+
body: JSON.stringify({
|
|
2519
|
+
content: [{ type: "text", text: message }],
|
|
2520
|
+
channel,
|
|
2521
|
+
sender
|
|
2522
|
+
})
|
|
2161
2523
|
});
|
|
2162
|
-
if (
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2524
|
+
if (!res.ok) {
|
|
2525
|
+
console.warn(`[file-sharing] notify mind on port ${port} failed: ${res.status}`);
|
|
2526
|
+
}
|
|
2527
|
+
} catch (err) {
|
|
2528
|
+
console.warn(`[file-sharing] notify mind on port ${port} failed:`, err);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
2532
|
+
const senderName = c.req.param("name");
|
|
2533
|
+
const senderEntry = findMind(senderName);
|
|
2534
|
+
if (!senderEntry) return c.json({ error: "Sender mind not found" }, 404);
|
|
2535
|
+
const body = await c.req.json();
|
|
2536
|
+
if (!body.targetMind || !body.filePath) {
|
|
2537
|
+
return c.json({ error: "targetMind and filePath are required" }, 400);
|
|
2538
|
+
}
|
|
2539
|
+
const receiverEntry = findMind(body.targetMind);
|
|
2540
|
+
if (!receiverEntry) return c.json({ error: "Target mind not found" }, 404);
|
|
2541
|
+
const pathErr = validateFilePath(body.filePath);
|
|
2542
|
+
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2543
|
+
const senderDir = mindDir(senderName);
|
|
2544
|
+
const filePath = resolve9(senderDir, "home", body.filePath);
|
|
2545
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
2546
|
+
const stat3 = statSync2(filePath, { throwIfNoEntry: false });
|
|
2547
|
+
if (!stat3) return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2548
|
+
if (stat3.size > MAX_FILE_SIZE) {
|
|
2549
|
+
return c.json(
|
|
2550
|
+
{
|
|
2551
|
+
error: `File too large (${formatFileSize(stat3.size)}, max ${formatFileSize(MAX_FILE_SIZE)})`
|
|
2552
|
+
},
|
|
2553
|
+
413
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
let content;
|
|
2557
|
+
try {
|
|
2558
|
+
content = readFileSync6(filePath);
|
|
2559
|
+
} catch {
|
|
2560
|
+
return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2561
|
+
}
|
|
2562
|
+
const receiverDir = mindDir(body.targetMind);
|
|
2563
|
+
const filename = body.filePath;
|
|
2564
|
+
const sizeStr = formatFileSize(content.length);
|
|
2565
|
+
if (isTrustedSender(receiverDir, senderName)) {
|
|
2566
|
+
const config = readFileSharingConfig(receiverDir);
|
|
2567
|
+
const destPath = deliverFile(receiverDir, senderName, filename, content, config.inboxPath);
|
|
2568
|
+
if (receiverEntry.running) {
|
|
2569
|
+
await notifyMind(
|
|
2570
|
+
receiverEntry.port,
|
|
2571
|
+
`[file] ${senderName} sent ${filename} (${sizeStr}) \u2192 ${destPath}`,
|
|
2572
|
+
"system:file-sharing",
|
|
2573
|
+
senderName
|
|
2169
2574
|
);
|
|
2170
2575
|
}
|
|
2576
|
+
return c.json({ status: "delivered", destPath }, 200);
|
|
2577
|
+
}
|
|
2578
|
+
const { id } = stageFile(body.targetMind, senderName, filename, content, body.filePath);
|
|
2579
|
+
if (receiverEntry.running) {
|
|
2580
|
+
await notifyMind(
|
|
2581
|
+
receiverEntry.port,
|
|
2582
|
+
`[file] ${senderName} wants to send ${filename} (${sizeStr}) \u2014 run: volute file accept ${id}`,
|
|
2583
|
+
"system:file-sharing",
|
|
2584
|
+
senderName
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
return c.json({ status: "pending", id }, 200);
|
|
2588
|
+
}).get("/:name/files/pending", (c) => {
|
|
2589
|
+
const name = c.req.param("name");
|
|
2590
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2591
|
+
return c.json(listPending(name));
|
|
2592
|
+
}).post("/:name/files/accept", async (c) => {
|
|
2593
|
+
const name = c.req.param("name");
|
|
2594
|
+
const entry = findMind(name);
|
|
2595
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2596
|
+
const body = await c.req.json();
|
|
2597
|
+
if (!body.id) return c.json({ error: "id is required" }, 400);
|
|
2598
|
+
let result;
|
|
2599
|
+
try {
|
|
2600
|
+
result = acceptPending(name, body.id, mindDir(name));
|
|
2601
|
+
} catch (err) {
|
|
2602
|
+
const message = err.message;
|
|
2603
|
+
if (message.includes("not found") || message.includes("Invalid pending")) {
|
|
2604
|
+
return c.json({ error: message }, 404);
|
|
2605
|
+
}
|
|
2606
|
+
return c.json({ error: `Failed to accept file: ${message}` }, 500);
|
|
2607
|
+
}
|
|
2608
|
+
const senderEntry = findMind(result.sender);
|
|
2609
|
+
if (senderEntry?.running) {
|
|
2610
|
+
await notifyMind(
|
|
2611
|
+
senderEntry.port,
|
|
2612
|
+
`[file] ${name} accepted ${result.filename}`,
|
|
2613
|
+
"system:file-sharing",
|
|
2614
|
+
name
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
return c.json({ ok: true, destPath: result.destPath });
|
|
2618
|
+
}).post("/:name/files/reject", async (c) => {
|
|
2619
|
+
const name = c.req.param("name");
|
|
2620
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2621
|
+
const body = await c.req.json();
|
|
2622
|
+
if (!body.id) return c.json({ error: "id is required" }, 400);
|
|
2623
|
+
let result;
|
|
2624
|
+
try {
|
|
2625
|
+
result = rejectPending(name, body.id);
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
const message = err.message;
|
|
2628
|
+
if (message.includes("not found") || message.includes("Invalid pending")) {
|
|
2629
|
+
return c.json({ error: message }, 404);
|
|
2630
|
+
}
|
|
2631
|
+
return c.json({ error: `Failed to reject file: ${message}` }, 500);
|
|
2632
|
+
}
|
|
2633
|
+
const senderEntry = findMind(result.sender);
|
|
2634
|
+
if (senderEntry?.running) {
|
|
2635
|
+
await notifyMind(
|
|
2636
|
+
senderEntry.port,
|
|
2637
|
+
`[file] ${name} rejected ${result.filename}`,
|
|
2638
|
+
"system:file-sharing",
|
|
2639
|
+
name
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
return c.json({ ok: true });
|
|
2643
|
+
}).post("/:name/files/trust", async (c) => {
|
|
2644
|
+
const name = c.req.param("name");
|
|
2645
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2646
|
+
const body = await c.req.json();
|
|
2647
|
+
if (!body.sender) return c.json({ error: "sender is required" }, 400);
|
|
2648
|
+
addTrust(mindDir(name), body.sender);
|
|
2649
|
+
return c.json({ ok: true });
|
|
2650
|
+
}).delete("/:name/files/trust/:sender", (c) => {
|
|
2651
|
+
const name = c.req.param("name");
|
|
2652
|
+
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2653
|
+
const sender = c.req.param("sender");
|
|
2654
|
+
removeTrust(mindDir(name), sender);
|
|
2655
|
+
return c.json({ ok: true });
|
|
2656
|
+
});
|
|
2657
|
+
var file_sharing_default = app6;
|
|
2658
|
+
|
|
2659
|
+
// src/web/api/files.ts
|
|
2660
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2661
|
+
import { readdir, readFile } from "fs/promises";
|
|
2662
|
+
import { resolve as resolve10 } from "path";
|
|
2663
|
+
import { Hono as Hono7 } from "hono";
|
|
2664
|
+
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2665
|
+
var app7 = new Hono7().get("/:name/files", async (c) => {
|
|
2666
|
+
const name = c.req.param("name");
|
|
2667
|
+
const entry = findMind(name);
|
|
2668
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2669
|
+
const dir = mindDir(name);
|
|
2670
|
+
const homeDir = resolve10(dir, "home");
|
|
2671
|
+
if (!existsSync8(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2672
|
+
const allFiles = await readdir(homeDir);
|
|
2673
|
+
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2674
|
+
return c.json(files);
|
|
2675
|
+
}).get("/:name/files/:filename", async (c) => {
|
|
2676
|
+
const name = c.req.param("name");
|
|
2677
|
+
const filename = c.req.param("filename");
|
|
2678
|
+
if (!ALLOWED_FILES.has(filename)) {
|
|
2679
|
+
return c.json({ error: "File not allowed" }, 403);
|
|
2680
|
+
}
|
|
2681
|
+
const entry = findMind(name);
|
|
2682
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2683
|
+
const dir = mindDir(name);
|
|
2684
|
+
const filePath = resolve10(dir, "home", filename);
|
|
2685
|
+
if (!existsSync8(filePath)) {
|
|
2686
|
+
return c.json({ error: "File not found" }, 404);
|
|
2687
|
+
}
|
|
2688
|
+
const content = await readFile(filePath, "utf-8");
|
|
2689
|
+
return c.json({ filename, content });
|
|
2690
|
+
});
|
|
2691
|
+
var files_default = app7;
|
|
2692
|
+
|
|
2693
|
+
// src/web/api/keys.ts
|
|
2694
|
+
import { Hono as Hono8 } from "hono";
|
|
2695
|
+
|
|
2696
|
+
// src/lib/identity.ts
|
|
2697
|
+
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
2698
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2699
|
+
import { resolve as resolve11 } from "path";
|
|
2700
|
+
function generateIdentity(mindDir2) {
|
|
2701
|
+
const identityDir = resolve11(mindDir2, ".mind/identity");
|
|
2702
|
+
mkdirSync5(identityDir, { recursive: true });
|
|
2703
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
2704
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2705
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2171
2706
|
});
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2181
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2707
|
+
const privatePath = resolve11(identityDir, "private.pem");
|
|
2708
|
+
const publicPath = resolve11(identityDir, "public.pem");
|
|
2709
|
+
writeFileSync5(privatePath, privateKey, { mode: 384 });
|
|
2710
|
+
writeFileSync5(publicPath, publicKey, { mode: 420 });
|
|
2711
|
+
const config = readVoluteConfig(mindDir2) ?? {};
|
|
2712
|
+
config.identity = {
|
|
2713
|
+
privateKey: ".mind/identity/private.pem",
|
|
2714
|
+
publicKey: ".mind/identity/public.pem"
|
|
2182
2715
|
};
|
|
2716
|
+
writeVoluteConfig(mindDir2, config);
|
|
2717
|
+
return { publicKeyPem: publicKey, privateKeyPem: privateKey };
|
|
2183
2718
|
}
|
|
2184
|
-
|
|
2185
|
-
const
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
await db.insert(conversationParticipants).values({
|
|
2192
|
-
conversation_id: conversationId,
|
|
2193
|
-
user_id: userId,
|
|
2194
|
-
role
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
async function removeParticipant(conversationId, userId) {
|
|
2198
|
-
const db = await getDb();
|
|
2199
|
-
await db.delete(conversationParticipants).where(
|
|
2200
|
-
and2(
|
|
2201
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
2202
|
-
eq3(conversationParticipants.user_id, userId)
|
|
2203
|
-
)
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
async function getParticipants(conversationId) {
|
|
2207
|
-
const db = await getDb();
|
|
2208
|
-
const rows = await db.select({
|
|
2209
|
-
userId: conversationParticipants.user_id,
|
|
2210
|
-
username: users.username,
|
|
2211
|
-
userType: users.user_type,
|
|
2212
|
-
role: conversationParticipants.role
|
|
2213
|
-
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
|
|
2214
|
-
return rows;
|
|
2215
|
-
}
|
|
2216
|
-
async function isParticipant(conversationId, userId) {
|
|
2217
|
-
const db = await getDb();
|
|
2218
|
-
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
2219
|
-
and2(
|
|
2220
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
2221
|
-
eq3(conversationParticipants.user_id, userId)
|
|
2222
|
-
)
|
|
2223
|
-
).get();
|
|
2224
|
-
return row != null;
|
|
2719
|
+
function getPrivateKey(mindDir2) {
|
|
2720
|
+
const config = readVoluteConfig(mindDir2);
|
|
2721
|
+
const relPath = config?.identity?.privateKey;
|
|
2722
|
+
if (!relPath) return null;
|
|
2723
|
+
const fullPath = resolve11(mindDir2, relPath);
|
|
2724
|
+
if (!existsSync9(fullPath)) return null;
|
|
2725
|
+
return readFileSync7(fullPath, "utf-8");
|
|
2225
2726
|
}
|
|
2226
|
-
|
|
2227
|
-
const
|
|
2228
|
-
const
|
|
2229
|
-
if (
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2727
|
+
function getPublicKey(mindDir2) {
|
|
2728
|
+
const config = readVoluteConfig(mindDir2);
|
|
2729
|
+
const relPath = config?.identity?.publicKey;
|
|
2730
|
+
if (!relPath) return null;
|
|
2731
|
+
const fullPath = resolve11(mindDir2, relPath);
|
|
2732
|
+
if (!existsSync9(fullPath)) return null;
|
|
2733
|
+
return readFileSync7(fullPath, "utf-8");
|
|
2232
2734
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
const db = await getDb();
|
|
2236
|
-
const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
|
|
2237
|
-
return row != null;
|
|
2735
|
+
function getFingerprint(publicKeyPem) {
|
|
2736
|
+
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
2238
2737
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2738
|
+
function signMessage(privateKeyPem, content, timestamp) {
|
|
2739
|
+
const data = `${content}
|
|
2740
|
+
${timestamp}`;
|
|
2741
|
+
const signature = sign(null, Buffer.from(data), privateKeyPem);
|
|
2742
|
+
return signature.toString("base64");
|
|
2243
2743
|
}
|
|
2244
|
-
async function
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2744
|
+
async function publishPublicKey(mindName, publicKeyPem) {
|
|
2745
|
+
const systems = readSystemsConfig();
|
|
2746
|
+
if (!systems) return false;
|
|
2747
|
+
try {
|
|
2748
|
+
const res = await fetch(`${systems.apiUrl}/api/keys/${encodeURIComponent(mindName)}`, {
|
|
2749
|
+
method: "PUT",
|
|
2750
|
+
headers: {
|
|
2751
|
+
"Content-Type": "application/json",
|
|
2752
|
+
Authorization: `Bearer ${systems.apiKey}`
|
|
2753
|
+
},
|
|
2754
|
+
body: JSON.stringify({ publicKey: publicKeyPem })
|
|
2755
|
+
});
|
|
2756
|
+
if (!res.ok) {
|
|
2757
|
+
logger_default.warn(`failed to publish key for ${mindName}: ${res.status}`);
|
|
2758
|
+
return false;
|
|
2254
2759
|
}
|
|
2760
|
+
return true;
|
|
2761
|
+
} catch (err) {
|
|
2762
|
+
logger_default.warn(`failed to publish key for ${mindName}`, logger_default.errorData(err));
|
|
2763
|
+
return false;
|
|
2255
2764
|
}
|
|
2256
|
-
const msg = {
|
|
2257
|
-
id: result.id,
|
|
2258
|
-
conversation_id: conversationId,
|
|
2259
|
-
role,
|
|
2260
|
-
sender_name: senderName,
|
|
2261
|
-
content,
|
|
2262
|
-
created_at: result.created_at
|
|
2263
|
-
};
|
|
2264
|
-
publish(conversationId, {
|
|
2265
|
-
type: "message",
|
|
2266
|
-
id: msg.id,
|
|
2267
|
-
role: msg.role,
|
|
2268
|
-
senderName: msg.sender_name,
|
|
2269
|
-
content: msg.content,
|
|
2270
|
-
createdAt: msg.created_at
|
|
2271
|
-
});
|
|
2272
|
-
return msg;
|
|
2273
2765
|
}
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2766
|
+
|
|
2767
|
+
// src/web/api/keys.ts
|
|
2768
|
+
var app8 = new Hono8().get("/:fingerprint", (c) => {
|
|
2769
|
+
const fingerprint = c.req.param("fingerprint");
|
|
2770
|
+
for (const entry of readRegistry()) {
|
|
2279
2771
|
try {
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2772
|
+
const pubKey = getPublicKey(mindDir(entry.name));
|
|
2773
|
+
if (!pubKey) continue;
|
|
2774
|
+
if (getFingerprint(pubKey) === fingerprint) {
|
|
2775
|
+
return c.json({ publicKey: pubKey, mind: entry.name });
|
|
2776
|
+
}
|
|
2282
2777
|
} catch {
|
|
2283
|
-
content = [{ type: "text", text: row.content }];
|
|
2284
2778
|
}
|
|
2285
|
-
|
|
2779
|
+
}
|
|
2780
|
+
return c.json({ error: "Key not found" }, 404);
|
|
2781
|
+
});
|
|
2782
|
+
var keys_default = app8;
|
|
2783
|
+
|
|
2784
|
+
// src/web/api/logs.ts
|
|
2785
|
+
import { spawn as spawn2 } from "child_process";
|
|
2786
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2787
|
+
import { resolve as resolve12 } from "path";
|
|
2788
|
+
import { Hono as Hono9 } from "hono";
|
|
2789
|
+
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
2790
|
+
var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
2791
|
+
const name = c.req.param("name");
|
|
2792
|
+
const entry = findMind(name);
|
|
2793
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2794
|
+
const logFile = resolve12(stateDir(name), "logs", "mind.log");
|
|
2795
|
+
if (!existsSync10(logFile)) {
|
|
2796
|
+
return c.json({ error: "No log file found" }, 404);
|
|
2797
|
+
}
|
|
2798
|
+
return streamSSE2(c, async (stream) => {
|
|
2799
|
+
const tail = spawn2("tail", ["-n", "200", "-f", logFile]);
|
|
2800
|
+
const onData = (data) => {
|
|
2801
|
+
const lines = data.toString().split("\n");
|
|
2802
|
+
for (const line of lines) {
|
|
2803
|
+
if (line) {
|
|
2804
|
+
stream.writeSSE({ data: line }).catch(() => {
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
tail.stdout.on("data", onData);
|
|
2810
|
+
stream.onAbort(() => {
|
|
2811
|
+
tail.kill();
|
|
2812
|
+
});
|
|
2813
|
+
await new Promise((resolve23) => {
|
|
2814
|
+
tail.on("exit", resolve23);
|
|
2815
|
+
stream.onAbort(resolve23);
|
|
2816
|
+
});
|
|
2286
2817
|
});
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
const
|
|
2290
|
-
if (
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2818
|
+
}).get("/:name/logs/tail", async (c) => {
|
|
2819
|
+
const name = c.req.param("name");
|
|
2820
|
+
const entry = findMind(name);
|
|
2821
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2822
|
+
const logFile = resolve12(stateDir(name), "logs", "mind.log");
|
|
2823
|
+
if (!existsSync10(logFile)) {
|
|
2824
|
+
return c.json({ error: "No log file found" }, 404);
|
|
2825
|
+
}
|
|
2826
|
+
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
2827
|
+
const n = Number.isFinite(nParam) && nParam > 0 ? Math.min(nParam, 1e4) : 50;
|
|
2828
|
+
const tail = spawn2("tail", ["-n", String(n), logFile]);
|
|
2829
|
+
let output = "";
|
|
2830
|
+
tail.stdout.on("data", (data) => {
|
|
2831
|
+
output += data.toString();
|
|
2832
|
+
});
|
|
2833
|
+
await new Promise((resolve23) => {
|
|
2834
|
+
tail.on("exit", resolve23);
|
|
2835
|
+
});
|
|
2836
|
+
return c.text(output);
|
|
2837
|
+
});
|
|
2838
|
+
var logs_default = app9;
|
|
2839
|
+
|
|
2840
|
+
// src/web/api/mind-skills.ts
|
|
2841
|
+
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
2842
|
+
import { Hono as Hono10 } from "hono";
|
|
2843
|
+
import { z as z2 } from "zod";
|
|
2844
|
+
var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
2845
|
+
const name = c.req.param("name");
|
|
2846
|
+
const entry = findMind(name);
|
|
2847
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2848
|
+
const dir = mindDir(name);
|
|
2849
|
+
const skills = await listMindSkills(dir);
|
|
2850
|
+
return c.json(skills);
|
|
2851
|
+
}).post(
|
|
2852
|
+
"/:name/skills/install",
|
|
2853
|
+
requireAdmin,
|
|
2854
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2855
|
+
async (c) => {
|
|
2856
|
+
const name = c.req.param("name");
|
|
2857
|
+
const entry = findMind(name);
|
|
2858
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2859
|
+
const { skillId } = c.req.valid("json");
|
|
2860
|
+
const dir = mindDir(name);
|
|
2861
|
+
try {
|
|
2862
|
+
await installSkill(name, dir, skillId);
|
|
2863
|
+
} catch (e) {
|
|
2864
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2865
|
+
return c.json({ error: msg }, 400);
|
|
2306
2866
|
}
|
|
2307
|
-
|
|
2308
|
-
userId: r.userId,
|
|
2309
|
-
username: r.username,
|
|
2310
|
-
userType: r.userType,
|
|
2311
|
-
role: r.role
|
|
2312
|
-
});
|
|
2867
|
+
return c.json({ ok: true });
|
|
2313
2868
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
const
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2869
|
+
).post(
|
|
2870
|
+
"/:name/skills/update",
|
|
2871
|
+
requireAdmin,
|
|
2872
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2873
|
+
async (c) => {
|
|
2874
|
+
const name = c.req.param("name");
|
|
2875
|
+
const entry = findMind(name);
|
|
2876
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2877
|
+
const { skillId } = c.req.valid("json");
|
|
2878
|
+
const dir = mindDir(name);
|
|
2879
|
+
try {
|
|
2880
|
+
const result = await updateSkill(name, dir, skillId);
|
|
2881
|
+
return c.json(result);
|
|
2882
|
+
} catch (e) {
|
|
2883
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2884
|
+
return c.json({ error: msg }, 400);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
).post(
|
|
2888
|
+
"/:name/skills/publish",
|
|
2889
|
+
requireAdmin,
|
|
2890
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2891
|
+
async (c) => {
|
|
2892
|
+
const name = c.req.param("name");
|
|
2893
|
+
const entry = findMind(name);
|
|
2894
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2895
|
+
const { skillId } = c.req.valid("json");
|
|
2896
|
+
const dir = mindDir(name);
|
|
2897
|
+
try {
|
|
2898
|
+
const skill = await publishSkill(name, dir, skillId);
|
|
2899
|
+
return c.json(skill);
|
|
2900
|
+
} catch (e) {
|
|
2901
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2902
|
+
return c.json({ error: msg }, 400);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
).delete("/:name/skills/:skill", requireAdmin, async (c) => {
|
|
2906
|
+
const name = c.req.param("name");
|
|
2907
|
+
const skillName = c.req.param("skill");
|
|
2908
|
+
const entry = findMind(name);
|
|
2909
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2910
|
+
const dir = mindDir(name);
|
|
2911
|
+
try {
|
|
2912
|
+
await uninstallSkill(name, dir, skillName);
|
|
2913
|
+
} catch (e) {
|
|
2914
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2915
|
+
return c.json({ error: msg }, 400);
|
|
2916
|
+
}
|
|
2917
|
+
return c.json({ ok: true });
|
|
2918
|
+
});
|
|
2919
|
+
var mind_skills_default = app10;
|
|
2920
|
+
|
|
2921
|
+
// src/web/api/minds.ts
|
|
2922
|
+
import {
|
|
2923
|
+
cpSync as cpSync2,
|
|
2924
|
+
existsSync as existsSync12,
|
|
2925
|
+
mkdirSync as mkdirSync8,
|
|
2926
|
+
readdirSync as readdirSync6,
|
|
2927
|
+
readFileSync as readFileSync11,
|
|
2928
|
+
rmSync as rmSync3,
|
|
2929
|
+
writeFileSync as writeFileSync9
|
|
2930
|
+
} from "fs";
|
|
2931
|
+
import { resolve as resolve16 } from "path";
|
|
2932
|
+
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2933
|
+
import { and as and3, desc as desc3, eq as eq4, sql as sql2 } from "drizzle-orm";
|
|
2934
|
+
import { Hono as Hono11 } from "hono";
|
|
2935
|
+
import { z as z3 } from "zod";
|
|
2936
|
+
|
|
2937
|
+
// src/lib/consolidate.ts
|
|
2938
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
2939
|
+
import { resolve as resolve13 } from "path";
|
|
2940
|
+
async function consolidateMemory(mindDir2) {
|
|
2941
|
+
const soulPath = resolve13(mindDir2, "home/SOUL.md");
|
|
2942
|
+
const memoryPath = resolve13(mindDir2, "home/MEMORY.md");
|
|
2943
|
+
const memoryDir = resolve13(mindDir2, "home/memory");
|
|
2944
|
+
const soul = readFileSync8(soulPath, "utf-8");
|
|
2945
|
+
const logs = [];
|
|
2946
|
+
try {
|
|
2947
|
+
const files = readdirSync4(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
2948
|
+
for (const filename of files) {
|
|
2949
|
+
const date = filename.replace(".md", "");
|
|
2950
|
+
const content2 = readFileSync8(resolve13(memoryDir, filename), "utf-8").trim();
|
|
2951
|
+
if (content2) {
|
|
2952
|
+
logs.push(`### ${date}
|
|
2953
|
+
|
|
2954
|
+
${content2}`);
|
|
2335
2955
|
}
|
|
2336
|
-
byLastMsg.set(m.conversation_id, {
|
|
2337
|
-
role: m.role,
|
|
2338
|
-
senderName: m.sender_name,
|
|
2339
|
-
text,
|
|
2340
|
-
createdAt: m.created_at
|
|
2341
|
-
});
|
|
2342
2956
|
}
|
|
2957
|
+
} catch {
|
|
2343
2958
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
lastMessage: byLastMsg.get(c.id)
|
|
2348
|
-
}));
|
|
2349
|
-
}
|
|
2350
|
-
async function findDMConversation(mindName, participantIds) {
|
|
2351
|
-
const db = await getDb();
|
|
2352
|
-
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
|
|
2353
|
-
for (const conv of mindConvs) {
|
|
2354
|
-
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
|
|
2355
|
-
if (rows.length !== 2) continue;
|
|
2356
|
-
const ids = new Set(rows.map((r) => r.user_id));
|
|
2357
|
-
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
2358
|
-
return conv.id;
|
|
2359
|
-
}
|
|
2959
|
+
if (logs.length === 0) {
|
|
2960
|
+
console.log("No daily logs found.");
|
|
2961
|
+
return;
|
|
2360
2962
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2963
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
2964
|
+
if (!apiKey) {
|
|
2965
|
+
console.error("ANTHROPIC_API_KEY not set, skipping memory consolidation.");
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
console.log("Consolidating memory from daily logs...");
|
|
2969
|
+
const userMessage = [
|
|
2970
|
+
"You have daily logs from a previous environment but no long-term memory file yet.",
|
|
2971
|
+
"Please review the daily logs below and produce consolidated MEMORY.md content.",
|
|
2972
|
+
"Keep it concise and organized by topic. Output ONLY the markdown content for MEMORY.md, nothing else.",
|
|
2973
|
+
"",
|
|
2974
|
+
"## Daily logs",
|
|
2975
|
+
"",
|
|
2976
|
+
logs.join("\n\n")
|
|
2977
|
+
].join("\n");
|
|
2978
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2979
|
+
method: "POST",
|
|
2980
|
+
headers: {
|
|
2981
|
+
"Content-Type": "application/json",
|
|
2982
|
+
"x-api-key": apiKey,
|
|
2983
|
+
"anthropic-version": "2023-06-01"
|
|
2984
|
+
},
|
|
2985
|
+
body: JSON.stringify({
|
|
2986
|
+
model: "claude-sonnet-4-20250514",
|
|
2987
|
+
max_tokens: 4096,
|
|
2988
|
+
system: soul,
|
|
2989
|
+
messages: [{ role: "user", content: userMessage }]
|
|
2990
|
+
})
|
|
2374
2991
|
});
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
}
|
|
2389
|
-
async function leaveChannel(conversationId, userId) {
|
|
2390
|
-
await removeParticipant(conversationId, userId);
|
|
2992
|
+
if (!res.ok) {
|
|
2993
|
+
const body = await res.text();
|
|
2994
|
+
console.error(`Anthropic API error (${res.status}): ${body}`);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
const data = await res.json();
|
|
2998
|
+
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
2999
|
+
if (content) {
|
|
3000
|
+
writeFileSync6(memoryPath, `${content}
|
|
3001
|
+
`);
|
|
3002
|
+
console.log("MEMORY.md created successfully.");
|
|
3003
|
+
} else {
|
|
3004
|
+
console.warn("Warning: No content produced.");
|
|
3005
|
+
}
|
|
2391
3006
|
}
|
|
2392
3007
|
|
|
2393
3008
|
// src/lib/convert-session.ts
|
|
2394
3009
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2395
|
-
import { mkdirSync as
|
|
3010
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
2396
3011
|
import { homedir } from "os";
|
|
2397
|
-
import { resolve as
|
|
3012
|
+
import { resolve as resolve14 } from "path";
|
|
2398
3013
|
function convertSession(opts) {
|
|
2399
|
-
const lines =
|
|
3014
|
+
const lines = readFileSync9(opts.sessionPath, "utf-8").trim().split("\n");
|
|
2400
3015
|
const sessionId = randomUUID2();
|
|
2401
3016
|
const idMap = /* @__PURE__ */ new Map();
|
|
2402
3017
|
const messages2 = [];
|
|
@@ -2510,10 +3125,10 @@ function convertSession(opts) {
|
|
|
2510
3125
|
}
|
|
2511
3126
|
}
|
|
2512
3127
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
2513
|
-
const sdkDir =
|
|
2514
|
-
|
|
2515
|
-
const sdkPath =
|
|
2516
|
-
|
|
3128
|
+
const sdkDir = resolve14(homedir(), ".claude", "projects", projectId);
|
|
3129
|
+
mkdirSync6(sdkDir, { recursive: true });
|
|
3130
|
+
const sdkPath = resolve14(sdkDir, `${sessionId}.jsonl`);
|
|
3131
|
+
writeFileSync7(sdkPath, `${sdkEvents.join("\n")}
|
|
2517
3132
|
`);
|
|
2518
3133
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
2519
3134
|
return sessionId;
|
|
@@ -2565,21 +3180,21 @@ function convertAssistantContent(content) {
|
|
|
2565
3180
|
}
|
|
2566
3181
|
|
|
2567
3182
|
// src/lib/mind-events.ts
|
|
2568
|
-
var
|
|
2569
|
-
function
|
|
2570
|
-
let set =
|
|
3183
|
+
var subscribers = /* @__PURE__ */ new Map();
|
|
3184
|
+
function subscribe3(mind, callback) {
|
|
3185
|
+
let set = subscribers.get(mind);
|
|
2571
3186
|
if (!set) {
|
|
2572
3187
|
set = /* @__PURE__ */ new Set();
|
|
2573
|
-
|
|
3188
|
+
subscribers.set(mind, set);
|
|
2574
3189
|
}
|
|
2575
3190
|
set.add(callback);
|
|
2576
3191
|
return () => {
|
|
2577
3192
|
set.delete(callback);
|
|
2578
|
-
if (set.size === 0)
|
|
3193
|
+
if (set.size === 0) subscribers.delete(mind);
|
|
2579
3194
|
};
|
|
2580
3195
|
}
|
|
2581
|
-
function
|
|
2582
|
-
const set =
|
|
3196
|
+
function publish3(mind, event) {
|
|
3197
|
+
const set = subscribers.get(mind);
|
|
2583
3198
|
if (!set) return;
|
|
2584
3199
|
for (const cb of set) {
|
|
2585
3200
|
try {
|
|
@@ -2587,7 +3202,7 @@ function publish2(mind, event) {
|
|
|
2587
3202
|
} catch (err) {
|
|
2588
3203
|
console.error("[mind-events] subscriber threw:", err);
|
|
2589
3204
|
set.delete(cb);
|
|
2590
|
-
if (set.size === 0)
|
|
3205
|
+
if (set.size === 0) subscribers.delete(mind);
|
|
2591
3206
|
}
|
|
2592
3207
|
}
|
|
2593
3208
|
}
|
|
@@ -2595,22 +3210,22 @@ function publish2(mind, event) {
|
|
|
2595
3210
|
// src/lib/template.ts
|
|
2596
3211
|
import {
|
|
2597
3212
|
cpSync,
|
|
2598
|
-
existsSync as
|
|
2599
|
-
mkdirSync as
|
|
2600
|
-
readdirSync as
|
|
2601
|
-
readFileSync as
|
|
3213
|
+
existsSync as existsSync11,
|
|
3214
|
+
mkdirSync as mkdirSync7,
|
|
3215
|
+
readdirSync as readdirSync5,
|
|
3216
|
+
readFileSync as readFileSync10,
|
|
2602
3217
|
renameSync as renameSync3,
|
|
2603
|
-
rmSync,
|
|
2604
|
-
statSync,
|
|
2605
|
-
writeFileSync as
|
|
3218
|
+
rmSync as rmSync2,
|
|
3219
|
+
statSync as statSync3,
|
|
3220
|
+
writeFileSync as writeFileSync8
|
|
2606
3221
|
} from "fs";
|
|
2607
3222
|
import { tmpdir } from "os";
|
|
2608
|
-
import { dirname as dirname2, join, relative, resolve as
|
|
3223
|
+
import { dirname as dirname2, join as join3, relative, resolve as resolve15 } from "path";
|
|
2609
3224
|
function findTemplatesRoot() {
|
|
2610
3225
|
let dir = dirname2(new URL(import.meta.url).pathname);
|
|
2611
3226
|
for (let i = 0; i < 5; i++) {
|
|
2612
|
-
const candidate =
|
|
2613
|
-
if (
|
|
3227
|
+
const candidate = resolve15(dir, "templates");
|
|
3228
|
+
if (existsSync11(resolve15(candidate, "_base"))) return candidate;
|
|
2614
3229
|
dir = dirname2(dir);
|
|
2615
3230
|
}
|
|
2616
3231
|
console.error(
|
|
@@ -2620,72 +3235,72 @@ function findTemplatesRoot() {
|
|
|
2620
3235
|
process.exit(1);
|
|
2621
3236
|
}
|
|
2622
3237
|
function composeTemplate(templatesRoot, templateName) {
|
|
2623
|
-
const baseDir =
|
|
2624
|
-
const templateDir =
|
|
2625
|
-
if (!
|
|
3238
|
+
const baseDir = resolve15(templatesRoot, "_base");
|
|
3239
|
+
const templateDir = resolve15(templatesRoot, templateName);
|
|
3240
|
+
if (!existsSync11(baseDir)) {
|
|
2626
3241
|
console.error("Base template not found:", baseDir);
|
|
2627
3242
|
process.exit(1);
|
|
2628
3243
|
}
|
|
2629
|
-
if (!
|
|
3244
|
+
if (!existsSync11(templateDir)) {
|
|
2630
3245
|
console.error(`Template not found: ${templateName}`);
|
|
2631
3246
|
process.exit(1);
|
|
2632
3247
|
}
|
|
2633
|
-
const composedDir =
|
|
2634
|
-
|
|
3248
|
+
const composedDir = resolve15(tmpdir(), `volute-template-${Date.now()}`);
|
|
3249
|
+
mkdirSync7(composedDir, { recursive: true });
|
|
2635
3250
|
cpSync(baseDir, composedDir, { recursive: true });
|
|
2636
3251
|
for (const file of listFiles(templateDir)) {
|
|
2637
|
-
const src =
|
|
2638
|
-
const dest =
|
|
2639
|
-
|
|
3252
|
+
const src = resolve15(templateDir, file);
|
|
3253
|
+
const dest = resolve15(composedDir, file);
|
|
3254
|
+
mkdirSync7(dirname2(dest), { recursive: true });
|
|
2640
3255
|
cpSync(src, dest);
|
|
2641
3256
|
}
|
|
2642
|
-
const manifestPath =
|
|
2643
|
-
if (!
|
|
2644
|
-
|
|
3257
|
+
const manifestPath = resolve15(composedDir, "volute-template.json");
|
|
3258
|
+
if (!existsSync11(manifestPath)) {
|
|
3259
|
+
rmSync2(composedDir, { recursive: true, force: true });
|
|
2645
3260
|
console.error(`Template manifest not found: ${templateName}/volute-template.json`);
|
|
2646
3261
|
process.exit(1);
|
|
2647
3262
|
}
|
|
2648
|
-
const manifest = JSON.parse(
|
|
2649
|
-
|
|
3263
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
3264
|
+
rmSync2(manifestPath);
|
|
2650
3265
|
return { composedDir, manifest };
|
|
2651
3266
|
}
|
|
2652
3267
|
function copyTemplateToDir(composedDir, destDir, mindName, manifest) {
|
|
2653
3268
|
cpSync(composedDir, destDir, { recursive: true });
|
|
2654
3269
|
for (const [from, to] of Object.entries(manifest.rename)) {
|
|
2655
|
-
const fromPath =
|
|
2656
|
-
if (
|
|
2657
|
-
renameSync3(fromPath,
|
|
3270
|
+
const fromPath = resolve15(destDir, from);
|
|
3271
|
+
if (existsSync11(fromPath)) {
|
|
3272
|
+
renameSync3(fromPath, resolve15(destDir, to));
|
|
2658
3273
|
}
|
|
2659
3274
|
}
|
|
2660
3275
|
for (const file of manifest.substitute) {
|
|
2661
|
-
const path =
|
|
2662
|
-
if (
|
|
2663
|
-
const content =
|
|
2664
|
-
|
|
3276
|
+
const path = resolve15(destDir, file);
|
|
3277
|
+
if (existsSync11(path)) {
|
|
3278
|
+
const content = readFileSync10(path, "utf-8");
|
|
3279
|
+
writeFileSync8(path, content.replaceAll("{{name}}", mindName));
|
|
2665
3280
|
}
|
|
2666
3281
|
}
|
|
2667
3282
|
}
|
|
2668
3283
|
function applyInitFiles(destDir) {
|
|
2669
|
-
const initDir =
|
|
2670
|
-
if (!
|
|
2671
|
-
const homeDir =
|
|
3284
|
+
const initDir = resolve15(destDir, ".init");
|
|
3285
|
+
if (!existsSync11(initDir)) return;
|
|
3286
|
+
const homeDir = resolve15(destDir, "home");
|
|
2672
3287
|
for (const file of listFiles(initDir)) {
|
|
2673
|
-
const src =
|
|
2674
|
-
const dest =
|
|
3288
|
+
const src = resolve15(initDir, file);
|
|
3289
|
+
const dest = resolve15(homeDir, file);
|
|
2675
3290
|
const parent = dirname2(dest);
|
|
2676
|
-
if (!
|
|
2677
|
-
|
|
3291
|
+
if (!existsSync11(parent)) {
|
|
3292
|
+
mkdirSync7(parent, { recursive: true });
|
|
2678
3293
|
}
|
|
2679
3294
|
cpSync(src, dest);
|
|
2680
3295
|
}
|
|
2681
|
-
|
|
3296
|
+
rmSync2(initDir, { recursive: true, force: true });
|
|
2682
3297
|
}
|
|
2683
3298
|
function listFiles(dir) {
|
|
2684
3299
|
const results = [];
|
|
2685
3300
|
function walk(current) {
|
|
2686
|
-
for (const entry of
|
|
2687
|
-
const full =
|
|
2688
|
-
if (
|
|
3301
|
+
for (const entry of readdirSync5(current)) {
|
|
3302
|
+
const full = join3(current, entry);
|
|
3303
|
+
if (statSync3(full).isDirectory()) {
|
|
2689
3304
|
if (entry === ".git") continue;
|
|
2690
3305
|
walk(full);
|
|
2691
3306
|
} else {
|
|
@@ -2729,6 +3344,12 @@ async function getMindStatus(name, port) {
|
|
|
2729
3344
|
return { status, channels };
|
|
2730
3345
|
}
|
|
2731
3346
|
var TEMPLATE_BRANCH = "volute/template";
|
|
3347
|
+
async function configureGitIdentity(mindName, opts) {
|
|
3348
|
+
const systemsConfig = readSystemsConfig();
|
|
3349
|
+
const system = systemsConfig?.system ?? "local";
|
|
3350
|
+
await gitExec(["config", "user.name", mindName], opts);
|
|
3351
|
+
await gitExec(["config", "user.email", `${mindName}.${system}@volute.systems`], opts);
|
|
3352
|
+
}
|
|
2732
3353
|
async function initTemplateBranch(projectRoot, composedDir, manifest, mindName, env) {
|
|
2733
3354
|
const templateFiles = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).map((f) => manifest.rename[f] ?? f);
|
|
2734
3355
|
const opts = { cwd: projectRoot, mindName, env };
|
|
@@ -2740,7 +3361,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
|
|
|
2740
3361
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
2741
3362
|
}
|
|
2742
3363
|
async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
2743
|
-
const tempWorktree =
|
|
3364
|
+
const tempWorktree = resolve16(projectRoot, ".variants", "_template_update");
|
|
2744
3365
|
let branchExists = false;
|
|
2745
3366
|
try {
|
|
2746
3367
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -2751,8 +3372,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2751
3372
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2752
3373
|
} catch {
|
|
2753
3374
|
}
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
3375
|
+
if (existsSync12(tempWorktree)) {
|
|
3376
|
+
rmSync3(tempWorktree, { recursive: true, force: true });
|
|
2756
3377
|
}
|
|
2757
3378
|
const templatesRoot = findTemplatesRoot();
|
|
2758
3379
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -2772,9 +3393,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2772
3393
|
});
|
|
2773
3394
|
}
|
|
2774
3395
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
2775
|
-
const initDir =
|
|
2776
|
-
if (
|
|
2777
|
-
|
|
3396
|
+
const initDir = resolve16(tempWorktree, ".init");
|
|
3397
|
+
if (existsSync12(initDir)) {
|
|
3398
|
+
rmSync3(initDir, { recursive: true, force: true });
|
|
2778
3399
|
}
|
|
2779
3400
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
2780
3401
|
try {
|
|
@@ -2787,10 +3408,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2787
3408
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2788
3409
|
} catch {
|
|
2789
3410
|
}
|
|
2790
|
-
if (
|
|
2791
|
-
|
|
3411
|
+
if (existsSync12(tempWorktree)) {
|
|
3412
|
+
rmSync3(tempWorktree, { recursive: true, force: true });
|
|
2792
3413
|
}
|
|
2793
|
-
|
|
3414
|
+
rmSync3(composedDir, { recursive: true, force: true });
|
|
2794
3415
|
}
|
|
2795
3416
|
}
|
|
2796
3417
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -2813,14 +3434,14 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
2813
3434
|
async function npmInstallAsMind(cwd, mindName) {
|
|
2814
3435
|
if (isIsolationEnabled()) {
|
|
2815
3436
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
2816
|
-
await exec(cmd, args, { cwd, env: { ...process.env, HOME:
|
|
3437
|
+
await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve16(cwd, "home") } });
|
|
2817
3438
|
} else {
|
|
2818
3439
|
await exec("npm", ["install"], { cwd });
|
|
2819
3440
|
}
|
|
2820
3441
|
}
|
|
2821
3442
|
async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
2822
|
-
const extractedMindDir =
|
|
2823
|
-
if (!
|
|
3443
|
+
const extractedMindDir = resolve16(tempDir, "mind");
|
|
3444
|
+
if (!existsSync12(extractedMindDir)) {
|
|
2824
3445
|
return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
|
|
2825
3446
|
}
|
|
2826
3447
|
if (!manifest?.includes || !manifest.name || !manifest.template) {
|
|
@@ -2832,34 +3453,34 @@ async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
|
2832
3453
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
2833
3454
|
ensureVoluteHome();
|
|
2834
3455
|
const dest = mindDir(name);
|
|
2835
|
-
if (
|
|
3456
|
+
if (existsSync12(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
2836
3457
|
try {
|
|
2837
3458
|
cpSync2(extractedMindDir, dest, { recursive: true });
|
|
2838
3459
|
if (!manifest.includes.identity) {
|
|
2839
3460
|
generateIdentity(dest);
|
|
2840
3461
|
}
|
|
2841
3462
|
const state = stateDir(name);
|
|
2842
|
-
|
|
2843
|
-
const channelsJson =
|
|
2844
|
-
if (
|
|
2845
|
-
cpSync2(channelsJson,
|
|
3463
|
+
mkdirSync8(state, { recursive: true });
|
|
3464
|
+
const channelsJson = resolve16(tempDir, "state/channels.json");
|
|
3465
|
+
if (existsSync12(channelsJson)) {
|
|
3466
|
+
cpSync2(channelsJson, resolve16(state, "channels.json"));
|
|
2846
3467
|
}
|
|
2847
|
-
const envJson =
|
|
2848
|
-
if (
|
|
2849
|
-
cpSync2(envJson,
|
|
3468
|
+
const envJson = resolve16(tempDir, "state/env.json");
|
|
3469
|
+
if (existsSync12(envJson)) {
|
|
3470
|
+
cpSync2(envJson, resolve16(state, "env.json"));
|
|
2850
3471
|
}
|
|
2851
3472
|
const port = nextPort();
|
|
2852
3473
|
addMind(name, port, void 0, manifest.template);
|
|
2853
|
-
const homeDir =
|
|
3474
|
+
const homeDir = resolve16(dest, "home");
|
|
2854
3475
|
ensureVoluteGroup();
|
|
2855
3476
|
createMindUser(name, homeDir);
|
|
2856
3477
|
chownMindDir(dest, name);
|
|
2857
3478
|
await npmInstallAsMind(dest, name);
|
|
2858
|
-
const historyJsonl =
|
|
2859
|
-
if (
|
|
3479
|
+
const historyJsonl = resolve16(tempDir, "history.jsonl");
|
|
3480
|
+
if (existsSync12(historyJsonl)) {
|
|
2860
3481
|
try {
|
|
2861
3482
|
const db = await getDb();
|
|
2862
|
-
const lines =
|
|
3483
|
+
const lines = readFileSync11(historyJsonl, "utf-8").trim().split("\n");
|
|
2863
3484
|
let imported = 0;
|
|
2864
3485
|
let failed = 0;
|
|
2865
3486
|
for (const line of lines) {
|
|
@@ -2894,31 +3515,32 @@ async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
|
2894
3515
|
logger_default.error("Failed to open database for history import", logger_default.errorData(err));
|
|
2895
3516
|
}
|
|
2896
3517
|
}
|
|
2897
|
-
const sessionsDir =
|
|
2898
|
-
if (
|
|
2899
|
-
const destSessions =
|
|
2900
|
-
|
|
2901
|
-
for (const file of
|
|
2902
|
-
cpSync2(
|
|
3518
|
+
const sessionsDir = resolve16(tempDir, "sessions");
|
|
3519
|
+
if (existsSync12(sessionsDir)) {
|
|
3520
|
+
const destSessions = resolve16(dest, ".mind/sessions");
|
|
3521
|
+
mkdirSync8(destSessions, { recursive: true });
|
|
3522
|
+
for (const file of readdirSync6(sessionsDir)) {
|
|
3523
|
+
cpSync2(resolve16(sessionsDir, file), resolve16(destSessions, file));
|
|
2903
3524
|
}
|
|
2904
3525
|
}
|
|
2905
|
-
if (!
|
|
2906
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3526
|
+
if (!existsSync12(resolve16(dest, ".git"))) {
|
|
3527
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
|
|
2907
3528
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3529
|
+
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
2908
3530
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
2909
3531
|
await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
|
|
2910
3532
|
}
|
|
2911
3533
|
chownMindDir(dest, name);
|
|
2912
|
-
|
|
3534
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
2913
3535
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
2914
3536
|
} catch (err) {
|
|
2915
|
-
if (
|
|
3537
|
+
if (existsSync12(dest)) rmSync3(dest, { recursive: true, force: true });
|
|
2916
3538
|
try {
|
|
2917
3539
|
removeMind(name);
|
|
2918
3540
|
} catch (cleanupErr) {
|
|
2919
3541
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
2920
3542
|
}
|
|
2921
|
-
|
|
3543
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
2922
3544
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
2923
3545
|
}
|
|
2924
3546
|
}
|
|
@@ -2931,7 +3553,7 @@ var createMindSchema = z3.object({
|
|
|
2931
3553
|
seedSoul: z3.string().optional(),
|
|
2932
3554
|
skills: z3.array(z3.string()).optional()
|
|
2933
3555
|
});
|
|
2934
|
-
var
|
|
3556
|
+
var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindSchema), async (c) => {
|
|
2935
3557
|
const body = c.req.valid("json");
|
|
2936
3558
|
const { name, template = "claude" } = body;
|
|
2937
3559
|
const nameErr = validateMindName(name);
|
|
@@ -2939,7 +3561,7 @@ var app9 = new Hono9().post("/", requireAdmin, zValidator3("json", createMindSch
|
|
|
2939
3561
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
2940
3562
|
ensureVoluteHome();
|
|
2941
3563
|
const dest = mindDir(name);
|
|
2942
|
-
if (
|
|
3564
|
+
if (existsSync12(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
2943
3565
|
const templatesRoot = findTemplatesRoot();
|
|
2944
3566
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
2945
3567
|
try {
|
|
@@ -2947,21 +3569,21 @@ var app9 = new Hono9().post("/", requireAdmin, zValidator3("json", createMindSch
|
|
|
2947
3569
|
applyInitFiles(dest);
|
|
2948
3570
|
const { publicKeyPem } = generateIdentity(dest);
|
|
2949
3571
|
if (body.model) {
|
|
2950
|
-
const
|
|
2951
|
-
const existing =
|
|
3572
|
+
const configPath2 = resolve16(dest, "home/.config/config.json");
|
|
3573
|
+
const existing = existsSync12(configPath2) ? JSON.parse(readFileSync11(configPath2, "utf-8")) : {};
|
|
2952
3574
|
existing.model = body.model;
|
|
2953
|
-
|
|
3575
|
+
writeFileSync9(configPath2, `${JSON.stringify(existing, null, 2)}
|
|
2954
3576
|
`);
|
|
2955
3577
|
}
|
|
2956
3578
|
const mindPrompts = await getMindPromptDefaults();
|
|
2957
|
-
|
|
2958
|
-
|
|
3579
|
+
writeFileSync9(
|
|
3580
|
+
resolve16(dest, "home/.config/prompts.json"),
|
|
2959
3581
|
`${JSON.stringify(mindPrompts, null, 2)}
|
|
2960
3582
|
`
|
|
2961
3583
|
);
|
|
2962
3584
|
const port = nextPort();
|
|
2963
3585
|
addMind(name, port, body.stage, template);
|
|
2964
|
-
const homeDir =
|
|
3586
|
+
const homeDir = resolve16(dest, "home");
|
|
2965
3587
|
ensureVoluteGroup();
|
|
2966
3588
|
createMindUser(name, homeDir);
|
|
2967
3589
|
chownMindDir(dest, name);
|
|
@@ -2970,10 +3592,11 @@ var app9 = new Hono9().post("/", requireAdmin, zValidator3("json", createMindSch
|
|
|
2970
3592
|
try {
|
|
2971
3593
|
const env = isIsolationEnabled() ? { ...process.env, HOME: homeDir } : void 0;
|
|
2972
3594
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3595
|
+
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
2973
3596
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
2974
3597
|
} catch (err) {
|
|
2975
3598
|
logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
|
|
2976
|
-
|
|
3599
|
+
rmSync3(resolve16(dest, ".git"), { recursive: true, force: true });
|
|
2977
3600
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
2978
3601
|
}
|
|
2979
3602
|
try {
|
|
@@ -2988,7 +3611,7 @@ The human who planted you described you as: "${body.description}"
|
|
|
2988
3611
|
` : "";
|
|
2989
3612
|
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
2990
3613
|
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
2991
|
-
|
|
3614
|
+
writeFileSync9(resolve16(dest, "home/SOUL.md"), seedSoul);
|
|
2992
3615
|
}
|
|
2993
3616
|
const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
|
|
2994
3617
|
const skillWarnings = [];
|
|
@@ -3003,11 +3626,11 @@ The human who planted you described you as: "${body.description}"
|
|
|
3003
3626
|
if (body.stage !== "seed") {
|
|
3004
3627
|
const customSoul = await getPromptIfCustom("default_soul");
|
|
3005
3628
|
if (customSoul) {
|
|
3006
|
-
|
|
3629
|
+
writeFileSync9(resolve16(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3007
3630
|
}
|
|
3008
3631
|
const customMemory = await getPromptIfCustom("default_memory");
|
|
3009
3632
|
if (customMemory) {
|
|
3010
|
-
|
|
3633
|
+
writeFileSync9(resolve16(dest, "home/MEMORY.md"), customMemory);
|
|
3011
3634
|
}
|
|
3012
3635
|
}
|
|
3013
3636
|
publishPublicKey(name, publicKeyPem).catch(
|
|
@@ -3023,14 +3646,14 @@ The human who planted you described you as: "${body.description}"
|
|
|
3023
3646
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3024
3647
|
});
|
|
3025
3648
|
} catch (err) {
|
|
3026
|
-
if (
|
|
3649
|
+
if (existsSync12(dest)) rmSync3(dest, { recursive: true, force: true });
|
|
3027
3650
|
try {
|
|
3028
3651
|
removeMind(name);
|
|
3029
3652
|
} catch {
|
|
3030
3653
|
}
|
|
3031
3654
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
3032
3655
|
} finally {
|
|
3033
|
-
|
|
3656
|
+
rmSync3(composedDir, { recursive: true, force: true });
|
|
3034
3657
|
}
|
|
3035
3658
|
}).post("/import", requireAdmin, async (c) => {
|
|
3036
3659
|
let body;
|
|
@@ -3043,13 +3666,13 @@ The human who planted you described you as: "${body.description}"
|
|
|
3043
3666
|
return importFromArchive(c, body.archivePath, body.name, body.manifest);
|
|
3044
3667
|
}
|
|
3045
3668
|
const wsDir = body.workspacePath;
|
|
3046
|
-
if (!wsDir || !
|
|
3669
|
+
if (!wsDir || !existsSync12(resolve16(wsDir, "SOUL.md")) || !existsSync12(resolve16(wsDir, "IDENTITY.md"))) {
|
|
3047
3670
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
3048
3671
|
}
|
|
3049
|
-
const soul =
|
|
3050
|
-
const identity =
|
|
3051
|
-
const userPath =
|
|
3052
|
-
const user =
|
|
3672
|
+
const soul = readFileSync11(resolve16(wsDir, "SOUL.md"), "utf-8");
|
|
3673
|
+
const identity = readFileSync11(resolve16(wsDir, "IDENTITY.md"), "utf-8");
|
|
3674
|
+
const userPath = resolve16(wsDir, "USER.md");
|
|
3675
|
+
const user = existsSync12(userPath) ? readFileSync11(userPath, "utf-8") : "";
|
|
3053
3676
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
3054
3677
|
const template = body.template ?? "claude";
|
|
3055
3678
|
const nameErr = validateMindName(name);
|
|
@@ -3069,39 +3692,39 @@ ${user.trimEnd()}
|
|
|
3069
3692
|
` : "";
|
|
3070
3693
|
ensureVoluteHome();
|
|
3071
3694
|
const dest = mindDir(name);
|
|
3072
|
-
if (
|
|
3695
|
+
if (existsSync12(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3073
3696
|
const templatesRoot = findTemplatesRoot();
|
|
3074
3697
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3075
3698
|
try {
|
|
3076
3699
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
3077
3700
|
applyInitFiles(dest);
|
|
3078
3701
|
const { publicKeyPem: importPublicKey } = generateIdentity(dest);
|
|
3079
|
-
|
|
3080
|
-
const wsMemoryPath =
|
|
3081
|
-
const hasMemory =
|
|
3702
|
+
writeFileSync9(resolve16(dest, "home/SOUL.md"), mergedSoul);
|
|
3703
|
+
const wsMemoryPath = resolve16(wsDir, "MEMORY.md");
|
|
3704
|
+
const hasMemory = existsSync12(wsMemoryPath);
|
|
3082
3705
|
if (hasMemory) {
|
|
3083
|
-
const existingMemory =
|
|
3084
|
-
|
|
3085
|
-
|
|
3706
|
+
const existingMemory = readFileSync11(wsMemoryPath, "utf-8");
|
|
3707
|
+
writeFileSync9(
|
|
3708
|
+
resolve16(dest, "home/MEMORY.md"),
|
|
3086
3709
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
3087
3710
|
);
|
|
3088
3711
|
} else if (user) {
|
|
3089
|
-
|
|
3712
|
+
writeFileSync9(resolve16(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
3090
3713
|
`);
|
|
3091
3714
|
}
|
|
3092
|
-
const wsMemoryDir =
|
|
3715
|
+
const wsMemoryDir = resolve16(wsDir, "memory");
|
|
3093
3716
|
let dailyLogCount = 0;
|
|
3094
|
-
if (
|
|
3095
|
-
const destMemoryDir =
|
|
3096
|
-
const files =
|
|
3717
|
+
if (existsSync12(wsMemoryDir)) {
|
|
3718
|
+
const destMemoryDir = resolve16(dest, "home/memory");
|
|
3719
|
+
const files = readdirSync6(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
3097
3720
|
for (const file of files) {
|
|
3098
|
-
cpSync2(
|
|
3721
|
+
cpSync2(resolve16(wsMemoryDir, file), resolve16(destMemoryDir, file));
|
|
3099
3722
|
}
|
|
3100
3723
|
dailyLogCount = files.length;
|
|
3101
3724
|
}
|
|
3102
3725
|
const port = nextPort();
|
|
3103
3726
|
addMind(name, port, void 0, template);
|
|
3104
|
-
const homeDir =
|
|
3727
|
+
const homeDir = resolve16(dest, "home");
|
|
3105
3728
|
ensureVoluteGroup();
|
|
3106
3729
|
createMindUser(name, homeDir);
|
|
3107
3730
|
chownMindDir(dest, name);
|
|
@@ -3109,19 +3732,20 @@ ${user.trimEnd()}
|
|
|
3109
3732
|
if (!hasMemory && dailyLogCount > 0) {
|
|
3110
3733
|
await consolidateMemory(dest);
|
|
3111
3734
|
}
|
|
3112
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3735
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dest, "home") } : void 0;
|
|
3113
3736
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3737
|
+
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3114
3738
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3115
3739
|
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
|
|
3116
|
-
const sessionFile = body.sessionPath ?
|
|
3117
|
-
if (sessionFile &&
|
|
3740
|
+
const sessionFile = body.sessionPath ? resolve16(body.sessionPath) : findOpenClawSession(wsDir);
|
|
3741
|
+
if (sessionFile && existsSync12(sessionFile)) {
|
|
3118
3742
|
if (template === "pi") {
|
|
3119
3743
|
importPiSession(sessionFile, dest);
|
|
3120
3744
|
} else if (template === "claude") {
|
|
3121
3745
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
3122
|
-
const mindRuntimeDir =
|
|
3123
|
-
|
|
3124
|
-
|
|
3746
|
+
const mindRuntimeDir = resolve16(dest, ".mind");
|
|
3747
|
+
mkdirSync8(mindRuntimeDir, { recursive: true });
|
|
3748
|
+
writeFileSync9(resolve16(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
|
|
3125
3749
|
}
|
|
3126
3750
|
}
|
|
3127
3751
|
importOpenClawConnectors(name, dest);
|
|
@@ -3136,14 +3760,14 @@ ${user.trimEnd()}
|
|
|
3136
3760
|
);
|
|
3137
3761
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3138
3762
|
} catch (err) {
|
|
3139
|
-
if (
|
|
3763
|
+
if (existsSync12(dest)) rmSync3(dest, { recursive: true, force: true });
|
|
3140
3764
|
try {
|
|
3141
3765
|
removeMind(name);
|
|
3142
3766
|
} catch {
|
|
3143
3767
|
}
|
|
3144
3768
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3145
3769
|
} finally {
|
|
3146
|
-
|
|
3770
|
+
rmSync3(composedDir, { recursive: true, force: true });
|
|
3147
3771
|
}
|
|
3148
3772
|
}).get("/", async (c) => {
|
|
3149
3773
|
const entries = readRegistry();
|
|
@@ -3160,7 +3784,7 @@ ${user.trimEnd()}
|
|
|
3160
3784
|
const minds = await Promise.all(
|
|
3161
3785
|
entries.map(async (entry) => {
|
|
3162
3786
|
const { status, channels } = await getMindStatus(entry.name, entry.port);
|
|
3163
|
-
const hasPages =
|
|
3787
|
+
const hasPages = existsSync12(resolve16(mindDir(entry.name), "home", "pages"));
|
|
3164
3788
|
return {
|
|
3165
3789
|
...entry,
|
|
3166
3790
|
status,
|
|
@@ -3171,58 +3795,15 @@ ${user.trimEnd()}
|
|
|
3171
3795
|
})
|
|
3172
3796
|
);
|
|
3173
3797
|
return c.json(minds);
|
|
3798
|
+
}).get("/pages/sites", async (c) => {
|
|
3799
|
+
return c.json(getCachedSites());
|
|
3174
3800
|
}).get("/pages/recent", async (c) => {
|
|
3175
|
-
|
|
3176
|
-
const pages = [];
|
|
3177
|
-
for (const entry of entries) {
|
|
3178
|
-
const pagesDir = resolve13(mindDir(entry.name), "home", "pages");
|
|
3179
|
-
if (!existsSync10(pagesDir)) continue;
|
|
3180
|
-
let items;
|
|
3181
|
-
try {
|
|
3182
|
-
items = readdirSync4(pagesDir);
|
|
3183
|
-
} catch (err) {
|
|
3184
|
-
logger_default.warn("Failed to read pages dir", { mind: entry.name, error: err.message });
|
|
3185
|
-
continue;
|
|
3186
|
-
}
|
|
3187
|
-
for (const item of items) {
|
|
3188
|
-
const fullPath = resolve13(pagesDir, item);
|
|
3189
|
-
try {
|
|
3190
|
-
const s = statSync2(fullPath);
|
|
3191
|
-
if (s.isFile()) {
|
|
3192
|
-
pages.push({
|
|
3193
|
-
mind: entry.name,
|
|
3194
|
-
file: item,
|
|
3195
|
-
modified: s.mtime.toISOString(),
|
|
3196
|
-
url: `/pages/${entry.name}/${item}`
|
|
3197
|
-
});
|
|
3198
|
-
} else if (s.isDirectory()) {
|
|
3199
|
-
const indexPath = resolve13(fullPath, "index.html");
|
|
3200
|
-
if (existsSync10(indexPath)) {
|
|
3201
|
-
const indexStat = statSync2(indexPath);
|
|
3202
|
-
pages.push({
|
|
3203
|
-
mind: entry.name,
|
|
3204
|
-
file: join2(item, "index.html"),
|
|
3205
|
-
modified: indexStat.mtime.toISOString(),
|
|
3206
|
-
url: `/pages/${entry.name}/${item}/`
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
} catch (err) {
|
|
3211
|
-
logger_default.warn("Failed to stat page item", {
|
|
3212
|
-
mind: entry.name,
|
|
3213
|
-
item,
|
|
3214
|
-
error: err.message
|
|
3215
|
-
});
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
|
-
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
3220
|
-
return c.json(pages.slice(0, 10));
|
|
3801
|
+
return c.json(getCachedRecentPages());
|
|
3221
3802
|
}).get("/:name", async (c) => {
|
|
3222
3803
|
const name = c.req.param("name");
|
|
3223
3804
|
const entry = findMind(name);
|
|
3224
3805
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3225
|
-
if (!
|
|
3806
|
+
if (!existsSync12(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
|
|
3226
3807
|
const { status, channels } = await getMindStatus(name, entry.port);
|
|
3227
3808
|
const variants = readVariants(name);
|
|
3228
3809
|
const manager = getMindManager();
|
|
@@ -3237,7 +3818,7 @@ ${user.trimEnd()}
|
|
|
3237
3818
|
return { name: v.name, port: v.port, status: variantStatus };
|
|
3238
3819
|
})
|
|
3239
3820
|
);
|
|
3240
|
-
const hasPages =
|
|
3821
|
+
const hasPages = existsSync12(resolve16(mindDir(name), "home", "pages"));
|
|
3241
3822
|
return c.json({ ...entry, status, channels, variants: variantStatuses, hasPages });
|
|
3242
3823
|
}).post("/:name/start", requireAdmin, async (c) => {
|
|
3243
3824
|
const name = c.req.param("name");
|
|
@@ -3249,7 +3830,7 @@ ${user.trimEnd()}
|
|
|
3249
3830
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
3250
3831
|
} else {
|
|
3251
3832
|
const dir = mindDir(baseName);
|
|
3252
|
-
if (!
|
|
3833
|
+
if (!existsSync12(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3253
3834
|
}
|
|
3254
3835
|
if (getMindManager().isRunning(name)) {
|
|
3255
3836
|
return c.json({ error: "Mind already running" }, 409);
|
|
@@ -3270,7 +3851,7 @@ ${user.trimEnd()}
|
|
|
3270
3851
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
3271
3852
|
} else {
|
|
3272
3853
|
const dir = mindDir(baseName);
|
|
3273
|
-
if (!
|
|
3854
|
+
if (!existsSync12(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3274
3855
|
}
|
|
3275
3856
|
let context;
|
|
3276
3857
|
const contentType = c.req.header("content-type");
|
|
@@ -3297,7 +3878,7 @@ ${user.trimEnd()}
|
|
|
3297
3878
|
const variant = findVariant(baseName, mergeVariantName);
|
|
3298
3879
|
if (variant) {
|
|
3299
3880
|
const projectRoot = mindDir(baseName);
|
|
3300
|
-
if (
|
|
3881
|
+
if (existsSync12(variant.path)) {
|
|
3301
3882
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3302
3883
|
if (status) {
|
|
3303
3884
|
try {
|
|
@@ -3325,7 +3906,7 @@ ${user.trimEnd()}
|
|
|
3325
3906
|
}
|
|
3326
3907
|
}
|
|
3327
3908
|
await gitExec(["merge", variant.branch], { cwd: projectRoot });
|
|
3328
|
-
if (
|
|
3909
|
+
if (existsSync12(variant.path)) {
|
|
3329
3910
|
try {
|
|
3330
3911
|
await gitExec(["worktree", "remove", "--force", variant.path], {
|
|
3331
3912
|
cwd: projectRoot
|
|
@@ -3414,11 +3995,11 @@ ${user.trimEnd()}
|
|
|
3414
3995
|
removeMind(name);
|
|
3415
3996
|
await deleteMindUser2(name);
|
|
3416
3997
|
const state = stateDir(name);
|
|
3417
|
-
if (
|
|
3418
|
-
|
|
3998
|
+
if (existsSync12(state)) {
|
|
3999
|
+
rmSync3(state, { recursive: true, force: true });
|
|
3419
4000
|
}
|
|
3420
|
-
if (force &&
|
|
3421
|
-
|
|
4001
|
+
if (force && existsSync12(dir)) {
|
|
4002
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
3422
4003
|
deleteMindUser(name);
|
|
3423
4004
|
}
|
|
3424
4005
|
return c.json({ ok: true });
|
|
@@ -3427,7 +4008,7 @@ ${user.trimEnd()}
|
|
|
3427
4008
|
const entry = findMind(mindName);
|
|
3428
4009
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3429
4010
|
const dir = mindDir(mindName);
|
|
3430
|
-
if (!
|
|
4011
|
+
if (!existsSync12(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3431
4012
|
let body = {};
|
|
3432
4013
|
try {
|
|
3433
4014
|
body = await c.req.json();
|
|
@@ -3436,8 +4017,8 @@ ${user.trimEnd()}
|
|
|
3436
4017
|
const template = body.template ?? entry.template ?? "claude";
|
|
3437
4018
|
const UPGRADE_VARIANT = "upgrade";
|
|
3438
4019
|
if (body.continue) {
|
|
3439
|
-
const worktreeDir2 =
|
|
3440
|
-
if (!
|
|
4020
|
+
const worktreeDir2 = resolve16(dir, ".variants", UPGRADE_VARIANT);
|
|
4021
|
+
if (!existsSync12(worktreeDir2)) {
|
|
3441
4022
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3442
4023
|
}
|
|
3443
4024
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
|
|
@@ -3497,19 +4078,37 @@ ${user.trimEnd()}
|
|
|
3497
4078
|
);
|
|
3498
4079
|
}
|
|
3499
4080
|
}
|
|
3500
|
-
const worktreeDir =
|
|
3501
|
-
if (
|
|
4081
|
+
const worktreeDir = resolve16(dir, ".variants", UPGRADE_VARIANT);
|
|
4082
|
+
if (existsSync12(worktreeDir)) {
|
|
3502
4083
|
return c.json(
|
|
3503
4084
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
3504
4085
|
409
|
|
3505
4086
|
);
|
|
3506
4087
|
}
|
|
4088
|
+
if (!existsSync12(resolve16(dir, ".git"))) {
|
|
4089
|
+
try {
|
|
4090
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve16(dir, "home") } : void 0;
|
|
4091
|
+
await gitExec(["init"], { cwd: dir, mindName, env });
|
|
4092
|
+
await configureGitIdentity(mindName, { cwd: dir, mindName, env });
|
|
4093
|
+
await gitExec(["add", "-A"], { cwd: dir, mindName, env });
|
|
4094
|
+
await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
|
|
4095
|
+
chownMindDir(dir, mindName);
|
|
4096
|
+
} catch (err) {
|
|
4097
|
+
rmSync3(resolve16(dir, ".git"), { recursive: true, force: true });
|
|
4098
|
+
return c.json(
|
|
4099
|
+
{
|
|
4100
|
+
error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4101
|
+
},
|
|
4102
|
+
500
|
|
4103
|
+
);
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
3507
4106
|
await gitExec(["worktree", "prune"], { cwd: dir });
|
|
3508
4107
|
try {
|
|
3509
4108
|
await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
|
|
3510
4109
|
} catch {
|
|
3511
4110
|
}
|
|
3512
|
-
if (!
|
|
4111
|
+
if (!existsSync12(resolve16(dir, "home", "shared"))) {
|
|
3513
4112
|
try {
|
|
3514
4113
|
await addSharedWorktree(mindName, dir);
|
|
3515
4114
|
} catch (err) {
|
|
@@ -3520,9 +4119,9 @@ ${user.trimEnd()}
|
|
|
3520
4119
|
}
|
|
3521
4120
|
}
|
|
3522
4121
|
await updateTemplateBranch(dir, template, mindName);
|
|
3523
|
-
const parentDir =
|
|
3524
|
-
if (!
|
|
3525
|
-
|
|
4122
|
+
const parentDir = resolve16(dir, ".variants");
|
|
4123
|
+
if (!existsSync12(parentDir)) {
|
|
4124
|
+
mkdirSync8(parentDir, { recursive: true });
|
|
3526
4125
|
}
|
|
3527
4126
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
3528
4127
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -3704,7 +4303,79 @@ ${user.trimEnd()}
|
|
|
3704
4303
|
const usage = getTokenBudget().getUsage(baseName);
|
|
3705
4304
|
if (!usage) return c.json({ error: "No budget configured" }, 404);
|
|
3706
4305
|
return c.json(usage);
|
|
3707
|
-
}).get("/:name/
|
|
4306
|
+
}).get("/:name/config", (c) => {
|
|
4307
|
+
const name = c.req.param("name");
|
|
4308
|
+
const entry = findMind(name);
|
|
4309
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4310
|
+
const dir = mindDir(name);
|
|
4311
|
+
if (!existsSync12(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4312
|
+
let config = readVoluteConfig(dir);
|
|
4313
|
+
if (!config && entry.template === "pi") {
|
|
4314
|
+
const piConfigPath = resolve16(dir, "home/.config/config.json");
|
|
4315
|
+
if (existsSync12(piConfigPath)) {
|
|
4316
|
+
try {
|
|
4317
|
+
config = JSON.parse(readFileSync11(piConfigPath, "utf-8"));
|
|
4318
|
+
} catch {
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
return c.json({
|
|
4323
|
+
registry: {
|
|
4324
|
+
name: entry.name,
|
|
4325
|
+
port: entry.port,
|
|
4326
|
+
created: entry.created,
|
|
4327
|
+
stage: entry.stage,
|
|
4328
|
+
template: entry.template
|
|
4329
|
+
},
|
|
4330
|
+
config: {
|
|
4331
|
+
model: config?.model ?? null,
|
|
4332
|
+
thinkingLevel: config?.thinkingLevel ?? null,
|
|
4333
|
+
tokenBudget: config?.tokenBudget ?? null,
|
|
4334
|
+
tokenBudgetPeriodMinutes: config?.tokenBudgetPeriodMinutes ?? null
|
|
4335
|
+
}
|
|
4336
|
+
});
|
|
4337
|
+
}).put(
|
|
4338
|
+
"/:name/config",
|
|
4339
|
+
requireAdmin,
|
|
4340
|
+
zValidator3(
|
|
4341
|
+
"json",
|
|
4342
|
+
z3.object({
|
|
4343
|
+
model: z3.string().optional(),
|
|
4344
|
+
thinkingLevel: z3.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
|
|
4345
|
+
tokenBudget: z3.number().int().positive().nullable().optional(),
|
|
4346
|
+
tokenBudgetPeriodMinutes: z3.number().int().positive().nullable().optional()
|
|
4347
|
+
})
|
|
4348
|
+
),
|
|
4349
|
+
async (c) => {
|
|
4350
|
+
const name = c.req.param("name");
|
|
4351
|
+
const entry = findMind(name);
|
|
4352
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4353
|
+
const dir = mindDir(name);
|
|
4354
|
+
if (!existsSync12(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4355
|
+
const body = c.req.valid("json");
|
|
4356
|
+
const existing = readVoluteConfig(dir) ?? {};
|
|
4357
|
+
if (body.model !== void 0) existing.model = body.model;
|
|
4358
|
+
if (body.thinkingLevel !== void 0) {
|
|
4359
|
+
existing.thinkingLevel = body.thinkingLevel;
|
|
4360
|
+
}
|
|
4361
|
+
if (body.tokenBudget !== void 0) {
|
|
4362
|
+
if (body.tokenBudget === null) {
|
|
4363
|
+
delete existing.tokenBudget;
|
|
4364
|
+
} else {
|
|
4365
|
+
existing.tokenBudget = body.tokenBudget;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
if (body.tokenBudgetPeriodMinutes !== void 0) {
|
|
4369
|
+
if (body.tokenBudgetPeriodMinutes === null) {
|
|
4370
|
+
delete existing.tokenBudgetPeriodMinutes;
|
|
4371
|
+
} else {
|
|
4372
|
+
existing.tokenBudgetPeriodMinutes = body.tokenBudgetPeriodMinutes;
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
writeVoluteConfig(dir, existing);
|
|
4376
|
+
return c.json({ ok: true });
|
|
4377
|
+
}
|
|
4378
|
+
).get("/:name/delivery/pending", async (c) => {
|
|
3708
4379
|
const name = c.req.param("name");
|
|
3709
4380
|
const [baseName] = name.split("@", 2);
|
|
3710
4381
|
try {
|
|
@@ -3743,7 +4414,7 @@ ${user.trimEnd()}
|
|
|
3743
4414
|
} catch (err) {
|
|
3744
4415
|
logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
|
|
3745
4416
|
}
|
|
3746
|
-
|
|
4417
|
+
publish3(baseName, {
|
|
3747
4418
|
mind: baseName,
|
|
3748
4419
|
type: body.type,
|
|
3749
4420
|
session: body.session,
|
|
@@ -3752,15 +4423,17 @@ ${user.trimEnd()}
|
|
|
3752
4423
|
content: body.content,
|
|
3753
4424
|
metadata: body.metadata
|
|
3754
4425
|
});
|
|
4426
|
+
onMindEvent(baseName, body.type, body.channel);
|
|
3755
4427
|
if ((body.type === "text" || body.type === "outbound") && body.channel) {
|
|
3756
|
-
getTypingMap()
|
|
4428
|
+
const map = getTypingMap();
|
|
4429
|
+
const affected = map.deleteSender(baseName);
|
|
4430
|
+
publishTypingForChannels(affected, map);
|
|
3757
4431
|
}
|
|
3758
4432
|
if (body.type === "done") {
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
}
|
|
4433
|
+
const map = getTypingMap();
|
|
4434
|
+
const affected = map.deleteSender(baseName);
|
|
4435
|
+
publishTypingForChannels(affected, map);
|
|
4436
|
+
broadcast({ type: "mind_done", mind: baseName, summary: "Finished processing" });
|
|
3764
4437
|
try {
|
|
3765
4438
|
getDeliveryManager().sessionDone(baseName, body.session);
|
|
3766
4439
|
} catch (err) {
|
|
@@ -3791,7 +4464,7 @@ ${user.trimEnd()}
|
|
|
3791
4464
|
|
|
3792
4465
|
`));
|
|
3793
4466
|
};
|
|
3794
|
-
const unsubscribe =
|
|
4467
|
+
const unsubscribe = subscribe3(baseName, (event) => {
|
|
3795
4468
|
if (typeFilter && !typeFilter.includes(event.type)) return;
|
|
3796
4469
|
if (sessionFilter && event.session !== sessionFilter) return;
|
|
3797
4470
|
if (channelFilter && event.channel !== channelFilter) return;
|
|
@@ -3873,15 +4546,15 @@ ${user.trimEnd()}
|
|
|
3873
4546
|
if (!full) {
|
|
3874
4547
|
conditions.push(sql2`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
3875
4548
|
}
|
|
3876
|
-
const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(
|
|
4549
|
+
const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
|
|
3877
4550
|
return c.json(rows);
|
|
3878
4551
|
});
|
|
3879
|
-
var minds_default =
|
|
4552
|
+
var minds_default = app11;
|
|
3880
4553
|
|
|
3881
4554
|
// src/web/api/pages.ts
|
|
3882
4555
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
3883
|
-
import { extname, resolve as
|
|
3884
|
-
import { Hono as
|
|
4556
|
+
import { extname, resolve as resolve17 } from "path";
|
|
4557
|
+
import { Hono as Hono12 } from "hono";
|
|
3885
4558
|
var MIME_TYPES = {
|
|
3886
4559
|
".html": "text/html",
|
|
3887
4560
|
".js": "application/javascript",
|
|
@@ -3898,16 +4571,21 @@ var MIME_TYPES = {
|
|
|
3898
4571
|
".txt": "text/plain",
|
|
3899
4572
|
".xml": "application/xml"
|
|
3900
4573
|
};
|
|
3901
|
-
var
|
|
4574
|
+
var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
3902
4575
|
const name = c.req.param("name");
|
|
3903
|
-
|
|
3904
|
-
|
|
4576
|
+
let pagesRoot;
|
|
4577
|
+
if (name === "_system") {
|
|
4578
|
+
pagesRoot = resolve17(voluteHome(), "shared", "pages");
|
|
4579
|
+
} else {
|
|
4580
|
+
if (!findMind(name)) return c.text("Not found", 404);
|
|
4581
|
+
pagesRoot = resolve17(mindDir(name), "home", "pages");
|
|
4582
|
+
}
|
|
3905
4583
|
const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
|
|
3906
|
-
const requestedPath =
|
|
4584
|
+
const requestedPath = resolve17(pagesRoot, wildcard.slice(1));
|
|
3907
4585
|
if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
|
|
3908
4586
|
let fileStat = await stat(requestedPath).catch(() => null);
|
|
3909
4587
|
if (fileStat?.isDirectory()) {
|
|
3910
|
-
const indexPath =
|
|
4588
|
+
const indexPath = resolve17(requestedPath, "index.html");
|
|
3911
4589
|
fileStat = await stat(indexPath).catch(() => null);
|
|
3912
4590
|
if (fileStat?.isFile()) {
|
|
3913
4591
|
const body = await readFile2(indexPath);
|
|
@@ -3923,14 +4601,14 @@ var app10 = new Hono10().get("/:name/*", async (c) => {
|
|
|
3923
4601
|
}
|
|
3924
4602
|
return c.text("Not found", 404);
|
|
3925
4603
|
});
|
|
3926
|
-
var pages_default =
|
|
4604
|
+
var pages_default = app12;
|
|
3927
4605
|
|
|
3928
4606
|
// src/web/api/prompts.ts
|
|
3929
4607
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
3930
4608
|
import { eq as eq5, sql as sql3 } from "drizzle-orm";
|
|
3931
|
-
import { Hono as
|
|
4609
|
+
import { Hono as Hono13 } from "hono";
|
|
3932
4610
|
import { z as z4 } from "zod";
|
|
3933
|
-
var
|
|
4611
|
+
var app13 = new Hono13().get("/", async (c) => {
|
|
3934
4612
|
let rows;
|
|
3935
4613
|
try {
|
|
3936
4614
|
const db = await getDb();
|
|
@@ -3974,10 +4652,12 @@ var app11 = new Hono11().get("/", async (c) => {
|
|
|
3974
4652
|
await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
|
|
3975
4653
|
return c.json({ ok: true });
|
|
3976
4654
|
});
|
|
3977
|
-
var prompts_default =
|
|
4655
|
+
var prompts_default = app13;
|
|
3978
4656
|
|
|
3979
4657
|
// src/web/api/schedules.ts
|
|
3980
|
-
import {
|
|
4658
|
+
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
4659
|
+
import { Hono as Hono14 } from "hono";
|
|
4660
|
+
var slog2 = logger_default.child("schedules");
|
|
3981
4661
|
function readSchedules(name) {
|
|
3982
4662
|
return readVoluteConfig(mindDir(name))?.schedules ?? [];
|
|
3983
4663
|
}
|
|
@@ -3988,7 +4668,7 @@ function writeSchedules(name, schedules) {
|
|
|
3988
4668
|
writeVoluteConfig(dir, config);
|
|
3989
4669
|
getScheduler().loadSchedules(name);
|
|
3990
4670
|
}
|
|
3991
|
-
var
|
|
4671
|
+
var app14 = new Hono14().get("/:name/schedules", (c) => {
|
|
3992
4672
|
const name = c.req.param("name");
|
|
3993
4673
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
3994
4674
|
return c.json(readSchedules(name));
|
|
@@ -3999,15 +4679,29 @@ var app12 = new Hono12().get("/:name/schedules", (c) => {
|
|
|
3999
4679
|
if (entry.stage === "seed")
|
|
4000
4680
|
return c.json({ error: "Seed minds cannot use schedules \u2014 sprout first" }, 403);
|
|
4001
4681
|
const body = await c.req.json();
|
|
4002
|
-
if (!body.cron
|
|
4003
|
-
return c.json({ error: "cron
|
|
4682
|
+
if (!body.cron) {
|
|
4683
|
+
return c.json({ error: "cron is required" }, 400);
|
|
4684
|
+
}
|
|
4685
|
+
if (!body.message && !body.script) {
|
|
4686
|
+
return c.json({ error: "message or script is required" }, 400);
|
|
4687
|
+
}
|
|
4688
|
+
if (body.message && body.script) {
|
|
4689
|
+
return c.json({ error: "message and script are mutually exclusive" }, 400);
|
|
4690
|
+
}
|
|
4691
|
+
try {
|
|
4692
|
+
CronExpressionParser2.parse(body.cron);
|
|
4693
|
+
} catch {
|
|
4694
|
+
return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
|
|
4004
4695
|
}
|
|
4005
4696
|
const schedules = readSchedules(name);
|
|
4006
4697
|
const id = body.id || `schedule-${Date.now()}`;
|
|
4007
4698
|
if (schedules.some((s) => s.id === id)) {
|
|
4008
4699
|
return c.json({ error: `Schedule "${id}" already exists` }, 409);
|
|
4009
4700
|
}
|
|
4010
|
-
|
|
4701
|
+
const schedule = { id, cron: body.cron, enabled: body.enabled ?? true };
|
|
4702
|
+
if (body.message) schedule.message = body.message;
|
|
4703
|
+
if (body.script) schedule.script = body.script;
|
|
4704
|
+
schedules.push(schedule);
|
|
4011
4705
|
writeSchedules(name, schedules);
|
|
4012
4706
|
return c.json({ ok: true, id }, 201);
|
|
4013
4707
|
}).put("/:name/schedules/:id", requireAdmin, async (c) => {
|
|
@@ -4018,8 +4712,25 @@ var app12 = new Hono12().get("/:name/schedules", (c) => {
|
|
|
4018
4712
|
const idx = schedules.findIndex((s) => s.id === id);
|
|
4019
4713
|
if (idx === -1) return c.json({ error: "Schedule not found" }, 404);
|
|
4020
4714
|
const body = await c.req.json();
|
|
4021
|
-
if (body.
|
|
4022
|
-
|
|
4715
|
+
if (body.message && body.script) {
|
|
4716
|
+
return c.json({ error: "message and script are mutually exclusive" }, 400);
|
|
4717
|
+
}
|
|
4718
|
+
if (body.cron !== void 0) {
|
|
4719
|
+
try {
|
|
4720
|
+
CronExpressionParser2.parse(body.cron);
|
|
4721
|
+
} catch {
|
|
4722
|
+
return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
|
|
4723
|
+
}
|
|
4724
|
+
schedules[idx].cron = body.cron;
|
|
4725
|
+
}
|
|
4726
|
+
if (body.message !== void 0) {
|
|
4727
|
+
schedules[idx].message = body.message;
|
|
4728
|
+
delete schedules[idx].script;
|
|
4729
|
+
}
|
|
4730
|
+
if (body.script !== void 0) {
|
|
4731
|
+
schedules[idx].script = body.script;
|
|
4732
|
+
delete schedules[idx].message;
|
|
4733
|
+
}
|
|
4023
4734
|
if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
|
|
4024
4735
|
writeSchedules(name, schedules);
|
|
4025
4736
|
return c.json({ ok: true });
|
|
@@ -4055,15 +4766,16 @@ var app12 = new Hono12().get("/:name/schedules", (c) => {
|
|
|
4055
4766
|
return c.json({ error: `Mind responded with ${res.status}` }, 502);
|
|
4056
4767
|
}
|
|
4057
4768
|
return c.json({ ok: true });
|
|
4058
|
-
} catch {
|
|
4769
|
+
} catch (err) {
|
|
4770
|
+
slog2.warn(`webhook delivery failed for ${name}`, logger_default.errorData(err));
|
|
4059
4771
|
return c.json({ error: "Failed to reach mind" }, 502);
|
|
4060
4772
|
}
|
|
4061
4773
|
});
|
|
4062
|
-
var schedules_default =
|
|
4774
|
+
var schedules_default = app14;
|
|
4063
4775
|
|
|
4064
4776
|
// src/web/api/shared.ts
|
|
4065
|
-
import { Hono as
|
|
4066
|
-
var
|
|
4777
|
+
import { Hono as Hono15 } from "hono";
|
|
4778
|
+
var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) => {
|
|
4067
4779
|
const name = c.req.param("name");
|
|
4068
4780
|
const entry = findMind(name);
|
|
4069
4781
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4112,22 +4824,22 @@ var app13 = new Hono13().post("/:name/shared/merge", requireAdmin, async (c) =>
|
|
|
4112
4824
|
return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
|
|
4113
4825
|
}
|
|
4114
4826
|
});
|
|
4115
|
-
var shared_default =
|
|
4827
|
+
var shared_default = app15;
|
|
4116
4828
|
|
|
4117
4829
|
// src/web/api/skills.ts
|
|
4118
|
-
import { existsSync as
|
|
4830
|
+
import { existsSync as existsSync13, mkdtempSync, readdirSync as readdirSync7, rmSync as rmSync4 } from "fs";
|
|
4119
4831
|
import { tmpdir as tmpdir2 } from "os";
|
|
4120
|
-
import { join as
|
|
4832
|
+
import { join as join4, resolve as resolve18 } from "path";
|
|
4121
4833
|
import AdmZip from "adm-zip";
|
|
4122
|
-
import { Hono as
|
|
4123
|
-
var
|
|
4834
|
+
import { Hono as Hono16 } from "hono";
|
|
4835
|
+
var app16 = new Hono16().get("/", async (c) => {
|
|
4124
4836
|
const skills = await listSharedSkills();
|
|
4125
4837
|
return c.json(skills);
|
|
4126
4838
|
}).get("/:id", async (c) => {
|
|
4127
4839
|
const id = c.req.param("id");
|
|
4128
4840
|
const skill = await getSharedSkill(id);
|
|
4129
4841
|
if (!skill) return c.json({ error: "Skill not found" }, 404);
|
|
4130
|
-
const dir =
|
|
4842
|
+
const dir = join4(sharedSkillsDir(), id);
|
|
4131
4843
|
const files = listFilesRecursive(dir);
|
|
4132
4844
|
return c.json({ ...skill, files });
|
|
4133
4845
|
}).post("/upload", requireAdmin, async (c) => {
|
|
@@ -4140,24 +4852,24 @@ var app14 = new Hono14().get("/", async (c) => {
|
|
|
4140
4852
|
return c.json({ error: "Only .zip files are accepted" }, 400);
|
|
4141
4853
|
}
|
|
4142
4854
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
4143
|
-
const tmpDir = mkdtempSync(
|
|
4855
|
+
const tmpDir = mkdtempSync(join4(tmpdir2(), "volute-skill-upload-"));
|
|
4144
4856
|
try {
|
|
4145
4857
|
const zip = new AdmZip(buffer);
|
|
4146
4858
|
for (const entry of zip.getEntries()) {
|
|
4147
|
-
const target =
|
|
4859
|
+
const target = resolve18(tmpDir, entry.entryName);
|
|
4148
4860
|
if (!target.startsWith(tmpDir)) {
|
|
4149
4861
|
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
4150
4862
|
}
|
|
4151
4863
|
}
|
|
4152
4864
|
zip.extractAllTo(tmpDir, true);
|
|
4153
4865
|
let skillDir = null;
|
|
4154
|
-
if (
|
|
4866
|
+
if (existsSync13(join4(tmpDir, "SKILL.md"))) {
|
|
4155
4867
|
skillDir = tmpDir;
|
|
4156
4868
|
} else {
|
|
4157
|
-
const entries =
|
|
4869
|
+
const entries = readdirSync7(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4158
4870
|
for (const entry of entries) {
|
|
4159
|
-
if (
|
|
4160
|
-
skillDir =
|
|
4871
|
+
if (existsSync13(join4(tmpDir, entry.name, "SKILL.md"))) {
|
|
4872
|
+
skillDir = join4(tmpDir, entry.name);
|
|
4161
4873
|
break;
|
|
4162
4874
|
}
|
|
4163
4875
|
}
|
|
@@ -4173,7 +4885,7 @@ var app14 = new Hono14().get("/", async (c) => {
|
|
|
4173
4885
|
}
|
|
4174
4886
|
throw e;
|
|
4175
4887
|
} finally {
|
|
4176
|
-
|
|
4888
|
+
rmSync4(tmpDir, { recursive: true, force: true });
|
|
4177
4889
|
}
|
|
4178
4890
|
}).delete("/:id", requireAdmin, async (c) => {
|
|
4179
4891
|
const id = c.req.param("id");
|
|
@@ -4185,12 +4897,12 @@ var app14 = new Hono14().get("/", async (c) => {
|
|
|
4185
4897
|
}
|
|
4186
4898
|
return c.json({ ok: true });
|
|
4187
4899
|
});
|
|
4188
|
-
var skills_default =
|
|
4900
|
+
var skills_default = app16;
|
|
4189
4901
|
|
|
4190
4902
|
// src/web/api/system.ts
|
|
4191
|
-
import { Hono as
|
|
4192
|
-
import { streamSSE as
|
|
4193
|
-
var
|
|
4903
|
+
import { Hono as Hono17 } from "hono";
|
|
4904
|
+
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
4905
|
+
var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
|
|
4194
4906
|
setTimeout(() => process.exit(1), 200);
|
|
4195
4907
|
return c.json({ ok: true });
|
|
4196
4908
|
}).post("/stop", requireAdmin, (c) => {
|
|
@@ -4199,7 +4911,7 @@ var app15 = new Hono15().post("/restart", requireAdmin, (c) => {
|
|
|
4199
4911
|
}).get("/logs", async (c) => {
|
|
4200
4912
|
const user = c.get("user");
|
|
4201
4913
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
4202
|
-
return
|
|
4914
|
+
return streamSSE3(c, async (stream) => {
|
|
4203
4915
|
for (const entry of logBuffer.getEntries()) {
|
|
4204
4916
|
await stream.writeSSE({ data: JSON.stringify(entry) });
|
|
4205
4917
|
}
|
|
@@ -4207,10 +4919,10 @@ var app15 = new Hono15().post("/restart", requireAdmin, (c) => {
|
|
|
4207
4919
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
4208
4920
|
});
|
|
4209
4921
|
});
|
|
4210
|
-
await new Promise((
|
|
4922
|
+
await new Promise((resolve23) => {
|
|
4211
4923
|
stream.onAbort(() => {
|
|
4212
4924
|
unsubscribe();
|
|
4213
|
-
|
|
4925
|
+
resolve23();
|
|
4214
4926
|
});
|
|
4215
4927
|
});
|
|
4216
4928
|
});
|
|
@@ -4218,18 +4930,18 @@ var app15 = new Hono15().post("/restart", requireAdmin, (c) => {
|
|
|
4218
4930
|
const config = readSystemsConfig();
|
|
4219
4931
|
return c.json({ system: config?.system ?? null });
|
|
4220
4932
|
});
|
|
4221
|
-
var system_default =
|
|
4933
|
+
var system_default = app17;
|
|
4222
4934
|
|
|
4223
4935
|
// src/web/api/typing.ts
|
|
4224
4936
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
4225
|
-
import { Hono as
|
|
4937
|
+
import { Hono as Hono18 } from "hono";
|
|
4226
4938
|
import { z as z5 } from "zod";
|
|
4227
4939
|
var typingSchema = z5.object({
|
|
4228
4940
|
channel: z5.string().min(1),
|
|
4229
4941
|
sender: z5.string().min(1),
|
|
4230
4942
|
active: z5.boolean()
|
|
4231
4943
|
});
|
|
4232
|
-
var
|
|
4944
|
+
var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema), (c) => {
|
|
4233
4945
|
const { channel, sender, active } = c.req.valid("json");
|
|
4234
4946
|
const map = getTypingMap();
|
|
4235
4947
|
if (active) {
|
|
@@ -4237,6 +4949,11 @@ var app16 = new Hono16().post("/:name/typing", zValidator5("json", typingSchema)
|
|
|
4237
4949
|
} else {
|
|
4238
4950
|
map.delete(channel, sender);
|
|
4239
4951
|
}
|
|
4952
|
+
const volutePrefix = "volute:";
|
|
4953
|
+
if (channel.startsWith(volutePrefix)) {
|
|
4954
|
+
const conversationId = channel.slice(volutePrefix.length);
|
|
4955
|
+
publish(conversationId, { type: "typing", senders: map.get(channel) });
|
|
4956
|
+
}
|
|
4240
4957
|
return c.json({ ok: true });
|
|
4241
4958
|
}).get("/:name/typing", (c) => {
|
|
4242
4959
|
const channel = c.req.query("channel");
|
|
@@ -4246,13 +4963,13 @@ var app16 = new Hono16().post("/:name/typing", zValidator5("json", typingSchema)
|
|
|
4246
4963
|
const map = getTypingMap();
|
|
4247
4964
|
return c.json({ typing: map.get(channel) });
|
|
4248
4965
|
});
|
|
4249
|
-
var typing_default =
|
|
4966
|
+
var typing_default = app18;
|
|
4250
4967
|
|
|
4251
4968
|
// src/web/api/update.ts
|
|
4252
4969
|
import { spawn as spawn3 } from "child_process";
|
|
4253
|
-
import { Hono as
|
|
4970
|
+
import { Hono as Hono19 } from "hono";
|
|
4254
4971
|
var bin;
|
|
4255
|
-
var
|
|
4972
|
+
var app19 = new Hono19().get("/update", async (c) => {
|
|
4256
4973
|
const result = await checkForUpdate();
|
|
4257
4974
|
return c.json(result);
|
|
4258
4975
|
}).post("/update", requireAdmin, async (c) => {
|
|
@@ -4267,19 +4984,19 @@ var app17 = new Hono17().get("/update", async (c) => {
|
|
|
4267
4984
|
child.unref();
|
|
4268
4985
|
return c.json({ ok: true, message: "Updating..." });
|
|
4269
4986
|
});
|
|
4270
|
-
var update_default =
|
|
4987
|
+
var update_default = app19;
|
|
4271
4988
|
|
|
4272
4989
|
// src/web/api/variants.ts
|
|
4273
|
-
import { existsSync as
|
|
4274
|
-
import { resolve as
|
|
4275
|
-
import { Hono as
|
|
4990
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
4991
|
+
import { resolve as resolve20 } from "path";
|
|
4992
|
+
import { Hono as Hono20 } from "hono";
|
|
4276
4993
|
|
|
4277
4994
|
// src/lib/spawn-server.ts
|
|
4278
4995
|
import { spawn as spawn4 } from "child_process";
|
|
4279
|
-
import { closeSync, mkdirSync as
|
|
4280
|
-
import { resolve as
|
|
4996
|
+
import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync12 } from "fs";
|
|
4997
|
+
import { resolve as resolve19 } from "path";
|
|
4281
4998
|
function tsxBin(cwd) {
|
|
4282
|
-
return
|
|
4999
|
+
return resolve19(cwd, "node_modules", ".bin", "tsx");
|
|
4283
5000
|
}
|
|
4284
5001
|
function spawnServer(cwd, port, options) {
|
|
4285
5002
|
if (options?.detached) {
|
|
@@ -4292,31 +5009,31 @@ function spawnAttached(cwd, port) {
|
|
|
4292
5009
|
cwd,
|
|
4293
5010
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4294
5011
|
});
|
|
4295
|
-
return new Promise((
|
|
4296
|
-
const timeout = setTimeout(() =>
|
|
5012
|
+
return new Promise((resolve23) => {
|
|
5013
|
+
const timeout = setTimeout(() => resolve23(null), 3e4);
|
|
4297
5014
|
function checkOutput(data) {
|
|
4298
5015
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
4299
5016
|
if (match) {
|
|
4300
5017
|
clearTimeout(timeout);
|
|
4301
|
-
|
|
5018
|
+
resolve23({ child, actualPort: parseInt(match[1], 10) });
|
|
4302
5019
|
}
|
|
4303
5020
|
}
|
|
4304
5021
|
child.stdout?.on("data", checkOutput);
|
|
4305
5022
|
child.stderr?.on("data", checkOutput);
|
|
4306
5023
|
child.on("error", () => {
|
|
4307
5024
|
clearTimeout(timeout);
|
|
4308
|
-
|
|
5025
|
+
resolve23(null);
|
|
4309
5026
|
});
|
|
4310
5027
|
child.on("exit", () => {
|
|
4311
5028
|
clearTimeout(timeout);
|
|
4312
|
-
|
|
5029
|
+
resolve23(null);
|
|
4313
5030
|
});
|
|
4314
5031
|
});
|
|
4315
5032
|
}
|
|
4316
5033
|
function spawnDetached(cwd, port, logDir) {
|
|
4317
|
-
const logsDir = logDir ??
|
|
4318
|
-
|
|
4319
|
-
const logPath =
|
|
5034
|
+
const logsDir = logDir ?? resolve19(cwd, ".mind", "logs");
|
|
5035
|
+
mkdirSync9(logsDir, { recursive: true });
|
|
5036
|
+
const logPath = resolve19(logsDir, "mind.log");
|
|
4320
5037
|
const logFd = openSync(logPath, "a");
|
|
4321
5038
|
const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
4322
5039
|
cwd,
|
|
@@ -4336,7 +5053,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
4336
5053
|
}
|
|
4337
5054
|
const interval = setInterval(() => {
|
|
4338
5055
|
try {
|
|
4339
|
-
const content =
|
|
5056
|
+
const content = readFileSync12(logPath, "utf-8");
|
|
4340
5057
|
const match = content.match(/listening on :(\d+)/);
|
|
4341
5058
|
if (match) {
|
|
4342
5059
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -4388,7 +5105,7 @@ async function verify2(port) {
|
|
|
4388
5105
|
}
|
|
4389
5106
|
|
|
4390
5107
|
// src/web/api/variants.ts
|
|
4391
|
-
var
|
|
5108
|
+
var app20 = new Hono20().get("/:name/variants", async (c) => {
|
|
4392
5109
|
const name = c.req.param("name");
|
|
4393
5110
|
const entry = findMind(name);
|
|
4394
5111
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4418,11 +5135,11 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4418
5135
|
const err = validateBranchName(variantName);
|
|
4419
5136
|
if (err) return c.json({ error: err }, 400);
|
|
4420
5137
|
const projectRoot = mindDir(mindName);
|
|
4421
|
-
const variantDir =
|
|
4422
|
-
if (
|
|
5138
|
+
const variantDir = resolve20(projectRoot, ".variants", variantName);
|
|
5139
|
+
if (existsSync14(variantDir)) {
|
|
4423
5140
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
4424
5141
|
}
|
|
4425
|
-
|
|
5142
|
+
mkdirSync10(resolve20(projectRoot, ".variants"), { recursive: true });
|
|
4426
5143
|
try {
|
|
4427
5144
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
4428
5145
|
} catch (e) {
|
|
@@ -4435,7 +5152,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4435
5152
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
4436
5153
|
await exec(cmd, args, {
|
|
4437
5154
|
cwd: variantDir,
|
|
4438
|
-
env: { ...process.env, HOME:
|
|
5155
|
+
env: { ...process.env, HOME: resolve20(variantDir, "home") }
|
|
4439
5156
|
});
|
|
4440
5157
|
} else {
|
|
4441
5158
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -4445,7 +5162,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4445
5162
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
4446
5163
|
}
|
|
4447
5164
|
if (body.soul) {
|
|
4448
|
-
|
|
5165
|
+
writeFileSync10(resolve20(variantDir, "home/SOUL.md"), body.soul);
|
|
4449
5166
|
}
|
|
4450
5167
|
const variantPort = body.port ?? nextPort();
|
|
4451
5168
|
const variant = {
|
|
@@ -4483,7 +5200,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4483
5200
|
} catch {
|
|
4484
5201
|
}
|
|
4485
5202
|
const projectRoot = mindDir(mindName);
|
|
4486
|
-
if (
|
|
5203
|
+
if (existsSync14(variant.path)) {
|
|
4487
5204
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
4488
5205
|
if (status) {
|
|
4489
5206
|
try {
|
|
@@ -4540,7 +5257,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4540
5257
|
} catch (e) {
|
|
4541
5258
|
return c.json({ error: "Merge failed. Resolve conflicts manually." }, 500);
|
|
4542
5259
|
}
|
|
4543
|
-
if (
|
|
5260
|
+
if (existsSync14(variant.path)) {
|
|
4544
5261
|
try {
|
|
4545
5262
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
4546
5263
|
} catch {
|
|
@@ -4557,7 +5274,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4557
5274
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
4558
5275
|
await exec(cmd, args, {
|
|
4559
5276
|
cwd: projectRoot,
|
|
4560
|
-
env: { ...process.env, HOME:
|
|
5277
|
+
env: { ...process.env, HOME: resolve20(projectRoot, "home") }
|
|
4561
5278
|
});
|
|
4562
5279
|
} else {
|
|
4563
5280
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -4600,7 +5317,7 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4600
5317
|
} catch {
|
|
4601
5318
|
}
|
|
4602
5319
|
}
|
|
4603
|
-
if (
|
|
5320
|
+
if (existsSync14(variant.path)) {
|
|
4604
5321
|
try {
|
|
4605
5322
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
4606
5323
|
} catch {
|
|
@@ -4614,11 +5331,11 @@ var app18 = new Hono18().get("/:name/variants", async (c) => {
|
|
|
4614
5331
|
chownMindDir(projectRoot, mindName);
|
|
4615
5332
|
return c.json({ ok: true });
|
|
4616
5333
|
});
|
|
4617
|
-
var variants_default =
|
|
5334
|
+
var variants_default = app20;
|
|
4618
5335
|
|
|
4619
5336
|
// src/web/api/volute/channels.ts
|
|
4620
5337
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
4621
|
-
import { Hono as
|
|
5338
|
+
import { Hono as Hono21 } from "hono";
|
|
4622
5339
|
import { z as z6 } from "zod";
|
|
4623
5340
|
var createSchema = z6.object({
|
|
4624
5341
|
name: z6.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
|
|
@@ -4626,7 +5343,7 @@ var createSchema = z6.object({
|
|
|
4626
5343
|
var inviteSchema = z6.object({
|
|
4627
5344
|
username: z6.string().min(1)
|
|
4628
5345
|
});
|
|
4629
|
-
var
|
|
5346
|
+
var app21 = new Hono21().get("/", async (c) => {
|
|
4630
5347
|
const user = c.get("user");
|
|
4631
5348
|
const channels = await listChannels();
|
|
4632
5349
|
const results = await Promise.all(
|
|
@@ -4690,12 +5407,12 @@ var app19 = new Hono19().get("/", async (c) => {
|
|
|
4690
5407
|
]);
|
|
4691
5408
|
return c.json({ ok: true });
|
|
4692
5409
|
});
|
|
4693
|
-
var channels_default2 =
|
|
5410
|
+
var channels_default2 = app21;
|
|
4694
5411
|
|
|
4695
5412
|
// src/web/api/volute/chat.ts
|
|
4696
5413
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
4697
|
-
import { Hono as
|
|
4698
|
-
import { streamSSE as
|
|
5414
|
+
import { Hono as Hono22 } from "hono";
|
|
5415
|
+
import { streamSSE as streamSSE4 } from "hono/streaming";
|
|
4699
5416
|
import { z as z7 } from "zod";
|
|
4700
5417
|
async function fanOutToMinds(opts) {
|
|
4701
5418
|
const participants = await getParticipants(opts.conversationId);
|
|
@@ -4703,7 +5420,7 @@ async function fanOutToMinds(opts) {
|
|
|
4703
5420
|
const participantNames = participants.map((p) => p.username);
|
|
4704
5421
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
4705
5422
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
4706
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
5423
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-3DMYKZPB.js");
|
|
4707
5424
|
const manager = getMindManager2();
|
|
4708
5425
|
const runningMinds = mindParticipants.map((ap) => {
|
|
4709
5426
|
const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
|
|
@@ -4735,7 +5452,7 @@ async function fanOutToMinds(opts) {
|
|
|
4735
5452
|
const target = opts.targetName ? opts.targetName(mindName) : mindName;
|
|
4736
5453
|
const channel = slugForMind(mindName);
|
|
4737
5454
|
const typingMap = getTypingMap();
|
|
4738
|
-
const currentlyTyping = typingMap.get(channel);
|
|
5455
|
+
const currentlyTyping = typingMap.get(channel).filter((name) => participantNames.includes(name));
|
|
4739
5456
|
deliverMessage(target, {
|
|
4740
5457
|
content: opts.contentBlocks,
|
|
4741
5458
|
channel,
|
|
@@ -4760,7 +5477,7 @@ var chatSchema = z7.object({
|
|
|
4760
5477
|
})
|
|
4761
5478
|
).optional()
|
|
4762
5479
|
});
|
|
4763
|
-
var
|
|
5480
|
+
var app22 = new Hono22().post("/:name/chat", zValidator7("json", chatSchema), async (c) => {
|
|
4764
5481
|
const name = c.req.param("name");
|
|
4765
5482
|
const [baseName] = name.split("@", 2);
|
|
4766
5483
|
const entry = findMind(baseName);
|
|
@@ -4832,7 +5549,7 @@ var app20 = new Hono20().post("/:name/chat", zValidator7("json", chatSchema), as
|
|
|
4832
5549
|
if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
|
|
4833
5550
|
return c.json({ error: "Conversation not found" }, 404);
|
|
4834
5551
|
}
|
|
4835
|
-
return
|
|
5552
|
+
return streamSSE4(c, async (stream) => {
|
|
4836
5553
|
const unsubscribe = subscribe(conversationId, (event) => {
|
|
4837
5554
|
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
4838
5555
|
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
@@ -4843,11 +5560,11 @@ var app20 = new Hono20().post("/:name/chat", zValidator7("json", chatSchema), as
|
|
|
4843
5560
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
4844
5561
|
});
|
|
4845
5562
|
}, 15e3);
|
|
4846
|
-
await new Promise((
|
|
5563
|
+
await new Promise((resolve23) => {
|
|
4847
5564
|
stream.onAbort(() => {
|
|
4848
5565
|
unsubscribe();
|
|
4849
5566
|
clearInterval(keepAlive);
|
|
4850
|
-
|
|
5567
|
+
resolve23();
|
|
4851
5568
|
});
|
|
4852
5569
|
});
|
|
4853
5570
|
});
|
|
@@ -4857,7 +5574,7 @@ var unifiedChatSchema = z7.object({
|
|
|
4857
5574
|
conversationId: z7.string(),
|
|
4858
5575
|
images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
|
|
4859
5576
|
});
|
|
4860
|
-
var unifiedChatApp = new
|
|
5577
|
+
var unifiedChatApp = new Hono22().post(
|
|
4861
5578
|
"/chat",
|
|
4862
5579
|
zValidator7("json", unifiedChatSchema),
|
|
4863
5580
|
async (c) => {
|
|
@@ -4893,18 +5610,18 @@ var unifiedChatApp = new Hono20().post(
|
|
|
4893
5610
|
return c.json({ ok: true, conversationId: body.conversationId });
|
|
4894
5611
|
}
|
|
4895
5612
|
);
|
|
4896
|
-
var chat_default =
|
|
5613
|
+
var chat_default = app22;
|
|
4897
5614
|
|
|
4898
5615
|
// src/web/api/volute/conversations.ts
|
|
4899
5616
|
import { zValidator as zValidator8 } from "@hono/zod-validator";
|
|
4900
|
-
import { Hono as
|
|
5617
|
+
import { Hono as Hono23 } from "hono";
|
|
4901
5618
|
import { z as z8 } from "zod";
|
|
4902
5619
|
var createConvSchema = z8.object({
|
|
4903
5620
|
title: z8.string().optional(),
|
|
4904
5621
|
participantIds: z8.array(z8.number()).optional(),
|
|
4905
5622
|
participantNames: z8.array(z8.string()).optional()
|
|
4906
5623
|
});
|
|
4907
|
-
var
|
|
5624
|
+
var app23 = new Hono23().get("/:name/conversations", async (c) => {
|
|
4908
5625
|
const name = c.req.param("name");
|
|
4909
5626
|
const user = c.get("user");
|
|
4910
5627
|
let lookupId = user.id;
|
|
@@ -4989,18 +5706,18 @@ var app21 = new Hono21().get("/:name/conversations", async (c) => {
|
|
|
4989
5706
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
4990
5707
|
return c.json({ ok: true });
|
|
4991
5708
|
});
|
|
4992
|
-
var conversations_default =
|
|
5709
|
+
var conversations_default = app23;
|
|
4993
5710
|
|
|
4994
5711
|
// src/web/api/volute/user-conversations.ts
|
|
4995
5712
|
import { zValidator as zValidator9 } from "@hono/zod-validator";
|
|
4996
|
-
import { Hono as
|
|
4997
|
-
import { streamSSE as
|
|
5713
|
+
import { Hono as Hono24 } from "hono";
|
|
5714
|
+
import { streamSSE as streamSSE5 } from "hono/streaming";
|
|
4998
5715
|
import { z as z9 } from "zod";
|
|
4999
5716
|
var createSchema2 = z9.object({
|
|
5000
5717
|
title: z9.string().optional(),
|
|
5001
5718
|
participantNames: z9.array(z9.string()).min(1)
|
|
5002
5719
|
});
|
|
5003
|
-
var
|
|
5720
|
+
var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
|
|
5004
5721
|
const user = c.get("user");
|
|
5005
5722
|
const convs = await listConversationsWithParticipants(user.id);
|
|
5006
5723
|
return c.json(convs);
|
|
@@ -5048,7 +5765,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5048
5765
|
if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
|
|
5049
5766
|
return c.json({ error: "Conversation not found" }, 404);
|
|
5050
5767
|
}
|
|
5051
|
-
return
|
|
5768
|
+
return streamSSE5(c, async (stream) => {
|
|
5052
5769
|
const unsubscribe = subscribe(conversationId, (event) => {
|
|
5053
5770
|
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
5054
5771
|
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
@@ -5059,11 +5776,11 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5059
5776
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5060
5777
|
});
|
|
5061
5778
|
}, 15e3);
|
|
5062
|
-
await new Promise((
|
|
5779
|
+
await new Promise((resolve23) => {
|
|
5063
5780
|
stream.onAbort(() => {
|
|
5064
5781
|
unsubscribe();
|
|
5065
5782
|
clearInterval(keepAlive);
|
|
5066
|
-
|
|
5783
|
+
resolve23();
|
|
5067
5784
|
});
|
|
5068
5785
|
});
|
|
5069
5786
|
});
|
|
@@ -5074,12 +5791,12 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5074
5791
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
5075
5792
|
return c.json({ ok: true });
|
|
5076
5793
|
});
|
|
5077
|
-
var user_conversations_default =
|
|
5794
|
+
var user_conversations_default = app24;
|
|
5078
5795
|
|
|
5079
5796
|
// src/web/app.ts
|
|
5080
5797
|
var httpLog = logger_default.child("http");
|
|
5081
|
-
var
|
|
5082
|
-
|
|
5798
|
+
var app25 = new Hono25();
|
|
5799
|
+
app25.onError((err, c) => {
|
|
5083
5800
|
if (err instanceof HTTPException) {
|
|
5084
5801
|
return err.getResponse();
|
|
5085
5802
|
}
|
|
@@ -5090,10 +5807,10 @@ app23.onError((err, c) => {
|
|
|
5090
5807
|
});
|
|
5091
5808
|
return c.json({ error: "Internal server error" }, 500);
|
|
5092
5809
|
});
|
|
5093
|
-
|
|
5810
|
+
app25.notFound((c) => {
|
|
5094
5811
|
return c.json({ error: "Not found" }, 404);
|
|
5095
5812
|
});
|
|
5096
|
-
|
|
5813
|
+
app25.use("*", async (c, next) => {
|
|
5097
5814
|
const start = Date.now();
|
|
5098
5815
|
await next();
|
|
5099
5816
|
const duration = Date.now() - start;
|
|
@@ -5104,7 +5821,7 @@ app23.use("*", async (c, next) => {
|
|
|
5104
5821
|
httpLog.debug("request", data);
|
|
5105
5822
|
}
|
|
5106
5823
|
});
|
|
5107
|
-
|
|
5824
|
+
app25.get("/api/health", (c) => {
|
|
5108
5825
|
let version = "unknown";
|
|
5109
5826
|
let cached = null;
|
|
5110
5827
|
try {
|
|
@@ -5119,18 +5836,19 @@ app23.get("/api/health", (c) => {
|
|
|
5119
5836
|
...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
|
|
5120
5837
|
});
|
|
5121
5838
|
});
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
var
|
|
5839
|
+
app25.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
|
|
5840
|
+
app25.use("/api/*", csrf());
|
|
5841
|
+
app25.use("/api/activity/*", authMiddleware);
|
|
5842
|
+
app25.use("/api/minds/*", authMiddleware);
|
|
5843
|
+
app25.use("/api/conversations/*", authMiddleware);
|
|
5844
|
+
app25.use("/api/volute/*", authMiddleware);
|
|
5845
|
+
app25.use("/api/system/*", authMiddleware);
|
|
5846
|
+
app25.use("/api/env/*", authMiddleware);
|
|
5847
|
+
app25.use("/api/prompts/*", authMiddleware);
|
|
5848
|
+
app25.use("/api/skills/*", authMiddleware);
|
|
5849
|
+
app25.route("/pages", pages_default);
|
|
5850
|
+
var routes = app25.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default).route("/api/minds", connectors_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp);
|
|
5851
|
+
var app_default = app25;
|
|
5134
5852
|
|
|
5135
5853
|
// src/web/server.ts
|
|
5136
5854
|
var MIME_TYPES2 = {
|
|
@@ -5149,8 +5867,8 @@ async function startServer({
|
|
|
5149
5867
|
let assetsDir = "";
|
|
5150
5868
|
let searchDir = dirname3(new URL(import.meta.url).pathname);
|
|
5151
5869
|
for (let i = 0; i < 5; i++) {
|
|
5152
|
-
const candidate =
|
|
5153
|
-
if (
|
|
5870
|
+
const candidate = resolve21(searchDir, "dist", "web-assets");
|
|
5871
|
+
if (existsSync15(candidate)) {
|
|
5154
5872
|
assetsDir = candidate;
|
|
5155
5873
|
break;
|
|
5156
5874
|
}
|
|
@@ -5160,7 +5878,7 @@ async function startServer({
|
|
|
5160
5878
|
app_default.get("*", async (c) => {
|
|
5161
5879
|
const urlPath = new URL(c.req.url).pathname;
|
|
5162
5880
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
5163
|
-
const filePath =
|
|
5881
|
+
const filePath = resolve21(assetsDir, urlPath.slice(1));
|
|
5164
5882
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
5165
5883
|
const s = await stat2(filePath).catch(() => null);
|
|
5166
5884
|
if (s?.isFile()) {
|
|
@@ -5169,7 +5887,7 @@ async function startServer({
|
|
|
5169
5887
|
const body = await readFile3(filePath);
|
|
5170
5888
|
return c.body(body, 200, { "Content-Type": mime });
|
|
5171
5889
|
}
|
|
5172
|
-
const indexPath =
|
|
5890
|
+
const indexPath = resolve21(assetsDir, "index.html");
|
|
5173
5891
|
const indexStat = await stat2(indexPath).catch(() => null);
|
|
5174
5892
|
if (indexStat?.isFile()) {
|
|
5175
5893
|
const body = await readFile3(indexPath, "utf-8");
|
|
@@ -5179,10 +5897,10 @@ async function startServer({
|
|
|
5179
5897
|
});
|
|
5180
5898
|
}
|
|
5181
5899
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
5182
|
-
await new Promise((
|
|
5900
|
+
await new Promise((resolve23, reject) => {
|
|
5183
5901
|
server.on("listening", () => {
|
|
5184
5902
|
logger_default.info("Volute UI running", { hostname, port });
|
|
5185
|
-
|
|
5903
|
+
resolve23();
|
|
5186
5904
|
});
|
|
5187
5905
|
server.on("error", (err) => {
|
|
5188
5906
|
reject(err);
|
|
@@ -5193,14 +5911,14 @@ async function startServer({
|
|
|
5193
5911
|
|
|
5194
5912
|
// src/daemon.ts
|
|
5195
5913
|
if (!process.env.VOLUTE_HOME) {
|
|
5196
|
-
process.env.VOLUTE_HOME =
|
|
5914
|
+
process.env.VOLUTE_HOME = resolve22(homedir2(), ".volute");
|
|
5197
5915
|
}
|
|
5198
5916
|
async function startDaemon(opts) {
|
|
5199
5917
|
const { port, hostname } = opts;
|
|
5200
5918
|
const myPid = String(process.pid);
|
|
5201
5919
|
const home = voluteHome();
|
|
5202
5920
|
if (!opts.foreground) {
|
|
5203
|
-
const rotatingLog = new RotatingLog(
|
|
5921
|
+
const rotatingLog = new RotatingLog(resolve22(home, "daemon.log"));
|
|
5204
5922
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
5205
5923
|
`));
|
|
5206
5924
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -5210,9 +5928,9 @@ async function startDaemon(opts) {
|
|
|
5210
5928
|
console.warn = write;
|
|
5211
5929
|
console.info = write;
|
|
5212
5930
|
}
|
|
5213
|
-
const DAEMON_PID_PATH =
|
|
5214
|
-
const DAEMON_JSON_PATH =
|
|
5215
|
-
|
|
5931
|
+
const DAEMON_PID_PATH = resolve22(home, "daemon.pid");
|
|
5932
|
+
const DAEMON_JSON_PATH = resolve22(home, "daemon.json");
|
|
5933
|
+
mkdirSync11(home, { recursive: true });
|
|
5216
5934
|
migrateAgentsToMinds();
|
|
5217
5935
|
try {
|
|
5218
5936
|
await ensureSharedRepo();
|
|
@@ -5225,7 +5943,7 @@ async function startDaemon(opts) {
|
|
|
5225
5943
|
} catch (err) {
|
|
5226
5944
|
logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
|
|
5227
5945
|
}
|
|
5228
|
-
const token = process.env.VOLUTE_DAEMON_TOKEN ||
|
|
5946
|
+
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
|
|
5229
5947
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
5230
5948
|
process.env.VOLUTE_DAEMON_PORT = String(port);
|
|
5231
5949
|
process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
|
|
@@ -5240,8 +5958,8 @@ async function startDaemon(opts) {
|
|
|
5240
5958
|
}
|
|
5241
5959
|
throw err;
|
|
5242
5960
|
}
|
|
5243
|
-
|
|
5244
|
-
|
|
5961
|
+
writeFileSync11(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
5962
|
+
writeFileSync11(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
|
|
5245
5963
|
`, {
|
|
5246
5964
|
mode: 420
|
|
5247
5965
|
});
|
|
@@ -5305,13 +6023,13 @@ async function startDaemon(opts) {
|
|
|
5305
6023
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
5306
6024
|
function cleanup() {
|
|
5307
6025
|
try {
|
|
5308
|
-
if (
|
|
6026
|
+
if (readFileSync13(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
5309
6027
|
unlinkSync2(DAEMON_PID_PATH);
|
|
5310
6028
|
}
|
|
5311
6029
|
} catch {
|
|
5312
6030
|
}
|
|
5313
6031
|
try {
|
|
5314
|
-
const data = JSON.parse(
|
|
6032
|
+
const data = JSON.parse(readFileSync13(DAEMON_JSON_PATH, "utf-8"));
|
|
5315
6033
|
if (data.token === token) {
|
|
5316
6034
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
5317
6035
|
}
|
|
@@ -5323,6 +6041,8 @@ async function startDaemon(opts) {
|
|
|
5323
6041
|
if (shuttingDown) return;
|
|
5324
6042
|
shuttingDown = true;
|
|
5325
6043
|
logger_default.info("shutting down...");
|
|
6044
|
+
stopAllWatchers();
|
|
6045
|
+
stopAll();
|
|
5326
6046
|
scheduler.stop();
|
|
5327
6047
|
scheduler.saveState();
|
|
5328
6048
|
mailPoller.stop();
|