volute 0.27.0 → 0.29.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 +20 -10
- package/dist/accept-666DIZX2.js +41 -0
- package/dist/api.d.ts +342 -143
- package/dist/{chat-MHJ3L6JQ.js → chat-KTPOR2JT.js} +18 -8
- package/dist/chunk-A6TUJJ3L.js +19 -0
- package/dist/{chunk-OQZH4PBB.js → chunk-CMILSHZD.js} +199 -277
- package/dist/{chunk-K5NAC55T.js → chunk-CQ7SNKNI.js} +1 -1
- package/dist/{chunk-PHSAT7YL.js → chunk-EHZKEMMV.js} +5 -5
- package/dist/{chunk-IAYBDWVG.js → chunk-FLZGS4QH.js} +145 -0
- package/dist/{chunk-USUXRNVD.js → chunk-J4IBNXGJ.js} +0 -2
- package/dist/chunk-MD4C26II.js +128 -0
- package/dist/{chunk-4WXYUOAK.js → chunk-NI5FFCCS.js} +8 -1
- package/dist/{chunk-JKOWNZ4P.js → chunk-P72MVS4R.js} +1 -40
- package/dist/chunk-THUUIU3E.js +232 -0
- package/dist/cli.js +21 -30
- package/dist/clock-DGCBVGYA.js +259 -0
- package/dist/{cloud-sync-T7M3ESC3.js → cloud-sync-KILFGV5Q.js} +7 -7
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-M2K4253F.js → conversations-P5BL7RMX.js} +7 -1
- package/dist/create-DFCAGEE5.js +70 -0
- package/dist/{daemon-restart-M2QTYMEG.js → daemon-restart-UHOMICXT.js} +1 -1
- package/dist/daemon.js +715 -661
- package/dist/files-M546TKVN.js +46 -0
- package/dist/{login-XX37I52P.js → login-BKP3AFWN.js} +7 -17
- package/dist/logout-IQK7FNEK.js +20 -0
- package/dist/{message-delivery-LDXLGERA.js → message-delivery-Q7VUMIEI.js} +11 -9
- package/dist/{mind-DI33C74K.js → mind-S5V6CK5W.js} +8 -13
- package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-WRHFI3YW.js} +1 -1
- package/dist/mind-list-UPJ75GPI.js +29 -0
- package/dist/{mind-manager-M6EMUW5I.js → mind-manager-P66HQDNE.js} +2 -2
- package/dist/mind-status-TK5AETEM.js +55 -0
- package/dist/{package-7WY6VKU3.js → package-OFKXNKJF.js} +1 -1
- package/dist/{pages-6EBS6CBR.js → pages-EUJR52AH.js} +5 -5
- package/dist/pages-watcher-P7QECRE2.js +21 -0
- package/dist/{publish-66UB2ZFY.js → publish-ZZB33WP4.js} +6 -17
- package/dist/{register-6B2CXTYM.js → register-CHREOMJ3.js} +5 -24
- package/dist/reject-LXIZFJ4Q.js +39 -0
- package/dist/{sandbox-TGBX22DS.js → sandbox-5BW5HPXM.js} +1 -1
- package/dist/{send-ZNCJDSRP.js → send-TAOEZ4NH.js} +64 -6
- package/dist/skills/dreaming/references/INSTALL.md +3 -17
- package/dist/skills/shared-files/SKILL.md +44 -0
- package/dist/skills/shared-files/scripts/merge.ts +72 -0
- package/dist/skills/shared-files/scripts/pull.ts +52 -0
- package/dist/skills/volute-mind/SKILL.md +48 -22
- package/dist/{sleep-manager-MWYHM5HV.js → sleep-manager-G4B5GW5P.js} +7 -7
- package/dist/{sprout-IJVVKSJ2.js → sprout-UNT7LKKE.js} +1 -1
- package/dist/{status-77YEPHMW.js → status-NQJYR4BG.js} +45 -1
- package/dist/{status-THLOBLWG.js → status-S7UUPNRW.js} +3 -13
- package/dist/systems-SMEFSHTA.js +60 -0
- package/dist/{up-NKSMXBWR.js → up-W6VAK2XE.js} +1 -1
- package/dist/{version-notify-5Z4MNR6M.js → version-notify-WDHRO3XD.js} +11 -11
- package/dist/web-assets/assets/index-BmKDnWDB.css +1 -0
- package/dist/web-assets/assets/index-CLJMx-GA.js +71 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/templates/_base/src/lib/logger.ts +10 -53
- package/templates/_base/src/lib/router.ts +1 -9
- package/templates/claude/src/lib/stream-consumer.ts +1 -4
- package/templates/pi/src/lib/event-handler.ts +1 -14
- package/dist/auth-D3OT2ARB.js +0 -37
- package/dist/chunk-KDGS53OS.js +0 -50
- package/dist/chunk-RWKVSSLY.js +0 -26
- package/dist/chunk-T6HKBWXZ.js +0 -23
- package/dist/create-D7J73A6H.js +0 -45
- package/dist/file-CR36YUPD.js +0 -204
- package/dist/log-ABYNVYJ3.js +0 -39
- package/dist/logout-W4KOOBIT.js +0 -18
- package/dist/logs-U35JR2KE.js +0 -77
- package/dist/merge-LNSMSAOF.js +0 -46
- package/dist/pull-XCHJTM5M.js +0 -39
- package/dist/schedule-QTJMFATP.js +0 -154
- package/dist/service-6LIN3F3K.js +0 -122
- package/dist/shared-ML5I4Q2A.js +0 -39
- package/dist/status-7GA4SM4Y.js +0 -35
- package/dist/web-assets/assets/index-CI5wgghI.css +0 -1
- package/dist/web-assets/assets/index-is5CvJWH.js +0 -75
- package/dist/{chunk-GIE6CSN5.js → chunk-DUAUMCEE.js} +0 -0
- package/dist/{history-XKRTAFS2.js → history-ALPTNB3I.js} +0 -0
- package/dist/{setup-JG4QAEBV.js → setup-RXYVGGT7.js} +3 -3
package/dist/daemon.js
CHANGED
|
@@ -3,11 +3,8 @@ import {
|
|
|
3
3
|
addSharedWorktree,
|
|
4
4
|
ensureSharedRepo,
|
|
5
5
|
removeSharedWorktree,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
sharedPull,
|
|
9
|
-
sharedStatus
|
|
10
|
-
} from "./chunk-JKOWNZ4P.js";
|
|
6
|
+
sharedMerge
|
|
7
|
+
} from "./chunk-P72MVS4R.js";
|
|
11
8
|
import {
|
|
12
9
|
announceToSystem,
|
|
13
10
|
approveUser,
|
|
@@ -15,15 +12,15 @@ import {
|
|
|
15
12
|
countAdmins,
|
|
16
13
|
createUser,
|
|
17
14
|
deleteMindUser as deleteMindUser2,
|
|
15
|
+
deleteSystemsConfig,
|
|
18
16
|
deleteUser,
|
|
19
17
|
deliverMessage,
|
|
20
18
|
ensureSystemChannel,
|
|
21
19
|
extractTextContent,
|
|
22
|
-
getCachedRecentPages,
|
|
23
|
-
getCachedSites,
|
|
24
20
|
getDeliveryManager,
|
|
25
21
|
getOrCreateMindUser,
|
|
26
22
|
getScheduler,
|
|
23
|
+
getSleepManagerIfReady,
|
|
27
24
|
getTokenBudget,
|
|
28
25
|
getTypingMap,
|
|
29
26
|
getUser,
|
|
@@ -40,44 +37,26 @@ import {
|
|
|
40
37
|
migrateMindRoles,
|
|
41
38
|
publish as publish2,
|
|
42
39
|
publishTypingForChannels,
|
|
40
|
+
readSystemsConfig,
|
|
43
41
|
readVoluteConfig,
|
|
44
42
|
recordInbound,
|
|
45
43
|
resolveChannelId,
|
|
46
44
|
setUserRole,
|
|
47
45
|
splitMessage,
|
|
48
46
|
startMindFull,
|
|
49
|
-
stopAllWatchers,
|
|
50
47
|
stopMindFull,
|
|
51
48
|
subscribe as subscribe3,
|
|
52
49
|
updateUserProfile,
|
|
53
50
|
verifyUser,
|
|
54
51
|
writeChannelEntry,
|
|
52
|
+
writeSystemsConfig,
|
|
55
53
|
writeVoluteConfig
|
|
56
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-CMILSHZD.js";
|
|
57
55
|
import {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} from "./chunk-
|
|
62
|
-
import {
|
|
63
|
-
getActiveMinds,
|
|
64
|
-
onMindEvent,
|
|
65
|
-
stopAll
|
|
66
|
-
} from "./chunk-K5NAC55T.js";
|
|
67
|
-
import {
|
|
68
|
-
PROMPT_DEFAULTS,
|
|
69
|
-
PROMPT_KEYS,
|
|
70
|
-
RestartTracker,
|
|
71
|
-
RotatingLog,
|
|
72
|
-
getMindManager,
|
|
73
|
-
getMindPromptDefaults,
|
|
74
|
-
getPrompt,
|
|
75
|
-
getPromptIfCustom,
|
|
76
|
-
initMindManager,
|
|
77
|
-
resolveMindToken,
|
|
78
|
-
substitute
|
|
79
|
-
} from "./chunk-PHSAT7YL.js";
|
|
80
|
-
import "./chunk-USUXRNVD.js";
|
|
56
|
+
getCachedRecentPages,
|
|
57
|
+
getCachedSites,
|
|
58
|
+
stopAllWatchers
|
|
59
|
+
} from "./chunk-THUUIU3E.js";
|
|
81
60
|
import {
|
|
82
61
|
addMessage,
|
|
83
62
|
createChannel,
|
|
@@ -92,21 +71,24 @@ import {
|
|
|
92
71
|
getParticipants,
|
|
93
72
|
getUnreadCounts,
|
|
94
73
|
initWebhook,
|
|
74
|
+
isConversationForMind,
|
|
95
75
|
isParticipant,
|
|
96
76
|
isParticipantOrOwner,
|
|
97
77
|
joinChannel,
|
|
98
78
|
leaveChannel,
|
|
99
79
|
listChannels,
|
|
80
|
+
listConversationsForMind,
|
|
100
81
|
listConversationsForUser,
|
|
101
82
|
listConversationsWithParticipants,
|
|
102
83
|
markConversationRead,
|
|
103
84
|
publish,
|
|
104
85
|
subscribe as subscribe2
|
|
105
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-FLZGS4QH.js";
|
|
106
87
|
import {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
checkForUpdate,
|
|
89
|
+
checkForUpdateCached,
|
|
90
|
+
getCurrentVersion
|
|
91
|
+
} from "./chunk-HDN7MNGD.js";
|
|
110
92
|
import {
|
|
111
93
|
applyInitFiles,
|
|
112
94
|
composeTemplate,
|
|
@@ -115,6 +97,29 @@ import {
|
|
|
115
97
|
findTemplatesRoot,
|
|
116
98
|
listFiles
|
|
117
99
|
} from "./chunk-AKPFNL7L.js";
|
|
100
|
+
import {
|
|
101
|
+
getActiveMinds,
|
|
102
|
+
onMindEvent,
|
|
103
|
+
stopAll
|
|
104
|
+
} from "./chunk-CQ7SNKNI.js";
|
|
105
|
+
import {
|
|
106
|
+
broadcast,
|
|
107
|
+
subscribe
|
|
108
|
+
} from "./chunk-VIVMW2H2.js";
|
|
109
|
+
import {
|
|
110
|
+
PROMPT_DEFAULTS,
|
|
111
|
+
PROMPT_KEYS,
|
|
112
|
+
RestartTracker,
|
|
113
|
+
RotatingLog,
|
|
114
|
+
getMindManager,
|
|
115
|
+
getMindPromptDefaults,
|
|
116
|
+
getPrompt,
|
|
117
|
+
getPromptIfCustom,
|
|
118
|
+
initMindManager,
|
|
119
|
+
resolveMindToken,
|
|
120
|
+
substitute
|
|
121
|
+
} from "./chunk-EHZKEMMV.js";
|
|
122
|
+
import "./chunk-J4IBNXGJ.js";
|
|
118
123
|
import {
|
|
119
124
|
SEED_SKILLS,
|
|
120
125
|
STANDARD_SKILLS,
|
|
@@ -130,12 +135,15 @@ import {
|
|
|
130
135
|
syncBuiltinSkills,
|
|
131
136
|
uninstallSkill,
|
|
132
137
|
updateSkill
|
|
133
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-NI5FFCCS.js";
|
|
134
139
|
import {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
acceptPending,
|
|
141
|
+
formatFileSize,
|
|
142
|
+
listPending,
|
|
143
|
+
rejectPending,
|
|
144
|
+
stageFile,
|
|
145
|
+
validateFilePath
|
|
146
|
+
} from "./chunk-MD4C26II.js";
|
|
139
147
|
import {
|
|
140
148
|
findBridgeForChannel,
|
|
141
149
|
findOpenClawSession,
|
|
@@ -182,7 +190,7 @@ import "./chunk-D424ZQGI.js";
|
|
|
182
190
|
import {
|
|
183
191
|
buildVoluteSlug,
|
|
184
192
|
slugify
|
|
185
|
-
} from "./chunk-
|
|
193
|
+
} from "./chunk-A6TUJJ3L.js";
|
|
186
194
|
import {
|
|
187
195
|
activity,
|
|
188
196
|
addMind,
|
|
@@ -220,10 +228,10 @@ import {
|
|
|
220
228
|
} from "./chunk-K3NQKI34.js";
|
|
221
229
|
|
|
222
230
|
// src/daemon.ts
|
|
223
|
-
import { randomBytes
|
|
224
|
-
import { mkdirSync as
|
|
231
|
+
import { randomBytes } from "crypto";
|
|
232
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
225
233
|
import { homedir as homedir2 } from "os";
|
|
226
|
-
import { resolve as
|
|
234
|
+
import { resolve as resolve22 } from "path";
|
|
227
235
|
import { format } from "util";
|
|
228
236
|
|
|
229
237
|
// src/lib/daemon/bridge-manager.ts
|
|
@@ -433,8 +441,8 @@ var BridgeManager = class {
|
|
|
433
441
|
if (!tracked) return;
|
|
434
442
|
this.stopping.add(platform);
|
|
435
443
|
this.bridges.delete(platform);
|
|
436
|
-
await new Promise((
|
|
437
|
-
tracked.child.on("exit", () =>
|
|
444
|
+
await new Promise((resolve23) => {
|
|
445
|
+
tracked.child.on("exit", () => resolve23());
|
|
438
446
|
try {
|
|
439
447
|
if (tracked.child.pid) {
|
|
440
448
|
process.kill(-tracked.child.pid, "SIGTERM");
|
|
@@ -445,7 +453,7 @@ var BridgeManager = class {
|
|
|
445
453
|
if (err instanceof Error && err.code !== "ESRCH") {
|
|
446
454
|
blog.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
|
|
447
455
|
}
|
|
448
|
-
|
|
456
|
+
resolve23();
|
|
449
457
|
}
|
|
450
458
|
setTimeout(() => {
|
|
451
459
|
try {
|
|
@@ -456,7 +464,7 @@ var BridgeManager = class {
|
|
|
456
464
|
}
|
|
457
465
|
} catch {
|
|
458
466
|
}
|
|
459
|
-
|
|
467
|
+
resolve23();
|
|
460
468
|
}, 5e3);
|
|
461
469
|
});
|
|
462
470
|
this.stopping.delete(platform);
|
|
@@ -544,6 +552,15 @@ function getBridgeManager() {
|
|
|
544
552
|
return instance;
|
|
545
553
|
}
|
|
546
554
|
|
|
555
|
+
// src/lib/history-cleanup.ts
|
|
556
|
+
import { and, eq, lt } from "drizzle-orm";
|
|
557
|
+
var LOG_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
558
|
+
async function cleanExpiredLogs() {
|
|
559
|
+
const db = await getDb();
|
|
560
|
+
const cutoff = new Date(Date.now() - LOG_RETENTION_MS).toISOString().replace("T", " ").slice(0, 19);
|
|
561
|
+
await db.delete(mindHistory).where(and(eq(mindHistory.type, "log"), lt(mindHistory.created_at, cutoff)));
|
|
562
|
+
}
|
|
563
|
+
|
|
547
564
|
// src/lib/migrate-agents-to-minds.ts
|
|
548
565
|
import { execFileSync } from "child_process";
|
|
549
566
|
import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -803,7 +820,7 @@ function migrateToSystemDir() {
|
|
|
803
820
|
|
|
804
821
|
// src/web/middleware/auth.ts
|
|
805
822
|
import { timingSafeEqual } from "crypto";
|
|
806
|
-
import { eq, lt } from "drizzle-orm";
|
|
823
|
+
import { eq as eq2, lt as lt2 } from "drizzle-orm";
|
|
807
824
|
import { getCookie } from "hono/cookie";
|
|
808
825
|
import { createMiddleware } from "hono/factory";
|
|
809
826
|
function isValidDaemonToken(token) {
|
|
@@ -826,14 +843,14 @@ async function createSession(userId) {
|
|
|
826
843
|
async function deleteSession(sessionId) {
|
|
827
844
|
sessionCache.delete(sessionId);
|
|
828
845
|
const db = await getDb();
|
|
829
|
-
await db.delete(sessions).where(
|
|
846
|
+
await db.delete(sessions).where(eq2(sessions.id, sessionId));
|
|
830
847
|
}
|
|
831
848
|
async function getSessionUserId(sessionId) {
|
|
832
849
|
const db = await getDb();
|
|
833
|
-
const row = await db.select().from(sessions).where(
|
|
850
|
+
const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
|
|
834
851
|
if (!row) return void 0;
|
|
835
852
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
836
|
-
await db.delete(sessions).where(
|
|
853
|
+
await db.delete(sessions).where(eq2(sessions.id, sessionId));
|
|
837
854
|
return void 0;
|
|
838
855
|
}
|
|
839
856
|
return row.userId;
|
|
@@ -841,7 +858,7 @@ async function getSessionUserId(sessionId) {
|
|
|
841
858
|
async function cleanExpiredSessions() {
|
|
842
859
|
const db = await getDb();
|
|
843
860
|
const cutoff = Date.now() - SESSION_MAX_AGE;
|
|
844
|
-
await db.delete(sessions).where(
|
|
861
|
+
await db.delete(sessions).where(lt2(sessions.createdAt, cutoff));
|
|
845
862
|
}
|
|
846
863
|
var requireAdmin = createMiddleware(async (c, next) => {
|
|
847
864
|
const user = c.get("user");
|
|
@@ -923,10 +940,10 @@ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
|
|
|
923
940
|
});
|
|
924
941
|
|
|
925
942
|
// src/web/server.ts
|
|
926
|
-
import { existsSync as
|
|
943
|
+
import { existsSync as existsSync16 } from "fs";
|
|
927
944
|
import { readFile as readFile4, stat as stat4 } from "fs/promises";
|
|
928
945
|
import { createServer as createHttpsServer } from "https";
|
|
929
|
-
import { dirname as dirname2, extname as extname5, resolve as
|
|
946
|
+
import { dirname as dirname2, extname as extname5, resolve as resolve21 } from "path";
|
|
930
947
|
import { serve } from "@hono/node-server";
|
|
931
948
|
|
|
932
949
|
// src/web/app.ts
|
|
@@ -997,8 +1014,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
997
1014
|
});
|
|
998
1015
|
}, 15e3);
|
|
999
1016
|
cleanups.push(() => clearInterval(keepAlive));
|
|
1000
|
-
await new Promise((
|
|
1001
|
-
stream.onAbort(() =>
|
|
1017
|
+
await new Promise((resolve23) => {
|
|
1018
|
+
stream.onAbort(() => resolve23());
|
|
1002
1019
|
});
|
|
1003
1020
|
} finally {
|
|
1004
1021
|
for (const cleanup of cleanups) {
|
|
@@ -1277,7 +1294,7 @@ import { Hono as Hono3 } from "hono";
|
|
|
1277
1294
|
import { z as z2 } from "zod";
|
|
1278
1295
|
|
|
1279
1296
|
// src/lib/puppets.ts
|
|
1280
|
-
import { and, eq as
|
|
1297
|
+
import { and as and2, eq as eq3 } from "drizzle-orm";
|
|
1281
1298
|
async function findOrCreatePuppet(platform, platformId, displayName) {
|
|
1282
1299
|
const username = `${platform}:${platformId}`;
|
|
1283
1300
|
const db = await getDb();
|
|
@@ -1286,10 +1303,10 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
|
|
|
1286
1303
|
username: users.username,
|
|
1287
1304
|
display_name: users.display_name,
|
|
1288
1305
|
avatar: users.avatar
|
|
1289
|
-
}).from(users).where(
|
|
1306
|
+
}).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
|
|
1290
1307
|
if (existing) {
|
|
1291
1308
|
if (existing.display_name !== displayName) {
|
|
1292
|
-
await db.update(users).set({ display_name: displayName }).where(
|
|
1309
|
+
await db.update(users).set({ display_name: displayName }).where(eq3(users.id, existing.id));
|
|
1293
1310
|
existing.display_name = displayName;
|
|
1294
1311
|
}
|
|
1295
1312
|
return existing;
|
|
@@ -1315,7 +1332,7 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
|
|
|
1315
1332
|
username: users.username,
|
|
1316
1333
|
display_name: users.display_name,
|
|
1317
1334
|
avatar: users.avatar
|
|
1318
|
-
}).from(users).where(
|
|
1335
|
+
}).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
|
|
1319
1336
|
if (retried) return retried;
|
|
1320
1337
|
}
|
|
1321
1338
|
throw err;
|
|
@@ -1397,7 +1414,7 @@ var app3 = new Hono3().post("/:platform/inbound", zValidator2("json", inboundSch
|
|
|
1397
1414
|
}
|
|
1398
1415
|
const participants = await getParticipants(channel.id);
|
|
1399
1416
|
if (!participants.some((p) => p.userId === puppet.id)) {
|
|
1400
|
-
const { addParticipant } = await import("./conversations-
|
|
1417
|
+
const { addParticipant } = await import("./conversations-P5BL7RMX.js");
|
|
1401
1418
|
await addParticipant(channel.id, puppet.id);
|
|
1402
1419
|
}
|
|
1403
1420
|
const contentBlocks = body.content;
|
|
@@ -1490,10 +1507,10 @@ async function fanOutToBridgedMinds(opts) {
|
|
|
1490
1507
|
const participants = await getParticipants(opts.conversationId);
|
|
1491
1508
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
1492
1509
|
const participantNames = participants.map((p) => p.username);
|
|
1493
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
1494
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
1510
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
|
|
1511
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
1495
1512
|
const manager = getMindManager2();
|
|
1496
|
-
const sm =
|
|
1513
|
+
const sm = getSleepManagerIfReady2();
|
|
1497
1514
|
const targetMinds = mindParticipants.filter((ap) => {
|
|
1498
1515
|
return (manager.isRunning(ap.username) || sm?.isSleeping(ap.username)) && ap.username !== opts.senderName;
|
|
1499
1516
|
}).map((ap) => ap.username);
|
|
@@ -1508,7 +1525,7 @@ async function fanOutToBridgedMinds(opts) {
|
|
|
1508
1525
|
writeChannelEntry(mindName, channel, {
|
|
1509
1526
|
platformId: opts.conversationId,
|
|
1510
1527
|
platform: "volute",
|
|
1511
|
-
type: opts.isDM ? "dm" : "
|
|
1528
|
+
type: opts.isDM ? "dm" : "channel"
|
|
1512
1529
|
});
|
|
1513
1530
|
} catch (err) {
|
|
1514
1531
|
logger_default.warn(`failed to write channel entry for ${mindName}`, logger_default.errorData(err));
|
|
@@ -1636,7 +1653,7 @@ async function listConversations(env) {
|
|
|
1636
1653
|
id: slug,
|
|
1637
1654
|
platformId: dm.id,
|
|
1638
1655
|
name: recipients.join(", ") || "DM",
|
|
1639
|
-
type: dm.type === 1 ? "dm" : "
|
|
1656
|
+
type: dm.type === 1 ? "dm" : "channel"
|
|
1640
1657
|
});
|
|
1641
1658
|
}
|
|
1642
1659
|
return results;
|
|
@@ -1806,7 +1823,6 @@ async function listConversations2(env) {
|
|
|
1806
1823
|
return data.channels.map((ch) => {
|
|
1807
1824
|
let type = "channel";
|
|
1808
1825
|
if (ch.is_im) type = "dm";
|
|
1809
|
-
else if (ch.is_mpim) type = "group";
|
|
1810
1826
|
let slug;
|
|
1811
1827
|
let name;
|
|
1812
1828
|
if (ch.is_im && ch.user) {
|
|
@@ -1883,7 +1899,7 @@ async function createConversation3(env, participants, name) {
|
|
|
1883
1899
|
platformId,
|
|
1884
1900
|
platform: "slack",
|
|
1885
1901
|
name: participants.join(", "),
|
|
1886
|
-
type: participants.length === 1 ? "dm" : "
|
|
1902
|
+
type: participants.length === 1 ? "dm" : "channel"
|
|
1887
1903
|
});
|
|
1888
1904
|
}
|
|
1889
1905
|
return slug;
|
|
@@ -1999,18 +2015,18 @@ import { resolve as resolve7 } from "path";
|
|
|
1999
2015
|
function getDaemonConfig() {
|
|
2000
2016
|
const newPath = resolve7(voluteSystemDir(), "daemon.json");
|
|
2001
2017
|
const legacyPath = resolve7(voluteHome(), "daemon.json");
|
|
2002
|
-
const
|
|
2003
|
-
if (!existsSync7(
|
|
2018
|
+
const configPath = existsSync7(newPath) ? newPath : legacyPath;
|
|
2019
|
+
if (!existsSync7(configPath)) {
|
|
2004
2020
|
throw new Error("Volute daemon is not running");
|
|
2005
2021
|
}
|
|
2006
2022
|
let config;
|
|
2007
2023
|
try {
|
|
2008
|
-
config = JSON.parse(readFileSync5(
|
|
2024
|
+
config = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
2009
2025
|
} catch (err) {
|
|
2010
|
-
throw new Error(`Failed to parse ${
|
|
2026
|
+
throw new Error(`Failed to parse ${configPath}: ${err}`);
|
|
2011
2027
|
}
|
|
2012
2028
|
if (typeof config.port !== "number") {
|
|
2013
|
-
throw new Error(`Invalid or missing port in ${
|
|
2029
|
+
throw new Error(`Invalid or missing port in ${configPath}`);
|
|
2014
2030
|
}
|
|
2015
2031
|
const url = new URL("http://localhost");
|
|
2016
2032
|
url.hostname = config.hostname || "localhost";
|
|
@@ -2099,7 +2115,7 @@ async function listConversations4(env) {
|
|
|
2099
2115
|
convType: conv.type,
|
|
2100
2116
|
convName: conv.name
|
|
2101
2117
|
});
|
|
2102
|
-
const convType = conv.type === "channel" ? "channel" :
|
|
2118
|
+
const convType = conv.type === "channel" ? "channel" : "dm";
|
|
2103
2119
|
results.push({
|
|
2104
2120
|
id: slug,
|
|
2105
2121
|
platformId: conv.id,
|
|
@@ -2304,7 +2320,7 @@ var app5 = new Hono5().get("/:name/env", async (c) => {
|
|
|
2304
2320
|
const value = merged[key];
|
|
2305
2321
|
if (value === void 0) return c.json({ error: "Key not found" }, 404);
|
|
2306
2322
|
return c.json({ value });
|
|
2307
|
-
}).put("/:name/env/:key",
|
|
2323
|
+
}).put("/:name/env/:key", requireSelf(), async (c) => {
|
|
2308
2324
|
const name = c.req.param("name");
|
|
2309
2325
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2310
2326
|
const key = c.req.param("key");
|
|
@@ -2322,7 +2338,7 @@ var app5 = new Hono5().get("/:name/env", async (c) => {
|
|
|
2322
2338
|
env[key] = body.value;
|
|
2323
2339
|
writeEnv(path, env);
|
|
2324
2340
|
return c.json({ ok: true });
|
|
2325
|
-
}).delete("/:name/env/:key",
|
|
2341
|
+
}).delete("/:name/env/:key", requireSelf(), async (c) => {
|
|
2326
2342
|
const name = c.req.param("name");
|
|
2327
2343
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
2328
2344
|
const key = c.req.param("key");
|
|
@@ -2363,165 +2379,9 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
|
2363
2379
|
var env_default = app5;
|
|
2364
2380
|
|
|
2365
2381
|
// src/web/api/file-sharing.ts
|
|
2366
|
-
import { readFileSync as
|
|
2367
|
-
import { resolve as
|
|
2382
|
+
import { readFileSync as readFileSync6, statSync } from "fs";
|
|
2383
|
+
import { resolve as resolve8 } from "path";
|
|
2368
2384
|
import { Hono as Hono6 } from "hono";
|
|
2369
|
-
|
|
2370
|
-
// src/lib/file-sharing.ts
|
|
2371
|
-
import { randomBytes } from "crypto";
|
|
2372
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2373
|
-
import { basename, join, normalize, resolve as resolve8 } from "path";
|
|
2374
|
-
function validateFilePath(filePath) {
|
|
2375
|
-
if (!filePath) return "File path is required";
|
|
2376
|
-
const normalized = normalize(filePath);
|
|
2377
|
-
if (normalized.startsWith("/") || normalized.startsWith("\\")) {
|
|
2378
|
-
return "Absolute paths are not allowed";
|
|
2379
|
-
}
|
|
2380
|
-
if (normalized.includes("..")) {
|
|
2381
|
-
return "Path traversal (..) is not allowed";
|
|
2382
|
-
}
|
|
2383
|
-
return null;
|
|
2384
|
-
}
|
|
2385
|
-
function configPath(dir) {
|
|
2386
|
-
return resolve8(dir, "home", ".config", "file-sharing.json");
|
|
2387
|
-
}
|
|
2388
|
-
function readFileSharingConfig(dir) {
|
|
2389
|
-
const p = configPath(dir);
|
|
2390
|
-
if (!existsSync8(p)) return {};
|
|
2391
|
-
try {
|
|
2392
|
-
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
2393
|
-
} catch (err) {
|
|
2394
|
-
console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
|
|
2395
|
-
return {};
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
function writeFileSharingConfig(dir, config) {
|
|
2399
|
-
const p = configPath(dir);
|
|
2400
|
-
mkdirSync5(resolve8(p, ".."), { recursive: true });
|
|
2401
|
-
writeFileSync4(p, `${JSON.stringify(config, null, 2)}
|
|
2402
|
-
`);
|
|
2403
|
-
}
|
|
2404
|
-
function isTrustedSender(dir, sender) {
|
|
2405
|
-
const config = readFileSharingConfig(dir);
|
|
2406
|
-
return config.trustedSenders?.includes(sender) ?? false;
|
|
2407
|
-
}
|
|
2408
|
-
function addTrust(dir, sender) {
|
|
2409
|
-
const config = readFileSharingConfig(dir);
|
|
2410
|
-
const trusted = config.trustedSenders ?? [];
|
|
2411
|
-
if (!trusted.includes(sender)) {
|
|
2412
|
-
trusted.push(sender);
|
|
2413
|
-
}
|
|
2414
|
-
config.trustedSenders = trusted;
|
|
2415
|
-
writeFileSharingConfig(dir, config);
|
|
2416
|
-
}
|
|
2417
|
-
function removeTrust(dir, sender) {
|
|
2418
|
-
const config = readFileSharingConfig(dir);
|
|
2419
|
-
const trusted = config.trustedSenders ?? [];
|
|
2420
|
-
config.trustedSenders = trusted.filter((s) => s !== sender);
|
|
2421
|
-
writeFileSharingConfig(dir, config);
|
|
2422
|
-
}
|
|
2423
|
-
function pendingDir(receiver) {
|
|
2424
|
-
return resolve8(stateDir(receiver), "pending-files");
|
|
2425
|
-
}
|
|
2426
|
-
function validateId(id) {
|
|
2427
|
-
if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
2428
|
-
throw new Error("Invalid pending file id");
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
function generateId(sender) {
|
|
2432
|
-
const ts = Date.now();
|
|
2433
|
-
const rand = randomBytes(2).toString("hex");
|
|
2434
|
-
return `${sender}-${ts}-${rand}`;
|
|
2435
|
-
}
|
|
2436
|
-
function stageFile(receiver, sender, filename, content, originalPath) {
|
|
2437
|
-
const err = validateFilePath(filename);
|
|
2438
|
-
if (err) throw new Error(err);
|
|
2439
|
-
if (sender.includes("/") || sender.includes("\\")) {
|
|
2440
|
-
throw new Error("Invalid sender name");
|
|
2441
|
-
}
|
|
2442
|
-
const id = generateId(sender);
|
|
2443
|
-
const dir = resolve8(pendingDir(receiver), id);
|
|
2444
|
-
mkdirSync5(dir, { recursive: true });
|
|
2445
|
-
const metadata = {
|
|
2446
|
-
id,
|
|
2447
|
-
sender,
|
|
2448
|
-
filename: basename(filename),
|
|
2449
|
-
originalPath,
|
|
2450
|
-
size: content.length,
|
|
2451
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2452
|
-
};
|
|
2453
|
-
writeFileSync4(resolve8(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
|
|
2454
|
-
`);
|
|
2455
|
-
writeFileSync4(resolve8(dir, "data"), content);
|
|
2456
|
-
return { id };
|
|
2457
|
-
}
|
|
2458
|
-
function listPending(receiver) {
|
|
2459
|
-
const dir = pendingDir(receiver);
|
|
2460
|
-
if (!existsSync8(dir)) return [];
|
|
2461
|
-
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2462
|
-
const result = [];
|
|
2463
|
-
for (const entry of entries) {
|
|
2464
|
-
if (!entry.isDirectory()) continue;
|
|
2465
|
-
const metaPath = resolve8(dir, entry.name, "metadata.json");
|
|
2466
|
-
if (!existsSync8(metaPath)) continue;
|
|
2467
|
-
try {
|
|
2468
|
-
result.push(JSON.parse(readFileSync6(metaPath, "utf-8")));
|
|
2469
|
-
} catch (err) {
|
|
2470
|
-
console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
return result.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2474
|
-
}
|
|
2475
|
-
function getPending(receiver, id) {
|
|
2476
|
-
validateId(id);
|
|
2477
|
-
const metaPath = resolve8(pendingDir(receiver), id, "metadata.json");
|
|
2478
|
-
if (!existsSync8(metaPath)) return null;
|
|
2479
|
-
try {
|
|
2480
|
-
return JSON.parse(readFileSync6(metaPath, "utf-8"));
|
|
2481
|
-
} catch (err) {
|
|
2482
|
-
console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
|
|
2483
|
-
return null;
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
function deliverFile(receiverDir, sender, filename, content, inboxPath) {
|
|
2487
|
-
const err = validateFilePath(filename);
|
|
2488
|
-
if (err) throw new Error(err);
|
|
2489
|
-
const inbox = inboxPath ?? "inbox";
|
|
2490
|
-
const inboxErr = validateFilePath(inbox);
|
|
2491
|
-
if (inboxErr) throw new Error(`Invalid inboxPath: ${inboxErr}`);
|
|
2492
|
-
if (sender.includes("/") || sender.includes("\\")) {
|
|
2493
|
-
throw new Error("Invalid sender name");
|
|
2494
|
-
}
|
|
2495
|
-
const destDir = resolve8(receiverDir, "home", inbox, sender);
|
|
2496
|
-
mkdirSync5(destDir, { recursive: true });
|
|
2497
|
-
const destPath = resolve8(destDir, basename(filename));
|
|
2498
|
-
writeFileSync4(destPath, content);
|
|
2499
|
-
return join(inbox, sender, basename(filename));
|
|
2500
|
-
}
|
|
2501
|
-
function acceptPending(receiver, id, receiverDir) {
|
|
2502
|
-
const meta = getPending(receiver, id);
|
|
2503
|
-
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2504
|
-
const dataPath = resolve8(pendingDir(receiver), id, "data");
|
|
2505
|
-
const content = readFileSync6(dataPath);
|
|
2506
|
-
const config = readFileSharingConfig(receiverDir);
|
|
2507
|
-
const inboxPath = config.inboxPath ?? "inbox";
|
|
2508
|
-
const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
|
|
2509
|
-
rmSync3(resolve8(pendingDir(receiver), id), { recursive: true });
|
|
2510
|
-
return { sender: meta.sender, filename: meta.filename, destPath };
|
|
2511
|
-
}
|
|
2512
|
-
function rejectPending(receiver, id) {
|
|
2513
|
-
const meta = getPending(receiver, id);
|
|
2514
|
-
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2515
|
-
rmSync3(resolve8(pendingDir(receiver), id), { recursive: true });
|
|
2516
|
-
return { sender: meta.sender, filename: meta.filename };
|
|
2517
|
-
}
|
|
2518
|
-
function formatFileSize(bytes) {
|
|
2519
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
2520
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2521
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
// src/web/api/file-sharing.ts
|
|
2525
2385
|
async function notifyMind(port, message, channel, sender) {
|
|
2526
2386
|
try {
|
|
2527
2387
|
const res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
@@ -2553,7 +2413,7 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
|
|
|
2553
2413
|
const pathErr = validateFilePath(body.filePath);
|
|
2554
2414
|
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2555
2415
|
const senderDir = mindDir(senderName);
|
|
2556
|
-
const filePath =
|
|
2416
|
+
const filePath = resolve8(senderDir, "home", body.filePath);
|
|
2557
2417
|
const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
2558
2418
|
const stat5 = statSync(filePath, { throwIfNoEntry: false });
|
|
2559
2419
|
if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
@@ -2567,31 +2427,21 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
|
|
|
2567
2427
|
}
|
|
2568
2428
|
let content;
|
|
2569
2429
|
try {
|
|
2570
|
-
content =
|
|
2571
|
-
} catch {
|
|
2572
|
-
|
|
2430
|
+
content = readFileSync6(filePath);
|
|
2431
|
+
} catch (err) {
|
|
2432
|
+
const code = err.code;
|
|
2433
|
+
if (code === "ENOENT") {
|
|
2434
|
+
return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2435
|
+
}
|
|
2436
|
+
return c.json({ error: `Failed to read file: ${code ?? err.message}` }, 500);
|
|
2573
2437
|
}
|
|
2574
|
-
const receiverDir = mindDir(body.targetMind);
|
|
2575
2438
|
const filename = body.filePath;
|
|
2576
2439
|
const sizeStr = formatFileSize(content.length);
|
|
2577
|
-
if (isTrustedSender(receiverDir, senderName)) {
|
|
2578
|
-
const config = readFileSharingConfig(receiverDir);
|
|
2579
|
-
const destPath = deliverFile(receiverDir, senderName, filename, content, config.inboxPath);
|
|
2580
|
-
if (receiverEntry.running) {
|
|
2581
|
-
await notifyMind(
|
|
2582
|
-
receiverEntry.port,
|
|
2583
|
-
`[file] ${senderName} sent ${filename} (${sizeStr}) \u2192 ${destPath}`,
|
|
2584
|
-
"system:file-sharing",
|
|
2585
|
-
senderName
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
return c.json({ status: "delivered", destPath }, 200);
|
|
2589
|
-
}
|
|
2590
2440
|
const { id } = stageFile(body.targetMind, senderName, filename, content, body.filePath);
|
|
2591
2441
|
if (receiverEntry.running) {
|
|
2592
2442
|
await notifyMind(
|
|
2593
2443
|
receiverEntry.port,
|
|
2594
|
-
`[file] ${senderName}
|
|
2444
|
+
`[file] ${senderName} sent ${filename} (${sizeStr}) \u2014 run: volute chat accept ${id}`,
|
|
2595
2445
|
"system:file-sharing",
|
|
2596
2446
|
senderName
|
|
2597
2447
|
);
|
|
@@ -2607,9 +2457,13 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
|
|
|
2607
2457
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2608
2458
|
const body = await c.req.json();
|
|
2609
2459
|
if (!body.id) return c.json({ error: "id is required" }, 400);
|
|
2460
|
+
if (body.dest) {
|
|
2461
|
+
const destErr = validateFilePath(body.dest);
|
|
2462
|
+
if (destErr) return c.json({ error: `Invalid dest: ${destErr}` }, 400);
|
|
2463
|
+
}
|
|
2610
2464
|
let result;
|
|
2611
2465
|
try {
|
|
2612
|
-
result = acceptPending(name, body.id, mindDir(name));
|
|
2466
|
+
result = acceptPending(name, body.id, mindDir(name), body.dest);
|
|
2613
2467
|
} catch (err) {
|
|
2614
2468
|
const message = err.message;
|
|
2615
2469
|
if (message.includes("not found") || message.includes("Invalid pending")) {
|
|
@@ -2652,26 +2506,44 @@ var app6 = new Hono6().post("/:name/files/send", requireSelf(), async (c) => {
|
|
|
2652
2506
|
);
|
|
2653
2507
|
}
|
|
2654
2508
|
return c.json({ ok: true });
|
|
2655
|
-
}).post("/:name/files/
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2509
|
+
}).post("/:name/files/stage", async (c) => {
|
|
2510
|
+
const receiverName = c.req.param("name");
|
|
2511
|
+
const receiverEntry = await findMind(receiverName);
|
|
2512
|
+
if (!receiverEntry) return c.json({ error: "Mind not found" }, 404);
|
|
2658
2513
|
const body = await c.req.json();
|
|
2659
|
-
if (!body.sender
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2514
|
+
if (!body.sender || !body.filename || !body.data) {
|
|
2515
|
+
return c.json({ error: "sender, filename, and data are required" }, 400);
|
|
2516
|
+
}
|
|
2517
|
+
const pathErr = validateFilePath(body.filename);
|
|
2518
|
+
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2519
|
+
const content = Buffer.from(body.data, "base64");
|
|
2520
|
+
const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
2521
|
+
if (content.length > MAX_FILE_SIZE2) {
|
|
2522
|
+
return c.json(
|
|
2523
|
+
{
|
|
2524
|
+
error: `File too large (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
|
|
2525
|
+
},
|
|
2526
|
+
413
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
const sizeStr = formatFileSize(content.length);
|
|
2530
|
+
const { id } = stageFile(receiverName, body.sender, body.filename, content, body.filename);
|
|
2531
|
+
if (receiverEntry.running) {
|
|
2532
|
+
await notifyMind(
|
|
2533
|
+
receiverEntry.port,
|
|
2534
|
+
`[file] ${body.sender} sent ${body.filename} (${sizeStr}) \u2014 run: volute chat accept ${id}`,
|
|
2535
|
+
"system:file-sharing",
|
|
2536
|
+
body.sender
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
return c.json({ status: "pending", id }, 200);
|
|
2668
2540
|
});
|
|
2669
2541
|
var file_sharing_default = app6;
|
|
2670
2542
|
|
|
2671
2543
|
// src/web/api/files.ts
|
|
2672
|
-
import { existsSync as
|
|
2544
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2673
2545
|
import { readdir, readFile, realpath, stat } from "fs/promises";
|
|
2674
|
-
import { extname as extname2, resolve as
|
|
2546
|
+
import { extname as extname2, resolve as resolve9 } from "path";
|
|
2675
2547
|
import { Hono as Hono7 } from "hono";
|
|
2676
2548
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2677
2549
|
var AVATAR_MIME2 = {
|
|
@@ -2692,8 +2564,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2692
2564
|
const ext = extname2(config.profile.avatar).toLowerCase();
|
|
2693
2565
|
const mime = AVATAR_MIME2[ext];
|
|
2694
2566
|
if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
|
|
2695
|
-
const homeDir =
|
|
2696
|
-
const avatarPath =
|
|
2567
|
+
const homeDir = resolve9(dir, "home");
|
|
2568
|
+
const avatarPath = resolve9(homeDir, config.profile.avatar);
|
|
2697
2569
|
if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
|
|
2698
2570
|
let realAvatarPath;
|
|
2699
2571
|
try {
|
|
@@ -2722,8 +2594,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2722
2594
|
const entry = await findMind(name);
|
|
2723
2595
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2724
2596
|
const dir = mindDir(name);
|
|
2725
|
-
const homeDir =
|
|
2726
|
-
if (!
|
|
2597
|
+
const homeDir = resolve9(dir, "home");
|
|
2598
|
+
if (!existsSync8(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2727
2599
|
const allFiles = await readdir(homeDir);
|
|
2728
2600
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2729
2601
|
return c.json(files);
|
|
@@ -2736,8 +2608,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2736
2608
|
const entry = await findMind(name);
|
|
2737
2609
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2738
2610
|
const dir = mindDir(name);
|
|
2739
|
-
const filePath =
|
|
2740
|
-
if (!
|
|
2611
|
+
const filePath = resolve9(dir, "home", filename);
|
|
2612
|
+
if (!existsSync8(filePath)) {
|
|
2741
2613
|
return c.json({ error: "File not found" }, 404);
|
|
2742
2614
|
}
|
|
2743
2615
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2750,19 +2622,19 @@ import { Hono as Hono8 } from "hono";
|
|
|
2750
2622
|
|
|
2751
2623
|
// src/lib/identity.ts
|
|
2752
2624
|
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
2753
|
-
import { existsSync as
|
|
2754
|
-
import { resolve as
|
|
2625
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2626
|
+
import { resolve as resolve10 } from "path";
|
|
2755
2627
|
function generateIdentity(mindDir2) {
|
|
2756
|
-
const identityDir =
|
|
2757
|
-
|
|
2628
|
+
const identityDir = resolve10(mindDir2, ".mind/identity");
|
|
2629
|
+
mkdirSync5(identityDir, { recursive: true });
|
|
2758
2630
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
2759
2631
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2760
2632
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2761
2633
|
});
|
|
2762
|
-
const privatePath =
|
|
2763
|
-
const publicPath =
|
|
2764
|
-
|
|
2765
|
-
|
|
2634
|
+
const privatePath = resolve10(identityDir, "private.pem");
|
|
2635
|
+
const publicPath = resolve10(identityDir, "public.pem");
|
|
2636
|
+
writeFileSync4(privatePath, privateKey, { mode: 384 });
|
|
2637
|
+
writeFileSync4(publicPath, publicKey, { mode: 420 });
|
|
2766
2638
|
const config = readVoluteConfig(mindDir2) ?? {};
|
|
2767
2639
|
config.identity = {
|
|
2768
2640
|
privateKey: ".mind/identity/private.pem",
|
|
@@ -2775,17 +2647,17 @@ function getPrivateKey(mindDir2) {
|
|
|
2775
2647
|
const config = readVoluteConfig(mindDir2);
|
|
2776
2648
|
const relPath = config?.identity?.privateKey;
|
|
2777
2649
|
if (!relPath) return null;
|
|
2778
|
-
const fullPath =
|
|
2779
|
-
if (!
|
|
2780
|
-
return
|
|
2650
|
+
const fullPath = resolve10(mindDir2, relPath);
|
|
2651
|
+
if (!existsSync9(fullPath)) return null;
|
|
2652
|
+
return readFileSync7(fullPath, "utf-8");
|
|
2781
2653
|
}
|
|
2782
2654
|
function getPublicKey(mindDir2) {
|
|
2783
2655
|
const config = readVoluteConfig(mindDir2);
|
|
2784
2656
|
const relPath = config?.identity?.publicKey;
|
|
2785
2657
|
if (!relPath) return null;
|
|
2786
|
-
const fullPath =
|
|
2787
|
-
if (!
|
|
2788
|
-
return
|
|
2658
|
+
const fullPath = resolve10(mindDir2, relPath);
|
|
2659
|
+
if (!existsSync9(fullPath)) return null;
|
|
2660
|
+
return readFileSync7(fullPath, "utf-8");
|
|
2789
2661
|
}
|
|
2790
2662
|
function getFingerprint(publicKeyPem) {
|
|
2791
2663
|
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
@@ -2838,16 +2710,16 @@ var keys_default = app8;
|
|
|
2838
2710
|
|
|
2839
2711
|
// src/web/api/logs.ts
|
|
2840
2712
|
import { spawn as spawn2 } from "child_process";
|
|
2841
|
-
import { existsSync as
|
|
2842
|
-
import { resolve as
|
|
2713
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2714
|
+
import { resolve as resolve11 } from "path";
|
|
2843
2715
|
import { Hono as Hono9 } from "hono";
|
|
2844
2716
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
2845
2717
|
var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
2846
2718
|
const name = c.req.param("name");
|
|
2847
2719
|
const entry = await findMind(name);
|
|
2848
2720
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2849
|
-
const logFile =
|
|
2850
|
-
if (!
|
|
2721
|
+
const logFile = resolve11(stateDir(name), "logs", "mind.log");
|
|
2722
|
+
if (!existsSync10(logFile)) {
|
|
2851
2723
|
return c.json({ error: "No log file found" }, 404);
|
|
2852
2724
|
}
|
|
2853
2725
|
return streamSSE2(c, async (stream) => {
|
|
@@ -2865,17 +2737,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2865
2737
|
stream.onAbort(() => {
|
|
2866
2738
|
tail.kill();
|
|
2867
2739
|
});
|
|
2868
|
-
await new Promise((
|
|
2869
|
-
tail.on("exit",
|
|
2870
|
-
stream.onAbort(
|
|
2740
|
+
await new Promise((resolve23) => {
|
|
2741
|
+
tail.on("exit", resolve23);
|
|
2742
|
+
stream.onAbort(resolve23);
|
|
2871
2743
|
});
|
|
2872
2744
|
});
|
|
2873
2745
|
}).get("/:name/logs/tail", async (c) => {
|
|
2874
2746
|
const name = c.req.param("name");
|
|
2875
2747
|
const entry = await findMind(name);
|
|
2876
2748
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2877
|
-
const logFile =
|
|
2878
|
-
if (!
|
|
2749
|
+
const logFile = resolve11(stateDir(name), "logs", "mind.log");
|
|
2750
|
+
if (!existsSync10(logFile)) {
|
|
2879
2751
|
return c.json({ error: "No log file found" }, 404);
|
|
2880
2752
|
}
|
|
2881
2753
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -2885,8 +2757,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2885
2757
|
tail.stdout.on("data", (data) => {
|
|
2886
2758
|
output += data.toString();
|
|
2887
2759
|
});
|
|
2888
|
-
await new Promise((
|
|
2889
|
-
tail.on("exit",
|
|
2760
|
+
await new Promise((resolve23) => {
|
|
2761
|
+
tail.on("exit", resolve23);
|
|
2890
2762
|
});
|
|
2891
2763
|
return c.text(output);
|
|
2892
2764
|
});
|
|
@@ -2905,7 +2777,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
|
2905
2777
|
return c.json(skills);
|
|
2906
2778
|
}).post(
|
|
2907
2779
|
"/:name/skills/install",
|
|
2908
|
-
|
|
2780
|
+
requireSelf(),
|
|
2909
2781
|
zValidator3("json", z3.object({ skillId: z3.string() })),
|
|
2910
2782
|
async (c) => {
|
|
2911
2783
|
const name = c.req.param("name");
|
|
@@ -2923,7 +2795,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
|
2923
2795
|
}
|
|
2924
2796
|
).post(
|
|
2925
2797
|
"/:name/skills/update",
|
|
2926
|
-
|
|
2798
|
+
requireSelf(),
|
|
2927
2799
|
zValidator3("json", z3.object({ skillId: z3.string() })),
|
|
2928
2800
|
async (c) => {
|
|
2929
2801
|
const name = c.req.param("name");
|
|
@@ -2941,7 +2813,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
|
2941
2813
|
}
|
|
2942
2814
|
).post(
|
|
2943
2815
|
"/:name/skills/publish",
|
|
2944
|
-
|
|
2816
|
+
requireSelf(),
|
|
2945
2817
|
zValidator3("json", z3.object({ skillId: z3.string() })),
|
|
2946
2818
|
async (c) => {
|
|
2947
2819
|
const name = c.req.param("name");
|
|
@@ -2957,7 +2829,7 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
|
2957
2829
|
return c.json({ error: msg }, 400);
|
|
2958
2830
|
}
|
|
2959
2831
|
}
|
|
2960
|
-
).delete("/:name/skills/:skill",
|
|
2832
|
+
).delete("/:name/skills/:skill", requireSelf(), async (c) => {
|
|
2961
2833
|
const name = c.req.param("name");
|
|
2962
2834
|
const skillName = c.req.param("skill");
|
|
2963
2835
|
const entry = await findMind(name);
|
|
@@ -2976,33 +2848,33 @@ var mind_skills_default = app10;
|
|
|
2976
2848
|
// src/web/api/minds.ts
|
|
2977
2849
|
import {
|
|
2978
2850
|
cpSync,
|
|
2979
|
-
existsSync as
|
|
2980
|
-
mkdirSync as
|
|
2981
|
-
readdirSync as
|
|
2982
|
-
readFileSync as
|
|
2983
|
-
rmSync as
|
|
2984
|
-
writeFileSync as
|
|
2851
|
+
existsSync as existsSync13,
|
|
2852
|
+
mkdirSync as mkdirSync8,
|
|
2853
|
+
readdirSync as readdirSync3,
|
|
2854
|
+
readFileSync as readFileSync11,
|
|
2855
|
+
rmSync as rmSync4,
|
|
2856
|
+
writeFileSync as writeFileSync8
|
|
2985
2857
|
} from "fs";
|
|
2986
|
-
import { resolve as
|
|
2858
|
+
import { resolve as resolve15 } from "path";
|
|
2987
2859
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
2988
|
-
import { and as
|
|
2860
|
+
import { and as and3, desc as desc2, eq as eq4, sql } from "drizzle-orm";
|
|
2989
2861
|
import { Hono as Hono11 } from "hono";
|
|
2990
2862
|
import { z as z4 } from "zod";
|
|
2991
2863
|
|
|
2992
2864
|
// src/lib/consolidate.ts
|
|
2993
|
-
import { readdirSync as
|
|
2994
|
-
import { resolve as
|
|
2865
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
2866
|
+
import { resolve as resolve12 } from "path";
|
|
2995
2867
|
async function consolidateMemory(mindDir2) {
|
|
2996
|
-
const soulPath =
|
|
2997
|
-
const memoryPath =
|
|
2998
|
-
const memoryDir =
|
|
2999
|
-
const soul =
|
|
2868
|
+
const soulPath = resolve12(mindDir2, "home/SOUL.md");
|
|
2869
|
+
const memoryPath = resolve12(mindDir2, "home/MEMORY.md");
|
|
2870
|
+
const memoryDir = resolve12(mindDir2, "home/memory");
|
|
2871
|
+
const soul = readFileSync8(soulPath, "utf-8");
|
|
3000
2872
|
const logs = [];
|
|
3001
2873
|
try {
|
|
3002
|
-
const files =
|
|
2874
|
+
const files = readdirSync2(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
3003
2875
|
for (const filename of files) {
|
|
3004
2876
|
const date = filename.replace(".md", "");
|
|
3005
|
-
const content2 =
|
|
2877
|
+
const content2 = readFileSync8(resolve12(memoryDir, filename), "utf-8").trim();
|
|
3006
2878
|
if (content2) {
|
|
3007
2879
|
logs.push(`### ${date}
|
|
3008
2880
|
|
|
@@ -3052,7 +2924,7 @@ ${content2}`);
|
|
|
3052
2924
|
const data = await res.json();
|
|
3053
2925
|
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
3054
2926
|
if (content) {
|
|
3055
|
-
|
|
2927
|
+
writeFileSync5(memoryPath, `${content}
|
|
3056
2928
|
`);
|
|
3057
2929
|
console.log("MEMORY.md created successfully.");
|
|
3058
2930
|
} else {
|
|
@@ -3062,11 +2934,11 @@ ${content2}`);
|
|
|
3062
2934
|
|
|
3063
2935
|
// src/lib/convert-session.ts
|
|
3064
2936
|
import { randomUUID } from "crypto";
|
|
3065
|
-
import { mkdirSync as
|
|
2937
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
3066
2938
|
import { homedir } from "os";
|
|
3067
|
-
import { resolve as
|
|
2939
|
+
import { resolve as resolve13 } from "path";
|
|
3068
2940
|
function convertSession(opts) {
|
|
3069
|
-
const lines =
|
|
2941
|
+
const lines = readFileSync9(opts.sessionPath, "utf-8").trim().split("\n");
|
|
3070
2942
|
const sessionId = randomUUID();
|
|
3071
2943
|
const idMap = /* @__PURE__ */ new Map();
|
|
3072
2944
|
const messages = [];
|
|
@@ -3180,10 +3052,10 @@ function convertSession(opts) {
|
|
|
3180
3052
|
}
|
|
3181
3053
|
}
|
|
3182
3054
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
3183
|
-
const sdkDir =
|
|
3184
|
-
|
|
3185
|
-
const sdkPath =
|
|
3186
|
-
|
|
3055
|
+
const sdkDir = resolve13(homedir(), ".claude", "projects", projectId);
|
|
3056
|
+
mkdirSync6(sdkDir, { recursive: true });
|
|
3057
|
+
const sdkPath = resolve13(sdkDir, `${sessionId}.jsonl`);
|
|
3058
|
+
writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
|
|
3187
3059
|
`);
|
|
3188
3060
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
3189
3061
|
return sessionId;
|
|
@@ -3249,7 +3121,7 @@ async function checkHealth(port) {
|
|
|
3249
3121
|
}
|
|
3250
3122
|
|
|
3251
3123
|
// src/lib/variant-cleanup.ts
|
|
3252
|
-
import { existsSync as
|
|
3124
|
+
import { existsSync as existsSync11, rmSync as rmSync3 } from "fs";
|
|
3253
3125
|
async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
|
|
3254
3126
|
if (opts?.stop) {
|
|
3255
3127
|
try {
|
|
@@ -3261,11 +3133,11 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
|
|
|
3261
3133
|
const { findMind: findMind2 } = await import("./registry-NDNOOYG4.js");
|
|
3262
3134
|
const variantEntry = await findMind2(variantName);
|
|
3263
3135
|
const branchName = variantEntry?.branch ?? variantName;
|
|
3264
|
-
if (
|
|
3136
|
+
if (existsSync11(variantPath)) {
|
|
3265
3137
|
try {
|
|
3266
3138
|
await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
|
|
3267
3139
|
} catch {
|
|
3268
|
-
|
|
3140
|
+
rmSync3(variantPath, { recursive: true, force: true });
|
|
3269
3141
|
try {
|
|
3270
3142
|
await gitExec(["worktree", "prune"], { cwd: projectRoot });
|
|
3271
3143
|
} catch (err) {
|
|
@@ -3295,8 +3167,8 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
|
|
|
3295
3167
|
}
|
|
3296
3168
|
|
|
3297
3169
|
// src/lib/variants.ts
|
|
3298
|
-
import { existsSync as
|
|
3299
|
-
import { resolve as
|
|
3170
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
3171
|
+
import { resolve as resolve14 } from "path";
|
|
3300
3172
|
async function checkHealth2(port) {
|
|
3301
3173
|
try {
|
|
3302
3174
|
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
@@ -3325,8 +3197,8 @@ async function getMindStatus(name, port) {
|
|
|
3325
3197
|
const manager = getMindManager();
|
|
3326
3198
|
let status = "stopped";
|
|
3327
3199
|
try {
|
|
3328
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3329
|
-
if (
|
|
3200
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
3201
|
+
if (getSleepManagerIfReady2()?.isSleeping(name)) {
|
|
3330
3202
|
status = "sleeping";
|
|
3331
3203
|
}
|
|
3332
3204
|
} catch {
|
|
@@ -3373,7 +3245,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
|
|
|
3373
3245
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
3374
3246
|
}
|
|
3375
3247
|
async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
3376
|
-
const tempWorktree =
|
|
3248
|
+
const tempWorktree = resolve15(projectRoot, ".variants", "_template_update");
|
|
3377
3249
|
let branchExists = false;
|
|
3378
3250
|
try {
|
|
3379
3251
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -3384,8 +3256,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
3384
3256
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
3385
3257
|
} catch {
|
|
3386
3258
|
}
|
|
3387
|
-
if (
|
|
3388
|
-
|
|
3259
|
+
if (existsSync13(tempWorktree)) {
|
|
3260
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
3389
3261
|
}
|
|
3390
3262
|
const templatesRoot = findTemplatesRoot();
|
|
3391
3263
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -3405,9 +3277,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
3405
3277
|
});
|
|
3406
3278
|
}
|
|
3407
3279
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
3408
|
-
const initDir =
|
|
3409
|
-
if (
|
|
3410
|
-
|
|
3280
|
+
const initDir = resolve15(tempWorktree, ".init");
|
|
3281
|
+
if (existsSync13(initDir)) {
|
|
3282
|
+
rmSync4(initDir, { recursive: true, force: true });
|
|
3411
3283
|
}
|
|
3412
3284
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
3413
3285
|
try {
|
|
@@ -3420,10 +3292,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
3420
3292
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
3421
3293
|
} catch {
|
|
3422
3294
|
}
|
|
3423
|
-
if (
|
|
3424
|
-
|
|
3295
|
+
if (existsSync13(tempWorktree)) {
|
|
3296
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
3425
3297
|
}
|
|
3426
|
-
|
|
3298
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3427
3299
|
}
|
|
3428
3300
|
}
|
|
3429
3301
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -3446,14 +3318,14 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
3446
3318
|
async function npmInstallAsMind(cwd, mindName) {
|
|
3447
3319
|
if (isIsolationEnabled()) {
|
|
3448
3320
|
const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
|
|
3449
|
-
await exec(cmd, args, { cwd, env: { ...process.env, HOME:
|
|
3321
|
+
await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve15(cwd, "home") } });
|
|
3450
3322
|
} else {
|
|
3451
3323
|
await exec("npm", ["install"], { cwd });
|
|
3452
3324
|
}
|
|
3453
3325
|
}
|
|
3454
3326
|
async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
3455
|
-
const extractedMindDir =
|
|
3456
|
-
if (!
|
|
3327
|
+
const extractedMindDir = resolve15(tempDir, "mind");
|
|
3328
|
+
if (!existsSync13(extractedMindDir)) {
|
|
3457
3329
|
return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
|
|
3458
3330
|
}
|
|
3459
3331
|
if (!manifest?.includes || !manifest.name || !manifest.template) {
|
|
@@ -3471,21 +3343,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3471
3343
|
if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3472
3344
|
ensureVoluteHome();
|
|
3473
3345
|
const dest = mindDir(name);
|
|
3474
|
-
if (
|
|
3346
|
+
if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3475
3347
|
try {
|
|
3476
3348
|
cpSync(extractedMindDir, dest, { recursive: true });
|
|
3477
3349
|
if (!manifest.includes.identity) {
|
|
3478
3350
|
generateIdentity(dest);
|
|
3479
3351
|
}
|
|
3480
3352
|
const state = stateDir(name);
|
|
3481
|
-
|
|
3482
|
-
const channelsJson =
|
|
3483
|
-
if (
|
|
3484
|
-
cpSync(channelsJson,
|
|
3353
|
+
mkdirSync8(state, { recursive: true });
|
|
3354
|
+
const channelsJson = resolve15(tempDir, "state/channels.json");
|
|
3355
|
+
if (existsSync13(channelsJson)) {
|
|
3356
|
+
cpSync(channelsJson, resolve15(state, "channels.json"));
|
|
3485
3357
|
}
|
|
3486
|
-
const envJson =
|
|
3487
|
-
if (
|
|
3488
|
-
cpSync(envJson,
|
|
3358
|
+
const envJson = resolve15(tempDir, "state/env.json");
|
|
3359
|
+
if (existsSync13(envJson)) {
|
|
3360
|
+
cpSync(envJson, resolve15(state, "env.json"));
|
|
3489
3361
|
}
|
|
3490
3362
|
const port = await nextPort();
|
|
3491
3363
|
await addMind(name, port, manifest.stage, manifest.template);
|
|
@@ -3494,36 +3366,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3494
3366
|
} catch (err) {
|
|
3495
3367
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3496
3368
|
}
|
|
3497
|
-
const homeDir =
|
|
3369
|
+
const homeDir = resolve15(dest, "home");
|
|
3498
3370
|
ensureVoluteGroup();
|
|
3499
3371
|
createMindUser(name, homeDir);
|
|
3500
3372
|
chownMindDir(dest, name);
|
|
3501
3373
|
await npmInstallAsMind(dest, name);
|
|
3502
3374
|
await importHistoryFromArchive(name, tempDir);
|
|
3503
3375
|
importSessionsFromArchive(dest, tempDir);
|
|
3504
|
-
if (!
|
|
3376
|
+
if (!existsSync13(resolve15(dest, ".git"))) {
|
|
3505
3377
|
try {
|
|
3506
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3378
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
|
|
3507
3379
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3508
3380
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3509
3381
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3510
3382
|
await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
|
|
3511
3383
|
} catch (err) {
|
|
3512
3384
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3513
|
-
|
|
3385
|
+
rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
|
|
3514
3386
|
}
|
|
3515
3387
|
}
|
|
3516
3388
|
chownMindDir(dest, name);
|
|
3517
|
-
|
|
3389
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3518
3390
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3519
3391
|
} catch (err) {
|
|
3520
|
-
if (
|
|
3392
|
+
if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3521
3393
|
try {
|
|
3522
3394
|
await removeMind(name);
|
|
3523
3395
|
} catch (cleanupErr) {
|
|
3524
3396
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3525
3397
|
}
|
|
3526
|
-
|
|
3398
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3527
3399
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3528
3400
|
}
|
|
3529
3401
|
}
|
|
@@ -3534,7 +3406,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3534
3406
|
if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3535
3407
|
ensureVoluteHome();
|
|
3536
3408
|
const dest = mindDir(name);
|
|
3537
|
-
if (
|
|
3409
|
+
if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3538
3410
|
const templatesRoot = findTemplatesRoot();
|
|
3539
3411
|
const { composedDir, manifest: templateManifest } = composeTemplate(
|
|
3540
3412
|
templatesRoot,
|
|
@@ -3543,40 +3415,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3543
3415
|
try {
|
|
3544
3416
|
copyTemplateToDir(composedDir, dest, name, templateManifest);
|
|
3545
3417
|
applyInitFiles(dest);
|
|
3546
|
-
const extractedHome =
|
|
3547
|
-
if (
|
|
3548
|
-
cpSync(extractedHome,
|
|
3418
|
+
const extractedHome = resolve15(extractedMindDir, "home");
|
|
3419
|
+
if (existsSync13(extractedHome)) {
|
|
3420
|
+
cpSync(extractedHome, resolve15(dest, "home"), { recursive: true });
|
|
3549
3421
|
}
|
|
3550
|
-
const extractedMindInternal =
|
|
3551
|
-
if (
|
|
3552
|
-
cpSync(extractedMindInternal,
|
|
3422
|
+
const extractedMindInternal = resolve15(extractedMindDir, ".mind");
|
|
3423
|
+
if (existsSync13(extractedMindInternal)) {
|
|
3424
|
+
cpSync(extractedMindInternal, resolve15(dest, ".mind"), { recursive: true });
|
|
3553
3425
|
}
|
|
3554
|
-
const identityDir =
|
|
3426
|
+
const identityDir = resolve15(dest, ".mind/identity");
|
|
3555
3427
|
let publicKeyPem;
|
|
3556
|
-
if (!manifest.includes.identity || !
|
|
3428
|
+
if (!manifest.includes.identity || !existsSync13(resolve15(identityDir, "private.pem"))) {
|
|
3557
3429
|
({ publicKeyPem } = generateIdentity(dest));
|
|
3558
3430
|
} else {
|
|
3559
|
-
publicKeyPem =
|
|
3431
|
+
publicKeyPem = readFileSync11(resolve15(identityDir, "public.pem"), "utf-8");
|
|
3560
3432
|
}
|
|
3561
|
-
const promptsPath =
|
|
3562
|
-
if (!
|
|
3433
|
+
const promptsPath = resolve15(dest, "home/.config/prompts.json");
|
|
3434
|
+
if (!existsSync13(promptsPath)) {
|
|
3563
3435
|
const mindPrompts = await getMindPromptDefaults();
|
|
3564
|
-
|
|
3436
|
+
writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
|
|
3565
3437
|
`);
|
|
3566
3438
|
}
|
|
3567
3439
|
const state = stateDir(name);
|
|
3568
|
-
|
|
3569
|
-
const channelsJson =
|
|
3570
|
-
if (
|
|
3571
|
-
cpSync(channelsJson,
|
|
3440
|
+
mkdirSync8(state, { recursive: true });
|
|
3441
|
+
const channelsJson = resolve15(tempDir, "state/channels.json");
|
|
3442
|
+
if (existsSync13(channelsJson)) {
|
|
3443
|
+
cpSync(channelsJson, resolve15(state, "channels.json"));
|
|
3572
3444
|
}
|
|
3573
|
-
const envJson =
|
|
3574
|
-
if (
|
|
3575
|
-
cpSync(envJson,
|
|
3445
|
+
const envJson = resolve15(tempDir, "state/env.json");
|
|
3446
|
+
if (existsSync13(envJson)) {
|
|
3447
|
+
cpSync(envJson, resolve15(state, "env.json"));
|
|
3576
3448
|
}
|
|
3577
3449
|
const port = await nextPort();
|
|
3578
3450
|
await addMind(name, port, manifest.stage, manifest.template);
|
|
3579
|
-
const homeDir =
|
|
3451
|
+
const homeDir = resolve15(dest, "home");
|
|
3580
3452
|
ensureVoluteGroup();
|
|
3581
3453
|
createMindUser(name, homeDir);
|
|
3582
3454
|
chownMindDir(dest, name);
|
|
@@ -3589,7 +3461,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3589
3461
|
await initTemplateBranch(dest, composedDir, templateManifest, name, env);
|
|
3590
3462
|
} catch (err) {
|
|
3591
3463
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3592
|
-
|
|
3464
|
+
rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
|
|
3593
3465
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3594
3466
|
}
|
|
3595
3467
|
try {
|
|
@@ -3613,7 +3485,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3613
3485
|
publishPublicKey(name, publicKeyPem).catch(
|
|
3614
3486
|
(err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
|
|
3615
3487
|
);
|
|
3616
|
-
|
|
3488
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3617
3489
|
return c.json({
|
|
3618
3490
|
ok: true,
|
|
3619
3491
|
name,
|
|
@@ -3624,24 +3496,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3624
3496
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3625
3497
|
});
|
|
3626
3498
|
} catch (err) {
|
|
3627
|
-
if (
|
|
3499
|
+
if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3628
3500
|
try {
|
|
3629
3501
|
await removeMind(name);
|
|
3630
3502
|
} catch (cleanupErr) {
|
|
3631
3503
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3632
3504
|
}
|
|
3633
|
-
|
|
3505
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3634
3506
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3635
3507
|
} finally {
|
|
3636
|
-
|
|
3508
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3637
3509
|
}
|
|
3638
3510
|
}
|
|
3639
3511
|
async function importHistoryFromArchive(name, tempDir) {
|
|
3640
|
-
const historyJsonl =
|
|
3641
|
-
if (!
|
|
3512
|
+
const historyJsonl = resolve15(tempDir, "history.jsonl");
|
|
3513
|
+
if (!existsSync13(historyJsonl)) return;
|
|
3642
3514
|
try {
|
|
3643
3515
|
const db = await getDb();
|
|
3644
|
-
const lines =
|
|
3516
|
+
const lines = readFileSync11(historyJsonl, "utf-8").trim().split("\n");
|
|
3645
3517
|
let imported = 0;
|
|
3646
3518
|
let failed = 0;
|
|
3647
3519
|
for (const line of lines) {
|
|
@@ -3677,13 +3549,13 @@ async function importHistoryFromArchive(name, tempDir) {
|
|
|
3677
3549
|
}
|
|
3678
3550
|
}
|
|
3679
3551
|
function importSessionsFromArchive(dest, tempDir) {
|
|
3680
|
-
const sessionsDir =
|
|
3681
|
-
if (!
|
|
3552
|
+
const sessionsDir = resolve15(tempDir, "sessions");
|
|
3553
|
+
if (!existsSync13(sessionsDir)) return;
|
|
3682
3554
|
try {
|
|
3683
|
-
const destSessions =
|
|
3684
|
-
|
|
3685
|
-
for (const file of
|
|
3686
|
-
cpSync(
|
|
3555
|
+
const destSessions = resolve15(dest, ".mind/sessions");
|
|
3556
|
+
mkdirSync8(destSessions, { recursive: true });
|
|
3557
|
+
for (const file of readdirSync3(sessionsDir)) {
|
|
3558
|
+
cpSync(resolve15(sessionsDir, file), resolve15(destSessions, file));
|
|
3687
3559
|
}
|
|
3688
3560
|
} catch (err) {
|
|
3689
3561
|
logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
|
|
@@ -3706,7 +3578,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
|
|
|
3706
3578
|
if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3707
3579
|
ensureVoluteHome();
|
|
3708
3580
|
const dest = mindDir(name);
|
|
3709
|
-
if (
|
|
3581
|
+
if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3710
3582
|
const templatesRoot = findTemplatesRoot();
|
|
3711
3583
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3712
3584
|
try {
|
|
@@ -3739,15 +3611,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
|
|
|
3739
3611
|
writeVoluteConfig(dest, config);
|
|
3740
3612
|
}
|
|
3741
3613
|
if (body.model) {
|
|
3742
|
-
const
|
|
3743
|
-
const existing =
|
|
3614
|
+
const configPath = resolve15(dest, "home/.config/config.json");
|
|
3615
|
+
const existing = existsSync13(configPath) ? JSON.parse(readFileSync11(configPath, "utf-8")) : {};
|
|
3744
3616
|
existing.model = body.model;
|
|
3745
|
-
|
|
3617
|
+
writeFileSync8(configPath, `${JSON.stringify(existing, null, 2)}
|
|
3746
3618
|
`);
|
|
3747
3619
|
}
|
|
3748
3620
|
const mindPrompts = await getMindPromptDefaults();
|
|
3749
|
-
|
|
3750
|
-
|
|
3621
|
+
writeFileSync8(
|
|
3622
|
+
resolve15(dest, "home/.config/prompts.json"),
|
|
3751
3623
|
`${JSON.stringify(mindPrompts, null, 2)}
|
|
3752
3624
|
`
|
|
3753
3625
|
);
|
|
@@ -3758,7 +3630,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
|
|
|
3758
3630
|
} catch (err) {
|
|
3759
3631
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3760
3632
|
}
|
|
3761
|
-
const homeDir =
|
|
3633
|
+
const homeDir = resolve15(dest, "home");
|
|
3762
3634
|
ensureVoluteGroup();
|
|
3763
3635
|
createMindUser(name, homeDir);
|
|
3764
3636
|
chownMindDir(dest, name);
|
|
@@ -3771,7 +3643,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator4("json", createMindS
|
|
|
3771
3643
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
3772
3644
|
} catch (err) {
|
|
3773
3645
|
logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
|
|
3774
|
-
|
|
3646
|
+
rmSync4(resolve15(dest, ".git"), { recursive: true, force: true });
|
|
3775
3647
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3776
3648
|
}
|
|
3777
3649
|
try {
|
|
@@ -3786,7 +3658,7 @@ The human who planted you described you as: "${body.description}"
|
|
|
3786
3658
|
` : "";
|
|
3787
3659
|
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
3788
3660
|
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
3789
|
-
|
|
3661
|
+
writeFileSync8(resolve15(dest, "home/SOUL.md"), seedSoul);
|
|
3790
3662
|
}
|
|
3791
3663
|
const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
|
|
3792
3664
|
const skillWarnings = [];
|
|
@@ -3801,11 +3673,11 @@ The human who planted you described you as: "${body.description}"
|
|
|
3801
3673
|
if (body.stage !== "seed") {
|
|
3802
3674
|
const customSoul = await getPromptIfCustom("default_soul");
|
|
3803
3675
|
if (customSoul) {
|
|
3804
|
-
|
|
3676
|
+
writeFileSync8(resolve15(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3805
3677
|
}
|
|
3806
3678
|
const customMemory = await getPromptIfCustom("default_memory");
|
|
3807
3679
|
if (customMemory) {
|
|
3808
|
-
|
|
3680
|
+
writeFileSync8(resolve15(dest, "home/MEMORY.md"), customMemory);
|
|
3809
3681
|
}
|
|
3810
3682
|
}
|
|
3811
3683
|
publishPublicKey(name, publicKeyPem).catch(
|
|
@@ -3834,14 +3706,14 @@ The human who planted you described you as: "${body.description}"
|
|
|
3834
3706
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3835
3707
|
});
|
|
3836
3708
|
} catch (err) {
|
|
3837
|
-
if (
|
|
3709
|
+
if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3838
3710
|
try {
|
|
3839
3711
|
await removeMind(name);
|
|
3840
3712
|
} catch {
|
|
3841
3713
|
}
|
|
3842
3714
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
3843
3715
|
} finally {
|
|
3844
|
-
|
|
3716
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3845
3717
|
}
|
|
3846
3718
|
}).post("/import", requireAdmin, async (c) => {
|
|
3847
3719
|
let body;
|
|
@@ -3854,13 +3726,13 @@ The human who planted you described you as: "${body.description}"
|
|
|
3854
3726
|
return importFromArchive(c, body.archivePath, body.name, body.manifest);
|
|
3855
3727
|
}
|
|
3856
3728
|
const wsDir = body.workspacePath;
|
|
3857
|
-
if (!wsDir || !
|
|
3729
|
+
if (!wsDir || !existsSync13(resolve15(wsDir, "SOUL.md")) || !existsSync13(resolve15(wsDir, "IDENTITY.md"))) {
|
|
3858
3730
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
3859
3731
|
}
|
|
3860
|
-
const soul =
|
|
3861
|
-
const identity =
|
|
3862
|
-
const userPath =
|
|
3863
|
-
const user =
|
|
3732
|
+
const soul = readFileSync11(resolve15(wsDir, "SOUL.md"), "utf-8");
|
|
3733
|
+
const identity = readFileSync11(resolve15(wsDir, "IDENTITY.md"), "utf-8");
|
|
3734
|
+
const userPath = resolve15(wsDir, "USER.md");
|
|
3735
|
+
const user = existsSync13(userPath) ? readFileSync11(userPath, "utf-8") : "";
|
|
3864
3736
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
3865
3737
|
const template = body.template ?? "claude";
|
|
3866
3738
|
const nameErr = validateMindName(name);
|
|
@@ -3880,33 +3752,33 @@ ${user.trimEnd()}
|
|
|
3880
3752
|
` : "";
|
|
3881
3753
|
ensureVoluteHome();
|
|
3882
3754
|
const dest = mindDir(name);
|
|
3883
|
-
if (
|
|
3755
|
+
if (existsSync13(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3884
3756
|
const templatesRoot = findTemplatesRoot();
|
|
3885
3757
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3886
3758
|
try {
|
|
3887
3759
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
3888
3760
|
applyInitFiles(dest);
|
|
3889
3761
|
const { publicKeyPem: importPublicKey } = generateIdentity(dest);
|
|
3890
|
-
|
|
3891
|
-
const wsMemoryPath =
|
|
3892
|
-
const hasMemory =
|
|
3762
|
+
writeFileSync8(resolve15(dest, "home/SOUL.md"), mergedSoul);
|
|
3763
|
+
const wsMemoryPath = resolve15(wsDir, "MEMORY.md");
|
|
3764
|
+
const hasMemory = existsSync13(wsMemoryPath);
|
|
3893
3765
|
if (hasMemory) {
|
|
3894
|
-
const existingMemory =
|
|
3895
|
-
|
|
3896
|
-
|
|
3766
|
+
const existingMemory = readFileSync11(wsMemoryPath, "utf-8");
|
|
3767
|
+
writeFileSync8(
|
|
3768
|
+
resolve15(dest, "home/MEMORY.md"),
|
|
3897
3769
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
3898
3770
|
);
|
|
3899
3771
|
} else if (user) {
|
|
3900
|
-
|
|
3772
|
+
writeFileSync8(resolve15(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
3901
3773
|
`);
|
|
3902
3774
|
}
|
|
3903
|
-
const wsMemoryDir =
|
|
3775
|
+
const wsMemoryDir = resolve15(wsDir, "memory");
|
|
3904
3776
|
let dailyLogCount = 0;
|
|
3905
|
-
if (
|
|
3906
|
-
const destMemoryDir =
|
|
3907
|
-
const files =
|
|
3777
|
+
if (existsSync13(wsMemoryDir)) {
|
|
3778
|
+
const destMemoryDir = resolve15(dest, "home/memory");
|
|
3779
|
+
const files = readdirSync3(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
3908
3780
|
for (const file of files) {
|
|
3909
|
-
cpSync(
|
|
3781
|
+
cpSync(resolve15(wsMemoryDir, file), resolve15(destMemoryDir, file));
|
|
3910
3782
|
}
|
|
3911
3783
|
dailyLogCount = files.length;
|
|
3912
3784
|
}
|
|
@@ -3917,7 +3789,7 @@ ${user.trimEnd()}
|
|
|
3917
3789
|
} catch (err) {
|
|
3918
3790
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3919
3791
|
}
|
|
3920
|
-
const homeDir =
|
|
3792
|
+
const homeDir = resolve15(dest, "home");
|
|
3921
3793
|
ensureVoluteGroup();
|
|
3922
3794
|
createMindUser(name, homeDir);
|
|
3923
3795
|
chownMindDir(dest, name);
|
|
@@ -3925,20 +3797,20 @@ ${user.trimEnd()}
|
|
|
3925
3797
|
if (!hasMemory && dailyLogCount > 0) {
|
|
3926
3798
|
await consolidateMemory(dest);
|
|
3927
3799
|
}
|
|
3928
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3800
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dest, "home") } : void 0;
|
|
3929
3801
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3930
3802
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3931
3803
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3932
3804
|
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
|
|
3933
|
-
const sessionFile = body.sessionPath ?
|
|
3934
|
-
if (sessionFile &&
|
|
3805
|
+
const sessionFile = body.sessionPath ? resolve15(body.sessionPath) : findOpenClawSession(wsDir);
|
|
3806
|
+
if (sessionFile && existsSync13(sessionFile)) {
|
|
3935
3807
|
if (template === "pi") {
|
|
3936
3808
|
importPiSession(sessionFile, dest);
|
|
3937
3809
|
} else if (template === "claude") {
|
|
3938
3810
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
3939
|
-
const mindRuntimeDir =
|
|
3940
|
-
|
|
3941
|
-
|
|
3811
|
+
const mindRuntimeDir = resolve15(dest, ".mind");
|
|
3812
|
+
mkdirSync8(mindRuntimeDir, { recursive: true });
|
|
3813
|
+
writeFileSync8(resolve15(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
|
|
3942
3814
|
}
|
|
3943
3815
|
}
|
|
3944
3816
|
importOpenClawConnectors(name, dest);
|
|
@@ -3953,14 +3825,14 @@ ${user.trimEnd()}
|
|
|
3953
3825
|
);
|
|
3954
3826
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3955
3827
|
} catch (err) {
|
|
3956
|
-
if (
|
|
3828
|
+
if (existsSync13(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3957
3829
|
try {
|
|
3958
3830
|
await removeMind(name);
|
|
3959
3831
|
} catch {
|
|
3960
3832
|
}
|
|
3961
3833
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3962
3834
|
} finally {
|
|
3963
|
-
|
|
3835
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3964
3836
|
}
|
|
3965
3837
|
}).get("/", async (c) => {
|
|
3966
3838
|
const entries = await readRegistry();
|
|
@@ -3977,7 +3849,7 @@ ${user.trimEnd()}
|
|
|
3977
3849
|
const minds = await Promise.all(
|
|
3978
3850
|
entries.map(async (entry) => {
|
|
3979
3851
|
const mindStatus = await getMindStatus(entry.name, entry.port);
|
|
3980
|
-
const hasPages =
|
|
3852
|
+
const hasPages = existsSync13(resolve15(mindDir(entry.name), "home", "public", "pages"));
|
|
3981
3853
|
return {
|
|
3982
3854
|
...entry,
|
|
3983
3855
|
...mindStatus,
|
|
@@ -3996,7 +3868,7 @@ ${user.trimEnd()}
|
|
|
3996
3868
|
const entry = await findMind(name);
|
|
3997
3869
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3998
3870
|
const dir = entry.dir ?? mindDir(entry.parent ?? name);
|
|
3999
|
-
if (!
|
|
3871
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4000
3872
|
const mindStatus = await getMindStatus(name, entry.port);
|
|
4001
3873
|
const variants = await findVariants(name);
|
|
4002
3874
|
const manager = getMindManager();
|
|
@@ -4010,9 +3882,9 @@ ${user.trimEnd()}
|
|
|
4010
3882
|
return { name: s.name, port: s.port, status: variantStatus };
|
|
4011
3883
|
})
|
|
4012
3884
|
);
|
|
4013
|
-
const hasPages =
|
|
3885
|
+
const hasPages = existsSync13(resolve15(mindDir(name), "home", "public", "pages"));
|
|
4014
3886
|
return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
|
|
4015
|
-
}).post("/:name/start",
|
|
3887
|
+
}).post("/:name/start", requireSelf(), async (c) => {
|
|
4016
3888
|
const name = c.req.param("name");
|
|
4017
3889
|
const entry = await findMind(name);
|
|
4018
3890
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4021,7 +3893,7 @@ ${user.trimEnd()}
|
|
|
4021
3893
|
if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
|
|
4022
3894
|
} else {
|
|
4023
3895
|
const dir = mindDir(name);
|
|
4024
|
-
if (!
|
|
3896
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4025
3897
|
}
|
|
4026
3898
|
if (getMindManager().isRunning(name)) {
|
|
4027
3899
|
return c.json({ error: "Mind already running" }, 409);
|
|
@@ -4042,7 +3914,7 @@ ${user.trimEnd()}
|
|
|
4042
3914
|
if (!entry.dir) return c.json({ error: `Variant ${name} has no directory` }, 404);
|
|
4043
3915
|
} else {
|
|
4044
3916
|
const dir = mindDir(name);
|
|
4045
|
-
if (!
|
|
3917
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4046
3918
|
}
|
|
4047
3919
|
let context;
|
|
4048
3920
|
const contentType = c.req.header("content-type");
|
|
@@ -4057,8 +3929,8 @@ ${user.trimEnd()}
|
|
|
4057
3929
|
const manager = getMindManager();
|
|
4058
3930
|
try {
|
|
4059
3931
|
if (context?.type === "reload") {
|
|
4060
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4061
|
-
const sleepState =
|
|
3932
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
3933
|
+
const sleepState = getSleepManagerIfReady2()?.getState(name);
|
|
4062
3934
|
if (sleepState?.sleeping) {
|
|
4063
3935
|
logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
|
|
4064
3936
|
return c.json({ ok: true, deferred: true, port: targetPort });
|
|
@@ -4077,7 +3949,7 @@ ${user.trimEnd()}
|
|
|
4077
3949
|
const variantEntry = await findMind(mergeVariantName);
|
|
4078
3950
|
if (variantEntry && variantEntry.parent === baseName && variantEntry.dir && variantEntry.branch) {
|
|
4079
3951
|
const projectRoot = mindDir(baseName);
|
|
4080
|
-
if (
|
|
3952
|
+
if (existsSync13(variantEntry.dir)) {
|
|
4081
3953
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
|
|
4082
3954
|
if (status) {
|
|
4083
3955
|
try {
|
|
@@ -4119,7 +3991,7 @@ ${user.trimEnd()}
|
|
|
4119
3991
|
if (context?.type === "sprouted" && !entry.parent) {
|
|
4120
3992
|
try {
|
|
4121
3993
|
const db = await getDb();
|
|
4122
|
-
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(
|
|
3994
|
+
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq4(conversations.mind_name, baseName)).all();
|
|
4123
3995
|
for (const conv of activeConvs) {
|
|
4124
3996
|
await addMessage(conv.id, "assistant", "system", [
|
|
4125
3997
|
{ type: "text", text: "[seed has sprouted]" }
|
|
@@ -4134,7 +4006,7 @@ ${user.trimEnd()}
|
|
|
4134
4006
|
} catch (err) {
|
|
4135
4007
|
return c.json({ error: err instanceof Error ? err.message : "Failed to restart mind" }, 500);
|
|
4136
4008
|
}
|
|
4137
|
-
}).post("/:name/stop",
|
|
4009
|
+
}).post("/:name/stop", requireSelf(), async (c) => {
|
|
4138
4010
|
const name = c.req.param("name");
|
|
4139
4011
|
const entry = await findMind(name);
|
|
4140
4012
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4148,20 +4020,20 @@ ${user.trimEnd()}
|
|
|
4148
4020
|
} catch (err) {
|
|
4149
4021
|
return c.json({ error: err instanceof Error ? err.message : "Failed to stop mind" }, 500);
|
|
4150
4022
|
}
|
|
4151
|
-
}).get("/:name/sleep",
|
|
4023
|
+
}).get("/:name/sleep", requireSelf(), async (c) => {
|
|
4152
4024
|
const name = c.req.param("name");
|
|
4153
4025
|
const entry = await findMind(name);
|
|
4154
4026
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4155
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4156
|
-
const sm =
|
|
4027
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
4028
|
+
const sm = getSleepManagerIfReady2();
|
|
4157
4029
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
4158
4030
|
return c.json(sm.getState(name));
|
|
4159
|
-
}).post("/:name/sleep",
|
|
4031
|
+
}).post("/:name/sleep", requireSelf(), async (c) => {
|
|
4160
4032
|
const name = c.req.param("name");
|
|
4161
4033
|
const entry = await findMind(name);
|
|
4162
4034
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4163
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4164
|
-
const sm =
|
|
4035
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
4036
|
+
const sm = getSleepManagerIfReady2();
|
|
4165
4037
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
4166
4038
|
if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
|
|
4167
4039
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -4176,12 +4048,12 @@ ${user.trimEnd()}
|
|
|
4176
4048
|
(err) => logger_default.error(`failed to initiate sleep for ${name}`, logger_default.errorData(err))
|
|
4177
4049
|
);
|
|
4178
4050
|
return c.json({ ok: true });
|
|
4179
|
-
}).post("/:name/wake",
|
|
4051
|
+
}).post("/:name/wake", requireSelf(), async (c) => {
|
|
4180
4052
|
const name = c.req.param("name");
|
|
4181
4053
|
const entry = await findMind(name);
|
|
4182
4054
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4183
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4184
|
-
const sm =
|
|
4055
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
4056
|
+
const sm = getSleepManagerIfReady2();
|
|
4185
4057
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
4186
4058
|
const sleepState = sm.getState(name);
|
|
4187
4059
|
if (!sleepState.sleeping) return c.json({ error: "Mind is not sleeping" }, 409);
|
|
@@ -4191,16 +4063,16 @@ ${user.trimEnd()}
|
|
|
4191
4063
|
sm.initiateWake(name).catch((err) => logger_default.error(`failed to wake ${name}`, logger_default.errorData(err)));
|
|
4192
4064
|
}
|
|
4193
4065
|
return c.json({ ok: true });
|
|
4194
|
-
}).post("/:name/sleep/messages",
|
|
4066
|
+
}).post("/:name/sleep/messages", requireSelf(), async (c) => {
|
|
4195
4067
|
const name = c.req.param("name");
|
|
4196
4068
|
const entry = await findMind(name);
|
|
4197
4069
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4198
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4199
|
-
const sm =
|
|
4070
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
4071
|
+
const sm = getSleepManagerIfReady2();
|
|
4200
4072
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
4201
4073
|
const flushed = await sm.flushQueuedMessages(name);
|
|
4202
4074
|
return c.json({ ok: true, flushed });
|
|
4203
|
-
}).post("/:name/sprout",
|
|
4075
|
+
}).post("/:name/sprout", requireSelf(), async (c) => {
|
|
4204
4076
|
const name = c.req.param("name");
|
|
4205
4077
|
const entry = await findMind(name);
|
|
4206
4078
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4233,11 +4105,11 @@ ${user.trimEnd()}
|
|
|
4233
4105
|
await removeMind(name);
|
|
4234
4106
|
await deleteMindUser2(name);
|
|
4235
4107
|
const state = stateDir(name);
|
|
4236
|
-
if (
|
|
4237
|
-
|
|
4108
|
+
if (existsSync13(state)) {
|
|
4109
|
+
rmSync4(state, { recursive: true, force: true });
|
|
4238
4110
|
}
|
|
4239
|
-
if (force &&
|
|
4240
|
-
|
|
4111
|
+
if (force && existsSync13(dir)) {
|
|
4112
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
4241
4113
|
deleteMindUser(name);
|
|
4242
4114
|
}
|
|
4243
4115
|
fireWebhook({
|
|
@@ -4246,12 +4118,12 @@ ${user.trimEnd()}
|
|
|
4246
4118
|
data: { port: entry.port, stage: entry.stage, template: entry.template }
|
|
4247
4119
|
});
|
|
4248
4120
|
return c.json({ ok: true });
|
|
4249
|
-
}).post("/:name/upgrade",
|
|
4121
|
+
}).post("/:name/upgrade", requireSelf(), async (c) => {
|
|
4250
4122
|
const mindName = c.req.param("name");
|
|
4251
4123
|
const entry = await findMind(mindName);
|
|
4252
4124
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4253
4125
|
const dir = mindDir(mindName);
|
|
4254
|
-
if (!
|
|
4126
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4255
4127
|
let body = {};
|
|
4256
4128
|
try {
|
|
4257
4129
|
body = await c.req.json();
|
|
@@ -4260,16 +4132,16 @@ ${user.trimEnd()}
|
|
|
4260
4132
|
const template = body.template ?? entry.template ?? "claude";
|
|
4261
4133
|
const UPGRADE_BRANCH = "upgrade";
|
|
4262
4134
|
const upgradeVariantName = `${mindName}-upgrade`;
|
|
4263
|
-
const worktreeDir =
|
|
4135
|
+
const worktreeDir = resolve15(dir, ".variants", UPGRADE_BRANCH);
|
|
4264
4136
|
if (body.abort) {
|
|
4265
|
-
if (!
|
|
4137
|
+
if (!existsSync13(worktreeDir)) {
|
|
4266
4138
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
4267
4139
|
}
|
|
4268
4140
|
try {
|
|
4269
4141
|
try {
|
|
4270
|
-
const gitDirContent =
|
|
4142
|
+
const gitDirContent = readFileSync11(resolve15(worktreeDir, ".git"), "utf-8").trim();
|
|
4271
4143
|
const gitDir = gitDirContent.replace("gitdir: ", "");
|
|
4272
|
-
if (
|
|
4144
|
+
if (existsSync13(resolve15(gitDir, "MERGE_HEAD"))) {
|
|
4273
4145
|
await gitExec(["merge", "--abort"], { cwd: worktreeDir });
|
|
4274
4146
|
}
|
|
4275
4147
|
} catch {
|
|
@@ -4288,7 +4160,7 @@ ${user.trimEnd()}
|
|
|
4288
4160
|
}
|
|
4289
4161
|
}
|
|
4290
4162
|
if (body.continue) {
|
|
4291
|
-
if (!
|
|
4163
|
+
if (!existsSync13(worktreeDir)) {
|
|
4292
4164
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
4293
4165
|
}
|
|
4294
4166
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir });
|
|
@@ -4327,7 +4199,7 @@ ${user.trimEnd()}
|
|
|
4327
4199
|
}
|
|
4328
4200
|
}
|
|
4329
4201
|
if (body.accept) {
|
|
4330
|
-
if (!
|
|
4202
|
+
if (!existsSync13(worktreeDir)) {
|
|
4331
4203
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
4332
4204
|
}
|
|
4333
4205
|
const variantEntry = await findMind(upgradeVariantName);
|
|
@@ -4341,7 +4213,7 @@ ${user.trimEnd()}
|
|
|
4341
4213
|
await gitExec(["commit", "-m", "Auto-commit before upgrade merge"], {
|
|
4342
4214
|
cwd: worktreeDir
|
|
4343
4215
|
});
|
|
4344
|
-
} catch
|
|
4216
|
+
} catch {
|
|
4345
4217
|
return c.json({ error: "Failed to auto-commit upgrade changes before merge" }, 500);
|
|
4346
4218
|
}
|
|
4347
4219
|
}
|
|
@@ -4385,22 +4257,22 @@ ${user.trimEnd()}
|
|
|
4385
4257
|
}
|
|
4386
4258
|
return c.json({ ok: true });
|
|
4387
4259
|
}
|
|
4388
|
-
if (
|
|
4260
|
+
if (existsSync13(worktreeDir)) {
|
|
4389
4261
|
return c.json(
|
|
4390
4262
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
4391
4263
|
409
|
|
4392
4264
|
);
|
|
4393
4265
|
}
|
|
4394
|
-
if (!
|
|
4266
|
+
if (!existsSync13(resolve15(dir, ".git"))) {
|
|
4395
4267
|
try {
|
|
4396
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
4268
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve15(dir, "home") } : void 0;
|
|
4397
4269
|
await gitExec(["init"], { cwd: dir, mindName, env });
|
|
4398
4270
|
await configureGitIdentity(mindName, { cwd: dir, mindName, env });
|
|
4399
4271
|
await gitExec(["add", "-A"], { cwd: dir, mindName, env });
|
|
4400
4272
|
await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
|
|
4401
4273
|
chownMindDir(dir, mindName);
|
|
4402
4274
|
} catch (err) {
|
|
4403
|
-
|
|
4275
|
+
rmSync4(resolve15(dir, ".git"), { recursive: true, force: true });
|
|
4404
4276
|
return c.json(
|
|
4405
4277
|
{
|
|
4406
4278
|
error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -4414,7 +4286,7 @@ ${user.trimEnd()}
|
|
|
4414
4286
|
await gitExec(["branch", "-D", UPGRADE_BRANCH], { cwd: dir });
|
|
4415
4287
|
} catch {
|
|
4416
4288
|
}
|
|
4417
|
-
if (!
|
|
4289
|
+
if (!existsSync13(resolve15(dir, "home", "shared"))) {
|
|
4418
4290
|
try {
|
|
4419
4291
|
await addSharedWorktree(mindName, dir);
|
|
4420
4292
|
} catch (err) {
|
|
@@ -4425,9 +4297,9 @@ ${user.trimEnd()}
|
|
|
4425
4297
|
}
|
|
4426
4298
|
}
|
|
4427
4299
|
await updateTemplateBranch(dir, template, mindName);
|
|
4428
|
-
const parentDir =
|
|
4429
|
-
if (!
|
|
4430
|
-
|
|
4300
|
+
const parentDir = resolve15(dir, ".variants");
|
|
4301
|
+
if (!existsSync13(parentDir)) {
|
|
4302
|
+
mkdirSync8(parentDir, { recursive: true });
|
|
4431
4303
|
}
|
|
4432
4304
|
await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
|
|
4433
4305
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -4464,8 +4336,8 @@ ${user.trimEnd()}
|
|
|
4464
4336
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4465
4337
|
const baseName = entry.parent ?? name;
|
|
4466
4338
|
try {
|
|
4467
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4468
|
-
const sm =
|
|
4339
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
4340
|
+
const sm = getSleepManagerIfReady2();
|
|
4469
4341
|
if (sm?.isSleeping(baseName)) {
|
|
4470
4342
|
const body2 = await c.req.text();
|
|
4471
4343
|
let parsed2 = null;
|
|
@@ -4562,7 +4434,7 @@ ${user.trimEnd()}
|
|
|
4562
4434
|
if (seedEntry?.stage === "seed") {
|
|
4563
4435
|
try {
|
|
4564
4436
|
const db = await getDb();
|
|
4565
|
-
const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(
|
|
4437
|
+
const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq4(mindHistory.mind, baseName));
|
|
4566
4438
|
const msgCount = countResult[0]?.count ?? 0;
|
|
4567
4439
|
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
4568
4440
|
const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute mind sprout.]";
|
|
@@ -4596,6 +4468,34 @@ ${user.trimEnd()}
|
|
|
4596
4468
|
logger_default.error(`delivery failed for ${name}`, logger_default.errorData(err));
|
|
4597
4469
|
});
|
|
4598
4470
|
return c.json({ ok: true });
|
|
4471
|
+
}).get("/:name/conversations", async (c) => {
|
|
4472
|
+
const name = c.req.param("name");
|
|
4473
|
+
const entry = await findMind(name);
|
|
4474
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4475
|
+
const convs = await listConversationsForMind(name);
|
|
4476
|
+
return c.json(convs);
|
|
4477
|
+
}).get("/:name/conversations/:convId/messages", async (c) => {
|
|
4478
|
+
const name = c.req.param("name");
|
|
4479
|
+
const convId = c.req.param("convId");
|
|
4480
|
+
const entry = await findMind(name);
|
|
4481
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4482
|
+
const belongs = await isConversationForMind(name, convId);
|
|
4483
|
+
if (!belongs) {
|
|
4484
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
4485
|
+
}
|
|
4486
|
+
const beforeStr = c.req.query("before");
|
|
4487
|
+
const limitStr = c.req.query("limit");
|
|
4488
|
+
if (!beforeStr && !limitStr) {
|
|
4489
|
+
const msgs = await getMessages(convId);
|
|
4490
|
+
return c.json({ items: msgs, hasMore: false });
|
|
4491
|
+
}
|
|
4492
|
+
const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
|
|
4493
|
+
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4494
|
+
if (before !== void 0 && isNaN(before) || limit !== void 0 && isNaN(limit)) {
|
|
4495
|
+
return c.json({ error: "Invalid pagination parameters" }, 400);
|
|
4496
|
+
}
|
|
4497
|
+
const result = await getMessagesPaginated(convId, { before, limit });
|
|
4498
|
+
return c.json({ items: result.messages, hasMore: result.hasMore });
|
|
4599
4499
|
}).get("/:name/budget", async (c) => {
|
|
4600
4500
|
const name = c.req.param("name");
|
|
4601
4501
|
const baseName = await getBaseName(name);
|
|
@@ -4607,13 +4507,13 @@ ${user.trimEnd()}
|
|
|
4607
4507
|
const entry = await findMind(name);
|
|
4608
4508
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4609
4509
|
const dir = mindDir(name);
|
|
4610
|
-
if (!
|
|
4510
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4611
4511
|
let config = readVoluteConfig(dir);
|
|
4612
4512
|
if (!config && entry.template === "pi") {
|
|
4613
|
-
const piConfigPath =
|
|
4614
|
-
if (
|
|
4513
|
+
const piConfigPath = resolve15(dir, "home/.config/config.json");
|
|
4514
|
+
if (existsSync13(piConfigPath)) {
|
|
4615
4515
|
try {
|
|
4616
|
-
config = JSON.parse(
|
|
4516
|
+
config = JSON.parse(readFileSync11(piConfigPath, "utf-8"));
|
|
4617
4517
|
} catch {
|
|
4618
4518
|
}
|
|
4619
4519
|
}
|
|
@@ -4635,7 +4535,7 @@ ${user.trimEnd()}
|
|
|
4635
4535
|
});
|
|
4636
4536
|
}).put(
|
|
4637
4537
|
"/:name/config",
|
|
4638
|
-
|
|
4538
|
+
requireSelf(),
|
|
4639
4539
|
zValidator4(
|
|
4640
4540
|
"json",
|
|
4641
4541
|
z4.object({
|
|
@@ -4650,7 +4550,7 @@ ${user.trimEnd()}
|
|
|
4650
4550
|
const entry = await findMind(name);
|
|
4651
4551
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4652
4552
|
const dir = mindDir(name);
|
|
4653
|
-
if (!
|
|
4553
|
+
if (!existsSync13(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4654
4554
|
const body = c.req.valid("json");
|
|
4655
4555
|
const existing = readVoluteConfig(dir) ?? {};
|
|
4656
4556
|
if (body.model !== void 0) existing.model = body.model;
|
|
@@ -4835,18 +4735,18 @@ ${user.trimEnd()}
|
|
|
4835
4735
|
event_count: sql`COUNT(*)`,
|
|
4836
4736
|
message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
|
|
4837
4737
|
tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
|
|
4838
|
-
}).from(mindHistory).where(
|
|
4738
|
+
}).from(mindHistory).where(and3(eq4(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
|
|
4839
4739
|
return c.json(rows);
|
|
4840
4740
|
}).get("/:name/history/channels", async (c) => {
|
|
4841
4741
|
const name = c.req.param("name");
|
|
4842
4742
|
const db = await getDb();
|
|
4843
|
-
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(
|
|
4743
|
+
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
|
|
4844
4744
|
return c.json(rows.map((r) => r.channel));
|
|
4845
4745
|
}).get("/:name/history/export", async (c) => {
|
|
4846
4746
|
const name = c.req.param("name");
|
|
4847
4747
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
4848
4748
|
const db = await getDb();
|
|
4849
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4749
|
+
const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
|
|
4850
4750
|
return c.json(rows);
|
|
4851
4751
|
}).get("/:name/history", async (c) => {
|
|
4852
4752
|
const name = c.req.param("name");
|
|
@@ -4856,17 +4756,17 @@ ${user.trimEnd()}
|
|
|
4856
4756
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
4857
4757
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
4858
4758
|
const db = await getDb();
|
|
4859
|
-
const conditions = [
|
|
4759
|
+
const conditions = [eq4(mindHistory.mind, name)];
|
|
4860
4760
|
if (channel) {
|
|
4861
|
-
conditions.push(
|
|
4761
|
+
conditions.push(eq4(mindHistory.channel, channel));
|
|
4862
4762
|
}
|
|
4863
4763
|
if (session) {
|
|
4864
|
-
conditions.push(
|
|
4764
|
+
conditions.push(eq4(mindHistory.session, session));
|
|
4865
4765
|
}
|
|
4866
4766
|
if (!full) {
|
|
4867
4767
|
conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
4868
4768
|
}
|
|
4869
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4769
|
+
const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
|
|
4870
4770
|
return c.json(rows);
|
|
4871
4771
|
});
|
|
4872
4772
|
var minds_default = app11;
|
|
@@ -4877,11 +4777,11 @@ import { Hono as Hono12 } from "hono";
|
|
|
4877
4777
|
import { z as z5 } from "zod";
|
|
4878
4778
|
|
|
4879
4779
|
// src/lib/notes.ts
|
|
4880
|
-
import { and as
|
|
4780
|
+
import { and as and4, count, desc as desc3, eq as eq5, inArray, sql as sql2 } from "drizzle-orm";
|
|
4881
4781
|
async function createNote(authorId, title, content, replyToId) {
|
|
4882
4782
|
const db = await getDb();
|
|
4883
4783
|
let slug = slugify(title) || "untitled";
|
|
4884
|
-
const existing = await db.select({ slug: notes.slug }).from(notes).where(
|
|
4784
|
+
const existing = await db.select({ slug: notes.slug }).from(notes).where(eq5(notes.author_id, authorId)).all();
|
|
4885
4785
|
const existingSlugs = new Set(existing.map((r) => r.slug));
|
|
4886
4786
|
if (existingSlugs.has(slug)) {
|
|
4887
4787
|
let i = 2;
|
|
@@ -4889,7 +4789,7 @@ async function createNote(authorId, title, content, replyToId) {
|
|
|
4889
4789
|
slug = `${slug}-${i}`;
|
|
4890
4790
|
}
|
|
4891
4791
|
const [row] = await db.insert(notes).values({ author_id: authorId, title, slug, content, reply_to_id: replyToId ?? null }).returning();
|
|
4892
|
-
const author = await db.select().from(users).where(
|
|
4792
|
+
const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
|
|
4893
4793
|
return {
|
|
4894
4794
|
...row,
|
|
4895
4795
|
author_username: author?.username ?? "unknown",
|
|
@@ -4910,7 +4810,7 @@ async function getNote(authorUsername, slug) {
|
|
|
4910
4810
|
updated_at: notes.updated_at,
|
|
4911
4811
|
author_username: users.username,
|
|
4912
4812
|
author_display_name: users.display_name
|
|
4913
|
-
}).from(notes).innerJoin(users,
|
|
4813
|
+
}).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
|
|
4914
4814
|
if (!row) return null;
|
|
4915
4815
|
const comments = await getComments(row.id);
|
|
4916
4816
|
const reactions = await getReactions(row.id);
|
|
@@ -4920,7 +4820,7 @@ async function getNote(authorUsername, slug) {
|
|
|
4920
4820
|
title: notes.title,
|
|
4921
4821
|
slug: notes.slug,
|
|
4922
4822
|
author_username: users.username
|
|
4923
|
-
}).from(notes).innerJoin(users,
|
|
4823
|
+
}).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.id, row.reply_to_id)).get();
|
|
4924
4824
|
if (parent) {
|
|
4925
4825
|
reply_to = parent;
|
|
4926
4826
|
}
|
|
@@ -4930,7 +4830,7 @@ async function getNote(authorUsername, slug) {
|
|
|
4930
4830
|
slug: notes.slug,
|
|
4931
4831
|
title: notes.title,
|
|
4932
4832
|
created_at: notes.created_at
|
|
4933
|
-
}).from(notes).innerJoin(users,
|
|
4833
|
+
}).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(eq5(notes.reply_to_id, row.id)).orderBy(notes.created_at).all();
|
|
4934
4834
|
return { ...row, comment_count: comments.length, comments, reactions, reply_to, replies };
|
|
4935
4835
|
}
|
|
4936
4836
|
async function listNotes(opts) {
|
|
@@ -4939,7 +4839,7 @@ async function listNotes(opts) {
|
|
|
4939
4839
|
const offset = opts?.offset ?? 0;
|
|
4940
4840
|
const conditions = [];
|
|
4941
4841
|
if (opts?.authorUsername) {
|
|
4942
|
-
conditions.push(
|
|
4842
|
+
conditions.push(eq5(users.username, opts.authorUsername));
|
|
4943
4843
|
}
|
|
4944
4844
|
const rows = await db.select({
|
|
4945
4845
|
id: notes.id,
|
|
@@ -4952,7 +4852,7 @@ async function listNotes(opts) {
|
|
|
4952
4852
|
updated_at: notes.updated_at,
|
|
4953
4853
|
author_username: users.username,
|
|
4954
4854
|
author_display_name: users.display_name
|
|
4955
|
-
}).from(notes).innerJoin(users,
|
|
4855
|
+
}).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(conditions.length > 0 ? and4(...conditions) : void 0).orderBy(desc3(notes.created_at)).limit(limit).offset(offset).all();
|
|
4956
4856
|
const noteIds = rows.map((r) => r.id);
|
|
4957
4857
|
if (noteIds.length === 0) return [];
|
|
4958
4858
|
const commentCounts = await db.select({
|
|
@@ -4978,7 +4878,7 @@ async function listNotes(opts) {
|
|
|
4978
4878
|
title: notes.title,
|
|
4979
4879
|
slug: notes.slug,
|
|
4980
4880
|
author_username: users.username
|
|
4981
|
-
}).from(notes).innerJoin(users,
|
|
4881
|
+
}).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(inArray(notes.id, replyToIds)).all();
|
|
4982
4882
|
for (const parent of parents) {
|
|
4983
4883
|
replyToMap.set(parent.id, {
|
|
4984
4884
|
author_username: parent.author_username,
|
|
@@ -5000,12 +4900,12 @@ async function listNotes(opts) {
|
|
|
5000
4900
|
}
|
|
5001
4901
|
async function updateNote(authorUsername, slug, updates) {
|
|
5002
4902
|
const db = await getDb();
|
|
5003
|
-
const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users,
|
|
4903
|
+
const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
|
|
5004
4904
|
if (!existing) return null;
|
|
5005
4905
|
const set = { updated_at: sql2`(datetime('now'))` };
|
|
5006
4906
|
if (updates.title !== void 0) set.title = updates.title;
|
|
5007
4907
|
if (updates.content !== void 0) set.content = updates.content;
|
|
5008
|
-
await db.update(notes).set(set).where(
|
|
4908
|
+
await db.update(notes).set(set).where(eq5(notes.id, existing.id));
|
|
5009
4909
|
return getNote(authorUsername, slug).then((n) => {
|
|
5010
4910
|
if (!n) return null;
|
|
5011
4911
|
const { comments, replies, ...note } = n;
|
|
@@ -5014,15 +4914,15 @@ async function updateNote(authorUsername, slug, updates) {
|
|
|
5014
4914
|
}
|
|
5015
4915
|
async function deleteNote(authorUsername, slug, authorId) {
|
|
5016
4916
|
const db = await getDb();
|
|
5017
|
-
const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users,
|
|
4917
|
+
const existing = await db.select({ id: notes.id, author_id: notes.author_id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, authorUsername), eq5(notes.slug, slug))).get();
|
|
5018
4918
|
if (!existing || existing.author_id !== authorId) return false;
|
|
5019
|
-
await db.delete(notes).where(
|
|
4919
|
+
await db.delete(notes).where(eq5(notes.id, existing.id));
|
|
5020
4920
|
return true;
|
|
5021
4921
|
}
|
|
5022
4922
|
async function addComment(noteId, authorId, content) {
|
|
5023
4923
|
const db = await getDb();
|
|
5024
4924
|
const [row] = await db.insert(noteComments).values({ note_id: noteId, author_id: authorId, content }).returning();
|
|
5025
|
-
const author = await db.select().from(users).where(
|
|
4925
|
+
const author = await db.select().from(users).where(eq5(users.id, authorId)).get();
|
|
5026
4926
|
return {
|
|
5027
4927
|
...row,
|
|
5028
4928
|
author_username: author?.username ?? "unknown",
|
|
@@ -5039,26 +4939,26 @@ async function getComments(noteId) {
|
|
|
5039
4939
|
created_at: noteComments.created_at,
|
|
5040
4940
|
author_username: users.username,
|
|
5041
4941
|
author_display_name: users.display_name
|
|
5042
|
-
}).from(noteComments).innerJoin(users,
|
|
4942
|
+
}).from(noteComments).innerJoin(users, eq5(noteComments.author_id, users.id)).where(eq5(noteComments.note_id, noteId)).orderBy(noteComments.created_at).all();
|
|
5043
4943
|
}
|
|
5044
4944
|
async function deleteComment(commentId, authorId) {
|
|
5045
4945
|
const db = await getDb();
|
|
5046
|
-
const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(
|
|
4946
|
+
const existing = await db.select({ id: noteComments.id, author_id: noteComments.author_id }).from(noteComments).where(eq5(noteComments.id, commentId)).get();
|
|
5047
4947
|
if (!existing || existing.author_id !== authorId) return false;
|
|
5048
|
-
await db.delete(noteComments).where(
|
|
4948
|
+
await db.delete(noteComments).where(eq5(noteComments.id, commentId));
|
|
5049
4949
|
return true;
|
|
5050
4950
|
}
|
|
5051
4951
|
async function toggleReaction(noteId, userId, emoji) {
|
|
5052
4952
|
const db = await getDb();
|
|
5053
4953
|
const existing = await db.select({ id: noteReactions.id }).from(noteReactions).where(
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
4954
|
+
and4(
|
|
4955
|
+
eq5(noteReactions.note_id, noteId),
|
|
4956
|
+
eq5(noteReactions.user_id, userId),
|
|
4957
|
+
eq5(noteReactions.emoji, emoji)
|
|
5058
4958
|
)
|
|
5059
4959
|
).get();
|
|
5060
4960
|
if (existing) {
|
|
5061
|
-
await db.delete(noteReactions).where(
|
|
4961
|
+
await db.delete(noteReactions).where(eq5(noteReactions.id, existing.id));
|
|
5062
4962
|
return { added: false };
|
|
5063
4963
|
}
|
|
5064
4964
|
await db.insert(noteReactions).values({ note_id: noteId, user_id: userId, emoji });
|
|
@@ -5069,7 +4969,7 @@ async function getReactions(noteId) {
|
|
|
5069
4969
|
const rows = await db.select({
|
|
5070
4970
|
emoji: noteReactions.emoji,
|
|
5071
4971
|
username: users.username
|
|
5072
|
-
}).from(noteReactions).innerJoin(users,
|
|
4972
|
+
}).from(noteReactions).innerJoin(users, eq5(noteReactions.user_id, users.id)).where(eq5(noteReactions.note_id, noteId)).orderBy(noteReactions.emoji).all();
|
|
5073
4973
|
const grouped = /* @__PURE__ */ new Map();
|
|
5074
4974
|
for (const r of rows) {
|
|
5075
4975
|
if (!grouped.has(r.emoji)) grouped.set(r.emoji, []);
|
|
@@ -5085,7 +4985,7 @@ async function resolveNoteId(authorSlug) {
|
|
|
5085
4985
|
const [author, slug] = authorSlug.split("/", 2);
|
|
5086
4986
|
if (!author || !slug) return null;
|
|
5087
4987
|
const db = await getDb();
|
|
5088
|
-
const row = await db.select({ id: notes.id }).from(notes).innerJoin(users,
|
|
4988
|
+
const row = await db.select({ id: notes.id }).from(notes).innerJoin(users, eq5(notes.author_id, users.id)).where(and4(eq5(users.username, author), eq5(notes.slug, slug))).get();
|
|
5089
4989
|
return row?.id ?? null;
|
|
5090
4990
|
}
|
|
5091
4991
|
|
|
@@ -5185,7 +5085,7 @@ var notes_default = app12;
|
|
|
5185
5085
|
|
|
5186
5086
|
// src/web/api/pages.ts
|
|
5187
5087
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
5188
|
-
import { extname as extname3, resolve as
|
|
5088
|
+
import { extname as extname3, resolve as resolve16 } from "path";
|
|
5189
5089
|
import { Hono as Hono13 } from "hono";
|
|
5190
5090
|
var MIME_TYPES = {
|
|
5191
5091
|
".html": "text/html",
|
|
@@ -5207,17 +5107,17 @@ var app13 = new Hono13().get("/:name/*", async (c) => {
|
|
|
5207
5107
|
const name = c.req.param("name");
|
|
5208
5108
|
let pagesRoot;
|
|
5209
5109
|
if (name === "_system") {
|
|
5210
|
-
pagesRoot =
|
|
5110
|
+
pagesRoot = resolve16(voluteHome(), "shared", "pages");
|
|
5211
5111
|
} else {
|
|
5212
5112
|
if (!await findMind(name)) return c.text("Not found", 404);
|
|
5213
|
-
pagesRoot =
|
|
5113
|
+
pagesRoot = resolve16(mindDir(name), "home", "public", "pages");
|
|
5214
5114
|
}
|
|
5215
5115
|
const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
|
|
5216
|
-
const requestedPath =
|
|
5116
|
+
const requestedPath = resolve16(pagesRoot, wildcard.slice(1));
|
|
5217
5117
|
if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
|
|
5218
5118
|
let fileStat = await stat2(requestedPath).catch(() => null);
|
|
5219
5119
|
if (fileStat?.isDirectory()) {
|
|
5220
|
-
const indexPath =
|
|
5120
|
+
const indexPath = resolve16(requestedPath, "index.html");
|
|
5221
5121
|
fileStat = await stat2(indexPath).catch(() => null);
|
|
5222
5122
|
if (fileStat?.isFile()) {
|
|
5223
5123
|
const body = await readFile2(indexPath);
|
|
@@ -5237,7 +5137,7 @@ var pages_default = app13;
|
|
|
5237
5137
|
|
|
5238
5138
|
// src/web/api/prompts.ts
|
|
5239
5139
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
5240
|
-
import { eq as
|
|
5140
|
+
import { eq as eq6, sql as sql3 } from "drizzle-orm";
|
|
5241
5141
|
import { Hono as Hono14 } from "hono";
|
|
5242
5142
|
import { z as z6 } from "zod";
|
|
5243
5143
|
var app14 = new Hono14().get("/", async (c) => {
|
|
@@ -5281,20 +5181,20 @@ var app14 = new Hono14().get("/", async (c) => {
|
|
|
5281
5181
|
return c.json({ error: "Unknown prompt key" }, 404);
|
|
5282
5182
|
}
|
|
5283
5183
|
const db = await getDb();
|
|
5284
|
-
await db.delete(systemPrompts).where(
|
|
5184
|
+
await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
|
|
5285
5185
|
return c.json({ ok: true });
|
|
5286
5186
|
});
|
|
5287
5187
|
var prompts_default = app14;
|
|
5288
5188
|
|
|
5289
5189
|
// src/web/api/public-files.ts
|
|
5290
5190
|
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
5291
|
-
import { extname as extname4, resolve as
|
|
5191
|
+
import { extname as extname4, resolve as resolve17 } from "path";
|
|
5292
5192
|
import { Hono as Hono15 } from "hono";
|
|
5293
5193
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
5294
5194
|
async function resolvePublicRoot(name) {
|
|
5295
|
-
if (name === "_system") return
|
|
5195
|
+
if (name === "_system") return resolve17(voluteHome(), "shared");
|
|
5296
5196
|
if (!await findMind(name)) return null;
|
|
5297
|
-
return
|
|
5197
|
+
return resolve17(mindDir(name), "home", "public");
|
|
5298
5198
|
}
|
|
5299
5199
|
function hasDotSegment(relativePath) {
|
|
5300
5200
|
return relativePath.split("/").some((seg) => seg.startsWith("."));
|
|
@@ -5341,7 +5241,7 @@ var app15 = new Hono15().get("/:name/", async (c) => {
|
|
|
5341
5241
|
if (!publicRoot) return c.text("Not found", 404);
|
|
5342
5242
|
const wildcard = c.req.path.replace(`/public/${name}`, "") || "/";
|
|
5343
5243
|
const relativePath = wildcard.slice(1);
|
|
5344
|
-
const requestedPath =
|
|
5244
|
+
const requestedPath = resolve17(publicRoot, relativePath);
|
|
5345
5245
|
if (!requestedPath.startsWith(publicRoot)) return c.text("Forbidden", 403);
|
|
5346
5246
|
if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
|
|
5347
5247
|
let fileStat;
|
|
@@ -5388,25 +5288,60 @@ function writeSchedules(name, schedules) {
|
|
|
5388
5288
|
config.schedules = schedules.length > 0 ? schedules : void 0;
|
|
5389
5289
|
writeVoluteConfig(dir, config);
|
|
5390
5290
|
getScheduler().loadSchedules(name);
|
|
5291
|
+
getSleepManagerIfReady()?.invalidateSleepConfig(name);
|
|
5391
5292
|
fireWebhook({
|
|
5392
5293
|
event: "schedule_changed",
|
|
5393
5294
|
mind: name,
|
|
5394
5295
|
data: { schedules }
|
|
5395
5296
|
});
|
|
5396
5297
|
}
|
|
5397
|
-
var app16 = new Hono16().get("/:name/
|
|
5298
|
+
var app16 = new Hono16().get("/:name/clock/status", async (c) => {
|
|
5299
|
+
const name = c.req.param("name");
|
|
5300
|
+
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
5301
|
+
const sleepManager = getSleepManagerIfReady();
|
|
5302
|
+
const sleepState = sleepManager?.getState(name) ?? null;
|
|
5303
|
+
const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
|
|
5304
|
+
const schedules = readSchedules(name);
|
|
5305
|
+
const now = /* @__PURE__ */ new Date();
|
|
5306
|
+
const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
|
|
5307
|
+
const upcoming = [];
|
|
5308
|
+
for (const s of schedules) {
|
|
5309
|
+
if (!s.enabled) continue;
|
|
5310
|
+
if (s.fireAt) {
|
|
5311
|
+
const fireDate = new Date(s.fireAt);
|
|
5312
|
+
if (fireDate >= now && fireDate <= in24h) {
|
|
5313
|
+
upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
|
|
5314
|
+
}
|
|
5315
|
+
} else if (s.cron) {
|
|
5316
|
+
try {
|
|
5317
|
+
const interval = CronExpressionParser.parse(s.cron);
|
|
5318
|
+
const next = interval.next().toDate();
|
|
5319
|
+
if (next <= in24h) {
|
|
5320
|
+
upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
|
|
5321
|
+
}
|
|
5322
|
+
} catch {
|
|
5323
|
+
slog.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
upcoming.sort((a, b) => a.at.localeCompare(b.at));
|
|
5328
|
+
return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming });
|
|
5329
|
+
}).get("/:name/schedules", async (c) => {
|
|
5398
5330
|
const name = c.req.param("name");
|
|
5399
5331
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
5400
5332
|
return c.json(readSchedules(name));
|
|
5401
|
-
}).post("/:name/schedules",
|
|
5333
|
+
}).post("/:name/schedules", requireSelf(), async (c) => {
|
|
5402
5334
|
const name = c.req.param("name");
|
|
5403
5335
|
const entry = await findMind(name);
|
|
5404
5336
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
5405
5337
|
if (entry.stage === "seed")
|
|
5406
5338
|
return c.json({ error: "Seed minds cannot use schedules \u2014 sprout first" }, 403);
|
|
5407
5339
|
const body = await c.req.json();
|
|
5408
|
-
if (!body.cron) {
|
|
5409
|
-
return c.json({ error: "cron is required" }, 400);
|
|
5340
|
+
if (!body.cron && !body.fireAt) {
|
|
5341
|
+
return c.json({ error: "cron or fireAt is required" }, 400);
|
|
5342
|
+
}
|
|
5343
|
+
if (body.cron && body.fireAt) {
|
|
5344
|
+
return c.json({ error: "cron and fireAt are mutually exclusive" }, 400);
|
|
5410
5345
|
}
|
|
5411
5346
|
if (!body.message && !body.script) {
|
|
5412
5347
|
return c.json({ error: "message or script is required" }, 400);
|
|
@@ -5414,24 +5349,40 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
|
|
|
5414
5349
|
if (body.message && body.script) {
|
|
5415
5350
|
return c.json({ error: "message and script are mutually exclusive" }, 400);
|
|
5416
5351
|
}
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5352
|
+
if (body.cron) {
|
|
5353
|
+
try {
|
|
5354
|
+
CronExpressionParser.parse(body.cron);
|
|
5355
|
+
} catch {
|
|
5356
|
+
return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
if (body.fireAt && Number.isNaN(new Date(body.fireAt).getTime())) {
|
|
5360
|
+
return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
|
|
5361
|
+
}
|
|
5362
|
+
if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
|
|
5363
|
+
return c.json(
|
|
5364
|
+
{
|
|
5365
|
+
error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
|
|
5366
|
+
},
|
|
5367
|
+
400
|
|
5368
|
+
);
|
|
5421
5369
|
}
|
|
5422
5370
|
const schedules = readSchedules(name);
|
|
5423
5371
|
const id = body.id || `schedule-${Date.now()}`;
|
|
5424
5372
|
if (schedules.some((s) => s.id === id)) {
|
|
5425
5373
|
return c.json({ error: `Schedule "${id}" already exists` }, 409);
|
|
5426
5374
|
}
|
|
5427
|
-
const schedule = { id,
|
|
5375
|
+
const schedule = { id, enabled: body.enabled ?? true };
|
|
5376
|
+
if (body.cron) schedule.cron = body.cron;
|
|
5377
|
+
if (body.fireAt) schedule.fireAt = body.fireAt;
|
|
5428
5378
|
if (body.message) schedule.message = body.message;
|
|
5429
5379
|
if (body.script) schedule.script = body.script;
|
|
5430
5380
|
if (body.channel) schedule.channel = body.channel;
|
|
5381
|
+
if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
|
|
5431
5382
|
schedules.push(schedule);
|
|
5432
5383
|
writeSchedules(name, schedules);
|
|
5433
5384
|
return c.json({ ok: true, id }, 201);
|
|
5434
|
-
}).put("/:name/schedules/:id",
|
|
5385
|
+
}).put("/:name/schedules/:id", requireSelf(), async (c) => {
|
|
5435
5386
|
const name = c.req.param("name");
|
|
5436
5387
|
const id = c.req.param("id");
|
|
5437
5388
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -5449,6 +5400,14 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
|
|
|
5449
5400
|
return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
|
|
5450
5401
|
}
|
|
5451
5402
|
schedules[idx].cron = body.cron;
|
|
5403
|
+
delete schedules[idx].fireAt;
|
|
5404
|
+
}
|
|
5405
|
+
if (body.fireAt !== void 0) {
|
|
5406
|
+
if (Number.isNaN(new Date(body.fireAt).getTime())) {
|
|
5407
|
+
return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
|
|
5408
|
+
}
|
|
5409
|
+
schedules[idx].fireAt = body.fireAt;
|
|
5410
|
+
delete schedules[idx].cron;
|
|
5452
5411
|
}
|
|
5453
5412
|
if (body.message !== void 0) {
|
|
5454
5413
|
schedules[idx].message = body.message;
|
|
@@ -5458,11 +5417,21 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
|
|
|
5458
5417
|
schedules[idx].script = body.script;
|
|
5459
5418
|
delete schedules[idx].message;
|
|
5460
5419
|
}
|
|
5420
|
+
if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
|
|
5421
|
+
return c.json(
|
|
5422
|
+
{
|
|
5423
|
+
error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
|
|
5424
|
+
},
|
|
5425
|
+
400
|
|
5426
|
+
);
|
|
5427
|
+
}
|
|
5461
5428
|
if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
|
|
5462
5429
|
if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
|
|
5430
|
+
if (body.whileSleeping !== void 0)
|
|
5431
|
+
schedules[idx].whileSleeping = body.whileSleeping || void 0;
|
|
5463
5432
|
writeSchedules(name, schedules);
|
|
5464
5433
|
return c.json({ ok: true });
|
|
5465
|
-
}).delete("/:name/schedules/:id",
|
|
5434
|
+
}).delete("/:name/schedules/:id", requireSelf(), async (c) => {
|
|
5466
5435
|
const name = c.req.param("name");
|
|
5467
5436
|
const id = c.req.param("id");
|
|
5468
5437
|
if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -5520,44 +5489,13 @@ var app17 = new Hono17().post("/:name/shared/merge", requireAdmin, async (c) =>
|
|
|
5520
5489
|
} catch (err) {
|
|
5521
5490
|
return c.json({ error: err instanceof Error ? err.message : "Merge failed" }, 500);
|
|
5522
5491
|
}
|
|
5523
|
-
}).post("/:name/shared/pull", requireAdmin, async (c) => {
|
|
5524
|
-
const name = c.req.param("name");
|
|
5525
|
-
const entry = await findMind(name);
|
|
5526
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
5527
|
-
try {
|
|
5528
|
-
const result = await sharedPull(name, mindDir(name));
|
|
5529
|
-
return c.json(result);
|
|
5530
|
-
} catch (err) {
|
|
5531
|
-
return c.json({ error: err instanceof Error ? err.message : "Pull failed" }, 500);
|
|
5532
|
-
}
|
|
5533
|
-
}).get("/:name/shared/log", async (c) => {
|
|
5534
|
-
const name = c.req.param("name");
|
|
5535
|
-
const entry = await findMind(name);
|
|
5536
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
5537
|
-
const limit = parseInt(c.req.query("limit") ?? "20", 10) || 20;
|
|
5538
|
-
try {
|
|
5539
|
-
const log2 = await sharedLog(limit);
|
|
5540
|
-
return c.text(log2);
|
|
5541
|
-
} catch (err) {
|
|
5542
|
-
return c.json({ error: err instanceof Error ? err.message : "Failed to read log" }, 500);
|
|
5543
|
-
}
|
|
5544
|
-
}).get("/:name/shared/status", async (c) => {
|
|
5545
|
-
const name = c.req.param("name");
|
|
5546
|
-
const entry = await findMind(name);
|
|
5547
|
-
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
5548
|
-
try {
|
|
5549
|
-
const status = await sharedStatus(name);
|
|
5550
|
-
return c.text(status);
|
|
5551
|
-
} catch (err) {
|
|
5552
|
-
return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
|
|
5553
|
-
}
|
|
5554
5492
|
});
|
|
5555
5493
|
var shared_default = app17;
|
|
5556
5494
|
|
|
5557
5495
|
// src/web/api/skills.ts
|
|
5558
|
-
import { existsSync as
|
|
5496
|
+
import { existsSync as existsSync14, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5 } from "fs";
|
|
5559
5497
|
import { tmpdir } from "os";
|
|
5560
|
-
import { join
|
|
5498
|
+
import { join, resolve as resolve18 } from "path";
|
|
5561
5499
|
import AdmZip from "adm-zip";
|
|
5562
5500
|
import { Hono as Hono18 } from "hono";
|
|
5563
5501
|
var app18 = new Hono18().get("/", async (c) => {
|
|
@@ -5567,7 +5505,7 @@ var app18 = new Hono18().get("/", async (c) => {
|
|
|
5567
5505
|
const id = c.req.param("id");
|
|
5568
5506
|
const skill = await getSharedSkill(id);
|
|
5569
5507
|
if (!skill) return c.json({ error: "Skill not found" }, 404);
|
|
5570
|
-
const dir =
|
|
5508
|
+
const dir = join(sharedSkillsDir(), id);
|
|
5571
5509
|
const files = listFilesRecursive(dir);
|
|
5572
5510
|
return c.json({ ...skill, files });
|
|
5573
5511
|
}).post("/upload", requireAdmin, async (c) => {
|
|
@@ -5580,24 +5518,24 @@ var app18 = new Hono18().get("/", async (c) => {
|
|
|
5580
5518
|
return c.json({ error: "Only .zip files are accepted" }, 400);
|
|
5581
5519
|
}
|
|
5582
5520
|
const buffer2 = Buffer.from(await file.arrayBuffer());
|
|
5583
|
-
const tmpDir = mkdtempSync(
|
|
5521
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "volute-skill-upload-"));
|
|
5584
5522
|
try {
|
|
5585
5523
|
const zip = new AdmZip(buffer2);
|
|
5586
5524
|
for (const entry of zip.getEntries()) {
|
|
5587
|
-
const target =
|
|
5525
|
+
const target = resolve18(tmpDir, entry.entryName);
|
|
5588
5526
|
if (!target.startsWith(tmpDir)) {
|
|
5589
5527
|
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
5590
5528
|
}
|
|
5591
5529
|
}
|
|
5592
5530
|
zip.extractAllTo(tmpDir, true);
|
|
5593
5531
|
let skillDir = null;
|
|
5594
|
-
if (
|
|
5532
|
+
if (existsSync14(join(tmpDir, "SKILL.md"))) {
|
|
5595
5533
|
skillDir = tmpDir;
|
|
5596
5534
|
} else {
|
|
5597
|
-
const entries =
|
|
5535
|
+
const entries = readdirSync4(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5598
5536
|
for (const entry of entries) {
|
|
5599
|
-
if (
|
|
5600
|
-
skillDir =
|
|
5537
|
+
if (existsSync14(join(tmpDir, entry.name, "SKILL.md"))) {
|
|
5538
|
+
skillDir = join(tmpDir, entry.name);
|
|
5601
5539
|
break;
|
|
5602
5540
|
}
|
|
5603
5541
|
}
|
|
@@ -5613,7 +5551,7 @@ var app18 = new Hono18().get("/", async (c) => {
|
|
|
5613
5551
|
}
|
|
5614
5552
|
throw e;
|
|
5615
5553
|
} finally {
|
|
5616
|
-
|
|
5554
|
+
rmSync5(tmpDir, { recursive: true, force: true });
|
|
5617
5555
|
}
|
|
5618
5556
|
}).delete("/:id", requireAdmin, async (c) => {
|
|
5619
5557
|
const id = c.req.param("id");
|
|
@@ -5650,10 +5588,10 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
|
|
|
5650
5588
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
5651
5589
|
});
|
|
5652
5590
|
});
|
|
5653
|
-
await new Promise((
|
|
5591
|
+
await new Promise((resolve23) => {
|
|
5654
5592
|
stream.onAbort(() => {
|
|
5655
5593
|
unsubscribe();
|
|
5656
|
-
|
|
5594
|
+
resolve23();
|
|
5657
5595
|
});
|
|
5658
5596
|
});
|
|
5659
5597
|
});
|
|
@@ -5738,6 +5676,38 @@ var app19 = new Hono19().post("/restart", requireAdmin, (c) => {
|
|
|
5738
5676
|
).post("/logout", requireAdmin, (c) => {
|
|
5739
5677
|
deleteSystemsConfig();
|
|
5740
5678
|
return c.json({ ok: true });
|
|
5679
|
+
}).put("/pages/publish/:name", requireAdmin, async (c) => {
|
|
5680
|
+
const config = readSystemsConfig();
|
|
5681
|
+
if (!config) return c.json({ error: "Not connected to volute.systems" }, 400);
|
|
5682
|
+
const name = c.req.param("name");
|
|
5683
|
+
const body = await c.req.text();
|
|
5684
|
+
try {
|
|
5685
|
+
const res = await fetch(`${config.apiUrl}/api/pages/publish/${name}`, {
|
|
5686
|
+
method: "PUT",
|
|
5687
|
+
headers: {
|
|
5688
|
+
"Content-Type": "application/json",
|
|
5689
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
5690
|
+
},
|
|
5691
|
+
body
|
|
5692
|
+
});
|
|
5693
|
+
const data = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
5694
|
+
return c.json(data, res.status);
|
|
5695
|
+
} catch (err) {
|
|
5696
|
+
return c.json({ error: `Connection failed: ${err.message}` }, 502);
|
|
5697
|
+
}
|
|
5698
|
+
}).get("/pages/status/:name", requireAdmin, async (c) => {
|
|
5699
|
+
const config = readSystemsConfig();
|
|
5700
|
+
if (!config) return c.json({ error: "Not connected to volute.systems" }, 400);
|
|
5701
|
+
const name = c.req.param("name");
|
|
5702
|
+
try {
|
|
5703
|
+
const res = await fetch(`${config.apiUrl}/api/pages/status/${name}`, {
|
|
5704
|
+
headers: { Authorization: `Bearer ${config.apiKey}` }
|
|
5705
|
+
});
|
|
5706
|
+
const data = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
5707
|
+
return c.json(data, res.status);
|
|
5708
|
+
} catch (err) {
|
|
5709
|
+
return c.json({ error: `Connection failed: ${err.message}` }, 502);
|
|
5710
|
+
}
|
|
5741
5711
|
});
|
|
5742
5712
|
var system_default = app19;
|
|
5743
5713
|
|
|
@@ -5805,11 +5775,11 @@ async function fanOutToMinds(opts) {
|
|
|
5805
5775
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
5806
5776
|
const participantNames = participants.map((p) => p.username);
|
|
5807
5777
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
5808
|
-
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "
|
|
5809
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
5810
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
5778
|
+
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
|
|
5779
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
|
|
5780
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
5811
5781
|
const manager = getMindManager2();
|
|
5812
|
-
const sm =
|
|
5782
|
+
const sm = getSleepManagerIfReady2();
|
|
5813
5783
|
const targetMinds = mindParticipants.map((ap) => {
|
|
5814
5784
|
const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
|
|
5815
5785
|
if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
|
|
@@ -5928,7 +5898,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
5928
5898
|
senderName,
|
|
5929
5899
|
convTitle,
|
|
5930
5900
|
isDM,
|
|
5931
|
-
channelEntryType:
|
|
5901
|
+
channelEntryType: isDM ? "dm" : "channel",
|
|
5932
5902
|
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
5933
5903
|
targetName: (username) => username === baseName ? name : username
|
|
5934
5904
|
});
|
|
@@ -5950,11 +5920,11 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
5950
5920
|
if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
|
|
5951
5921
|
});
|
|
5952
5922
|
}, 15e3);
|
|
5953
|
-
await new Promise((
|
|
5923
|
+
await new Promise((resolve23) => {
|
|
5954
5924
|
stream.onAbort(() => {
|
|
5955
5925
|
unsubscribe();
|
|
5956
5926
|
clearInterval(keepAlive);
|
|
5957
|
-
|
|
5927
|
+
resolve23();
|
|
5958
5928
|
});
|
|
5959
5929
|
});
|
|
5960
5930
|
});
|
|
@@ -5985,7 +5955,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
5985
5955
|
senderName,
|
|
5986
5956
|
convTitle: conv.title,
|
|
5987
5957
|
isDM,
|
|
5988
|
-
channelEntryType:
|
|
5958
|
+
channelEntryType: isDM ? "dm" : "channel",
|
|
5989
5959
|
slugExtra: { convType: conv.type, convName: conv.name }
|
|
5990
5960
|
});
|
|
5991
5961
|
return c.json({ ok: true, conversationId: body.conversationId });
|
|
@@ -6222,8 +6192,8 @@ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
|
|
|
6222
6192
|
});
|
|
6223
6193
|
}, 15e3);
|
|
6224
6194
|
cleanups.push(() => clearInterval(keepAlive));
|
|
6225
|
-
await new Promise((
|
|
6226
|
-
stream.onAbort(() =>
|
|
6195
|
+
await new Promise((resolve23) => {
|
|
6196
|
+
stream.onAbort(() => resolve23());
|
|
6227
6197
|
});
|
|
6228
6198
|
} finally {
|
|
6229
6199
|
for (const cleanup of cleanups) {
|
|
@@ -6238,16 +6208,16 @@ var app24 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
|
|
|
6238
6208
|
var events_default = app24;
|
|
6239
6209
|
|
|
6240
6210
|
// src/web/api/variants.ts
|
|
6241
|
-
import { existsSync as
|
|
6242
|
-
import { resolve as
|
|
6211
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
6212
|
+
import { resolve as resolve20 } from "path";
|
|
6243
6213
|
import { Hono as Hono25 } from "hono";
|
|
6244
6214
|
|
|
6245
6215
|
// src/lib/spawn-server.ts
|
|
6246
6216
|
import { spawn as spawn4 } from "child_process";
|
|
6247
|
-
import { closeSync, mkdirSync as
|
|
6248
|
-
import { resolve as
|
|
6217
|
+
import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync12 } from "fs";
|
|
6218
|
+
import { resolve as resolve19 } from "path";
|
|
6249
6219
|
function tsxBin(cwd) {
|
|
6250
|
-
return
|
|
6220
|
+
return resolve19(cwd, "node_modules", ".bin", "tsx");
|
|
6251
6221
|
}
|
|
6252
6222
|
function spawnServer(cwd, port, options) {
|
|
6253
6223
|
if (options?.detached) {
|
|
@@ -6260,31 +6230,31 @@ function spawnAttached(cwd, port) {
|
|
|
6260
6230
|
cwd,
|
|
6261
6231
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6262
6232
|
});
|
|
6263
|
-
return new Promise((
|
|
6264
|
-
const timeout = setTimeout(() =>
|
|
6233
|
+
return new Promise((resolve23) => {
|
|
6234
|
+
const timeout = setTimeout(() => resolve23(null), 3e4);
|
|
6265
6235
|
function checkOutput(data) {
|
|
6266
6236
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
6267
6237
|
if (match) {
|
|
6268
6238
|
clearTimeout(timeout);
|
|
6269
|
-
|
|
6239
|
+
resolve23({ child, actualPort: parseInt(match[1], 10) });
|
|
6270
6240
|
}
|
|
6271
6241
|
}
|
|
6272
6242
|
child.stdout?.on("data", checkOutput);
|
|
6273
6243
|
child.stderr?.on("data", checkOutput);
|
|
6274
6244
|
child.on("error", () => {
|
|
6275
6245
|
clearTimeout(timeout);
|
|
6276
|
-
|
|
6246
|
+
resolve23(null);
|
|
6277
6247
|
});
|
|
6278
6248
|
child.on("exit", () => {
|
|
6279
6249
|
clearTimeout(timeout);
|
|
6280
|
-
|
|
6250
|
+
resolve23(null);
|
|
6281
6251
|
});
|
|
6282
6252
|
});
|
|
6283
6253
|
}
|
|
6284
6254
|
function spawnDetached(cwd, port, logDir) {
|
|
6285
|
-
const logsDir = logDir ??
|
|
6286
|
-
|
|
6287
|
-
const logPath =
|
|
6255
|
+
const logsDir = logDir ?? resolve19(cwd, ".mind", "logs");
|
|
6256
|
+
mkdirSync9(logsDir, { recursive: true });
|
|
6257
|
+
const logPath = resolve19(logsDir, "mind.log");
|
|
6288
6258
|
const logFd = openSync(logPath, "a");
|
|
6289
6259
|
const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
6290
6260
|
cwd,
|
|
@@ -6304,7 +6274,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
6304
6274
|
}
|
|
6305
6275
|
const interval = setInterval(() => {
|
|
6306
6276
|
try {
|
|
6307
|
-
const content =
|
|
6277
|
+
const content = readFileSync12(logPath, "utf-8");
|
|
6308
6278
|
const match = content.match(/listening on :(\d+)/);
|
|
6309
6279
|
if (match) {
|
|
6310
6280
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -6380,7 +6350,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6380
6350
|
logger_default.warn(`failed to sync variant status for ${name}`, logger_default.errorData(err));
|
|
6381
6351
|
}
|
|
6382
6352
|
return c.json(results);
|
|
6383
|
-
}).post("/:name/variants",
|
|
6353
|
+
}).post("/:name/variants", requireSelf(), async (c) => {
|
|
6384
6354
|
const mindName = c.req.param("name");
|
|
6385
6355
|
const entry = await findMind(mindName);
|
|
6386
6356
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -6400,11 +6370,11 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6400
6370
|
return c.json({ error: `Name already in use: ${variantName}` }, 409);
|
|
6401
6371
|
}
|
|
6402
6372
|
const projectRoot = mindDir(mindName);
|
|
6403
|
-
const variantDir =
|
|
6404
|
-
if (
|
|
6373
|
+
const variantDir = resolve20(projectRoot, ".variants", variantName);
|
|
6374
|
+
if (existsSync15(variantDir)) {
|
|
6405
6375
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
6406
6376
|
}
|
|
6407
|
-
|
|
6377
|
+
mkdirSync10(resolve20(projectRoot, ".variants"), { recursive: true });
|
|
6408
6378
|
try {
|
|
6409
6379
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
6410
6380
|
} catch (e) {
|
|
@@ -6417,7 +6387,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6417
6387
|
const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
|
|
6418
6388
|
await exec(cmd, args, {
|
|
6419
6389
|
cwd: variantDir,
|
|
6420
|
-
env: { ...process.env, HOME:
|
|
6390
|
+
env: { ...process.env, HOME: resolve20(variantDir, "home") }
|
|
6421
6391
|
});
|
|
6422
6392
|
} else {
|
|
6423
6393
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -6427,7 +6397,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6427
6397
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
6428
6398
|
}
|
|
6429
6399
|
if (body.soul) {
|
|
6430
|
-
|
|
6400
|
+
writeFileSync9(resolve20(variantDir, "home/SOUL.md"), body.soul);
|
|
6431
6401
|
}
|
|
6432
6402
|
const variantPort = body.port ?? await nextPort();
|
|
6433
6403
|
await addVariant(variantName, mindName, variantPort, variantDir, variantName);
|
|
@@ -6443,7 +6413,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6443
6413
|
ok: true,
|
|
6444
6414
|
variant: { name: variantName, branch: variantName, path: variantDir, port: variantPort }
|
|
6445
6415
|
});
|
|
6446
|
-
}).post("/:name/variants/:variant/merge",
|
|
6416
|
+
}).post("/:name/variants/:variant/merge", requireSelf(), async (c) => {
|
|
6447
6417
|
const mindName = c.req.param("name");
|
|
6448
6418
|
const variantName = c.req.param("variant");
|
|
6449
6419
|
const parentEntry = await findMind(mindName);
|
|
@@ -6462,7 +6432,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6462
6432
|
} catch {
|
|
6463
6433
|
}
|
|
6464
6434
|
const projectRoot = mindDir(mindName);
|
|
6465
|
-
if (
|
|
6435
|
+
if (existsSync15(variantEntry.dir)) {
|
|
6466
6436
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variantEntry.dir })).trim();
|
|
6467
6437
|
if (status) {
|
|
6468
6438
|
try {
|
|
@@ -6536,7 +6506,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6536
6506
|
const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
|
|
6537
6507
|
await exec(cmd, args, {
|
|
6538
6508
|
cwd: projectRoot,
|
|
6539
|
-
env: { ...process.env, HOME:
|
|
6509
|
+
env: { ...process.env, HOME: resolve20(projectRoot, "home") }
|
|
6540
6510
|
});
|
|
6541
6511
|
} else {
|
|
6542
6512
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -6564,7 +6534,7 @@ var app25 = new Hono25().get("/:name/variants", async (c) => {
|
|
|
6564
6534
|
logger_default.warn(restartWarning);
|
|
6565
6535
|
}
|
|
6566
6536
|
return c.json({ ok: true, ...restartWarning && { warning: restartWarning } });
|
|
6567
|
-
}).delete("/:name/variants/:variant",
|
|
6537
|
+
}).delete("/:name/variants/:variant", requireSelf(), async (c) => {
|
|
6568
6538
|
const mindName = c.req.param("name");
|
|
6569
6539
|
const variantName = c.req.param("variant");
|
|
6570
6540
|
const parentEntry = await findMind(mindName);
|
|
@@ -6739,11 +6709,11 @@ async function fanOutToMinds2(opts) {
|
|
|
6739
6709
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
6740
6710
|
const participantNames = participants.map((p) => p.username);
|
|
6741
6711
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
6742
|
-
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "
|
|
6743
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
6744
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
6712
|
+
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
|
|
6713
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
|
|
6714
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
|
|
6745
6715
|
const manager = getMindManager2();
|
|
6746
|
-
const sm =
|
|
6716
|
+
const sm = getSleepManagerIfReady2();
|
|
6747
6717
|
const targetMinds = mindParticipants.map((ap) => {
|
|
6748
6718
|
const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
|
|
6749
6719
|
if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
|
|
@@ -6790,6 +6760,11 @@ async function fanOutToMinds2(opts) {
|
|
|
6790
6760
|
});
|
|
6791
6761
|
}
|
|
6792
6762
|
}
|
|
6763
|
+
var fileSchema = z12.object({
|
|
6764
|
+
filename: z12.string(),
|
|
6765
|
+
data: z12.string()
|
|
6766
|
+
// base64
|
|
6767
|
+
});
|
|
6793
6768
|
var chatSchema = z12.object({
|
|
6794
6769
|
message: z12.string().optional(),
|
|
6795
6770
|
conversationId: z12.string().optional(),
|
|
@@ -6799,7 +6774,8 @@ var chatSchema = z12.object({
|
|
|
6799
6774
|
media_type: z12.string(),
|
|
6800
6775
|
data: z12.string()
|
|
6801
6776
|
})
|
|
6802
|
-
).optional()
|
|
6777
|
+
).optional(),
|
|
6778
|
+
files: z12.array(fileSchema).optional()
|
|
6803
6779
|
});
|
|
6804
6780
|
var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), async (c) => {
|
|
6805
6781
|
const name = c.req.param("name");
|
|
@@ -6807,8 +6783,8 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
|
|
|
6807
6783
|
const entry = await findMind(baseName);
|
|
6808
6784
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
6809
6785
|
const body = c.req.valid("json");
|
|
6810
|
-
if (!body.message && (!body.images || body.images.length === 0)) {
|
|
6811
|
-
return c.json({ error: "message or
|
|
6786
|
+
if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
|
|
6787
|
+
return c.json({ error: "message, images, or files required" }, 400);
|
|
6812
6788
|
}
|
|
6813
6789
|
const user = c.get("user");
|
|
6814
6790
|
const mindUser = await getOrCreateMindUser(baseName);
|
|
@@ -6849,10 +6825,34 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
|
|
|
6849
6825
|
}
|
|
6850
6826
|
const conv = await getConversation(conversationId);
|
|
6851
6827
|
const convTitle = conv?.title ?? null;
|
|
6828
|
+
const fileNotifications = [];
|
|
6829
|
+
if (body.files && body.files.length > 0) {
|
|
6830
|
+
const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
6831
|
+
for (const file of body.files) {
|
|
6832
|
+
const pathErr = validateFilePath(file.filename);
|
|
6833
|
+
if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
|
|
6834
|
+
const content = Buffer.from(file.data, "base64");
|
|
6835
|
+
if (content.length > MAX_FILE_SIZE2) {
|
|
6836
|
+
return c.json(
|
|
6837
|
+
{
|
|
6838
|
+
error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
|
|
6839
|
+
},
|
|
6840
|
+
413
|
|
6841
|
+
);
|
|
6842
|
+
}
|
|
6843
|
+
const { id } = stageFile(baseName, senderName, file.filename, content, file.filename);
|
|
6844
|
+
fileNotifications.push(
|
|
6845
|
+
`[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
|
|
6846
|
+
);
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6852
6849
|
const contentBlocks = [];
|
|
6853
6850
|
if (body.message) {
|
|
6854
6851
|
contentBlocks.push({ type: "text", text: body.message });
|
|
6855
6852
|
}
|
|
6853
|
+
for (const note of fileNotifications) {
|
|
6854
|
+
contentBlocks.push({ type: "text", text: note });
|
|
6855
|
+
}
|
|
6856
6856
|
if (body.images) {
|
|
6857
6857
|
for (const img of body.images) {
|
|
6858
6858
|
contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
|
|
@@ -6872,7 +6872,7 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
|
|
|
6872
6872
|
senderName,
|
|
6873
6873
|
convTitle,
|
|
6874
6874
|
isDM,
|
|
6875
|
-
channelEntryType:
|
|
6875
|
+
channelEntryType: isDM ? "dm" : "channel",
|
|
6876
6876
|
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
6877
6877
|
targetName: (username) => username === baseName ? name : username
|
|
6878
6878
|
});
|
|
@@ -6894,11 +6894,11 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
|
|
|
6894
6894
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
6895
6895
|
});
|
|
6896
6896
|
}, 15e3);
|
|
6897
|
-
await new Promise((
|
|
6897
|
+
await new Promise((resolve23) => {
|
|
6898
6898
|
stream.onAbort(() => {
|
|
6899
6899
|
unsubscribe();
|
|
6900
6900
|
clearInterval(keepAlive);
|
|
6901
|
-
|
|
6901
|
+
resolve23();
|
|
6902
6902
|
});
|
|
6903
6903
|
});
|
|
6904
6904
|
});
|
|
@@ -6906,7 +6906,8 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
|
|
|
6906
6906
|
var unifiedChatSchema2 = z12.object({
|
|
6907
6907
|
message: z12.string().optional(),
|
|
6908
6908
|
conversationId: z12.string(),
|
|
6909
|
-
images: z12.array(z12.object({ media_type: z12.string(), data: z12.string() })).optional()
|
|
6909
|
+
images: z12.array(z12.object({ media_type: z12.string(), data: z12.string() })).optional(),
|
|
6910
|
+
files: z12.array(fileSchema).optional()
|
|
6910
6911
|
});
|
|
6911
6912
|
var unifiedChatApp = new Hono27().post(
|
|
6912
6913
|
"/chat",
|
|
@@ -6914,8 +6915,8 @@ var unifiedChatApp = new Hono27().post(
|
|
|
6914
6915
|
async (c) => {
|
|
6915
6916
|
const user = c.get("user");
|
|
6916
6917
|
const body = c.req.valid("json");
|
|
6917
|
-
if (!body.message && (!body.images || body.images.length === 0)) {
|
|
6918
|
-
return c.json({ error: "message or
|
|
6918
|
+
if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
|
|
6919
|
+
return c.json({ error: "message, images, or files required" }, 400);
|
|
6919
6920
|
}
|
|
6920
6921
|
const conv = await getConversation(body.conversationId);
|
|
6921
6922
|
if (!conv) return c.json({ error: "Conversation not found" }, 404);
|
|
@@ -6923,8 +6924,44 @@ var unifiedChatApp = new Hono27().post(
|
|
|
6923
6924
|
return c.json({ error: "Conversation not found" }, 404);
|
|
6924
6925
|
}
|
|
6925
6926
|
const senderName = user.username;
|
|
6927
|
+
const fileNotifications = [];
|
|
6928
|
+
if (body.files && body.files.length > 0) {
|
|
6929
|
+
const participants = await getParticipants(body.conversationId);
|
|
6930
|
+
const mindParticipants = participants.filter(
|
|
6931
|
+
(p) => p.userType === "mind" && p.username !== senderName
|
|
6932
|
+
);
|
|
6933
|
+
const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
6934
|
+
for (const file of body.files) {
|
|
6935
|
+
const pathErr = validateFilePath(file.filename);
|
|
6936
|
+
if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
|
|
6937
|
+
const content = Buffer.from(file.data, "base64");
|
|
6938
|
+
if (content.length > MAX_FILE_SIZE2) {
|
|
6939
|
+
return c.json(
|
|
6940
|
+
{
|
|
6941
|
+
error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
|
|
6942
|
+
},
|
|
6943
|
+
413
|
|
6944
|
+
);
|
|
6945
|
+
}
|
|
6946
|
+
for (const mind of mindParticipants) {
|
|
6947
|
+
const { id } = stageFile(
|
|
6948
|
+
mind.username,
|
|
6949
|
+
senderName,
|
|
6950
|
+
file.filename,
|
|
6951
|
+
content,
|
|
6952
|
+
file.filename
|
|
6953
|
+
);
|
|
6954
|
+
fileNotifications.push(
|
|
6955
|
+
`[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
|
|
6956
|
+
);
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6926
6960
|
const contentBlocks = [];
|
|
6927
6961
|
if (body.message) contentBlocks.push({ type: "text", text: body.message });
|
|
6962
|
+
for (const note of fileNotifications) {
|
|
6963
|
+
contentBlocks.push({ type: "text", text: note });
|
|
6964
|
+
}
|
|
6928
6965
|
if (body.images) {
|
|
6929
6966
|
for (const img of body.images) {
|
|
6930
6967
|
contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
|
|
@@ -6943,7 +6980,7 @@ var unifiedChatApp = new Hono27().post(
|
|
|
6943
6980
|
senderName,
|
|
6944
6981
|
convTitle: conv.title,
|
|
6945
6982
|
isDM,
|
|
6946
|
-
channelEntryType:
|
|
6983
|
+
channelEntryType: isDM ? "dm" : "channel",
|
|
6947
6984
|
slugExtra: { convType: conv.type, convName: conv.name }
|
|
6948
6985
|
});
|
|
6949
6986
|
return c.json({ ok: true, conversationId: body.conversationId });
|
|
@@ -7013,6 +7050,9 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
|
|
|
7013
7050
|
if (!u) return c.json({ error: `User ${id} not found` }, 400);
|
|
7014
7051
|
}
|
|
7015
7052
|
const participantIds = [...participantSet];
|
|
7053
|
+
if (participantIds.length > 2) {
|
|
7054
|
+
return c.json({ error: "Use channels for multi-participant conversations" }, 400);
|
|
7055
|
+
}
|
|
7016
7056
|
if (participantIds.length === 2) {
|
|
7017
7057
|
const existingId = await findDMConversation(name, participantIds);
|
|
7018
7058
|
if (existingId) {
|
|
@@ -7101,6 +7141,9 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
|
|
|
7101
7141
|
if (!firstMindName) {
|
|
7102
7142
|
return c.json({ error: "At least one mind participant is required" }, 400);
|
|
7103
7143
|
}
|
|
7144
|
+
if (participantIds.size > 2) {
|
|
7145
|
+
return c.json({ error: "Use channels for multi-participant conversations" }, 400);
|
|
7146
|
+
}
|
|
7104
7147
|
const conv = await createConversation(firstMindName, "volute", {
|
|
7105
7148
|
userId: user.id !== 0 ? user.id : void 0,
|
|
7106
7149
|
title: body.title,
|
|
@@ -7124,11 +7167,11 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
|
|
|
7124
7167
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
7125
7168
|
});
|
|
7126
7169
|
}, 15e3);
|
|
7127
|
-
await new Promise((
|
|
7170
|
+
await new Promise((resolve23) => {
|
|
7128
7171
|
stream.onAbort(() => {
|
|
7129
7172
|
unsubscribe();
|
|
7130
7173
|
clearInterval(keepAlive);
|
|
7131
|
-
|
|
7174
|
+
resolve23();
|
|
7132
7175
|
});
|
|
7133
7176
|
});
|
|
7134
7177
|
});
|
|
@@ -7234,8 +7277,8 @@ async function startServer({
|
|
|
7234
7277
|
let assetsDir = "";
|
|
7235
7278
|
let searchDir = dirname2(new URL(import.meta.url).pathname);
|
|
7236
7279
|
for (let i = 0; i < 5; i++) {
|
|
7237
|
-
const candidate =
|
|
7238
|
-
if (
|
|
7280
|
+
const candidate = resolve21(searchDir, "dist", "web-assets");
|
|
7281
|
+
if (existsSync16(candidate)) {
|
|
7239
7282
|
assetsDir = candidate;
|
|
7240
7283
|
break;
|
|
7241
7284
|
}
|
|
@@ -7245,7 +7288,7 @@ async function startServer({
|
|
|
7245
7288
|
app_default.get("*", async (c) => {
|
|
7246
7289
|
const urlPath = new URL(c.req.url).pathname;
|
|
7247
7290
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
7248
|
-
const filePath =
|
|
7291
|
+
const filePath = resolve21(assetsDir, urlPath.slice(1));
|
|
7249
7292
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
7250
7293
|
const s = await stat4(filePath).catch(() => null);
|
|
7251
7294
|
if (s?.isFile()) {
|
|
@@ -7254,7 +7297,7 @@ async function startServer({
|
|
|
7254
7297
|
const body = await readFile4(filePath);
|
|
7255
7298
|
return c.body(body, 200, { "Content-Type": mime });
|
|
7256
7299
|
}
|
|
7257
|
-
const indexPath =
|
|
7300
|
+
const indexPath = resolve21(assetsDir, "index.html");
|
|
7258
7301
|
const indexStat = await stat4(indexPath).catch(() => null);
|
|
7259
7302
|
if (indexStat?.isFile()) {
|
|
7260
7303
|
const body = await readFile4(indexPath, "utf-8");
|
|
@@ -7271,10 +7314,10 @@ async function startServer({
|
|
|
7271
7314
|
createServer: createHttpsServer,
|
|
7272
7315
|
serverOptions: { key: tls.key, cert: tls.cert }
|
|
7273
7316
|
});
|
|
7274
|
-
await new Promise((
|
|
7317
|
+
await new Promise((resolve23, reject) => {
|
|
7275
7318
|
server2.on("listening", () => {
|
|
7276
7319
|
logger_default.info("Volute UI running (https)", { hostname, port });
|
|
7277
|
-
|
|
7320
|
+
resolve23();
|
|
7278
7321
|
});
|
|
7279
7322
|
server2.on("error", (err) => {
|
|
7280
7323
|
reject(err);
|
|
@@ -7282,13 +7325,13 @@ async function startServer({
|
|
|
7282
7325
|
});
|
|
7283
7326
|
const internalPort = port + 1;
|
|
7284
7327
|
const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
|
|
7285
|
-
await new Promise((
|
|
7328
|
+
await new Promise((resolve23, reject) => {
|
|
7286
7329
|
internalServer.on("listening", () => {
|
|
7287
7330
|
logger_default.info("Volute API running (http, internal)", {
|
|
7288
7331
|
hostname: "127.0.0.1",
|
|
7289
7332
|
port: internalPort
|
|
7290
7333
|
});
|
|
7291
|
-
|
|
7334
|
+
resolve23();
|
|
7292
7335
|
});
|
|
7293
7336
|
internalServer.on("error", (err) => {
|
|
7294
7337
|
reject(err);
|
|
@@ -7297,10 +7340,10 @@ async function startServer({
|
|
|
7297
7340
|
return { server: server2, internalPort };
|
|
7298
7341
|
}
|
|
7299
7342
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
7300
|
-
await new Promise((
|
|
7343
|
+
await new Promise((resolve23, reject) => {
|
|
7301
7344
|
server.on("listening", () => {
|
|
7302
7345
|
logger_default.info("Volute API running (http)", { hostname, port });
|
|
7303
|
-
|
|
7346
|
+
resolve23();
|
|
7304
7347
|
});
|
|
7305
7348
|
server.on("error", (err) => {
|
|
7306
7349
|
reject(err);
|
|
@@ -7311,7 +7354,7 @@ async function startServer({
|
|
|
7311
7354
|
|
|
7312
7355
|
// src/daemon.ts
|
|
7313
7356
|
if (!process.env.VOLUTE_HOME) {
|
|
7314
|
-
process.env.VOLUTE_HOME =
|
|
7357
|
+
process.env.VOLUTE_HOME = resolve22(homedir2(), ".volute");
|
|
7315
7358
|
}
|
|
7316
7359
|
if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
|
|
7317
7360
|
process.env.TZ = process.env.VOLUTE_TIMEZONE;
|
|
@@ -7322,7 +7365,7 @@ async function startDaemon(opts) {
|
|
|
7322
7365
|
const home = voluteHome();
|
|
7323
7366
|
const systemDir = voluteSystemDir();
|
|
7324
7367
|
if (!opts.foreground) {
|
|
7325
|
-
const rotatingLog = new RotatingLog(
|
|
7368
|
+
const rotatingLog = new RotatingLog(resolve22(systemDir, "daemon.log"));
|
|
7326
7369
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
7327
7370
|
`));
|
|
7328
7371
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -7332,9 +7375,9 @@ async function startDaemon(opts) {
|
|
|
7332
7375
|
console.warn = write;
|
|
7333
7376
|
console.info = write;
|
|
7334
7377
|
}
|
|
7335
|
-
const DAEMON_PID_PATH =
|
|
7336
|
-
const DAEMON_JSON_PATH =
|
|
7337
|
-
|
|
7378
|
+
const DAEMON_PID_PATH = resolve22(systemDir, "daemon.pid");
|
|
7379
|
+
const DAEMON_JSON_PATH = resolve22(systemDir, "daemon.json");
|
|
7380
|
+
mkdirSync11(home, { recursive: true });
|
|
7338
7381
|
ensureSystemDir();
|
|
7339
7382
|
migrateToSystemDir();
|
|
7340
7383
|
migrateAgentsToMinds();
|
|
@@ -7346,7 +7389,13 @@ async function startDaemon(opts) {
|
|
|
7346
7389
|
await (await import("./db-IC4J52XQ.js")).getDb();
|
|
7347
7390
|
const { migrateRegistryToDb } = await import("./migrate-registry-to-db-XC7T5B7P.js");
|
|
7348
7391
|
migrateRegistryToDb();
|
|
7349
|
-
|
|
7392
|
+
try {
|
|
7393
|
+
const { migrateGroupDMsToChannels } = await import("./conversations-P5BL7RMX.js");
|
|
7394
|
+
await migrateGroupDMsToChannels();
|
|
7395
|
+
} catch (err) {
|
|
7396
|
+
logger_default.error("failed to migrate group DMs to channels", logger_default.errorData(err));
|
|
7397
|
+
}
|
|
7398
|
+
const { initSandbox } = await import("./sandbox-5BW5HPXM.js");
|
|
7350
7399
|
await initSandbox();
|
|
7351
7400
|
try {
|
|
7352
7401
|
await syncBuiltinSkills();
|
|
@@ -7358,7 +7407,9 @@ async function startDaemon(opts) {
|
|
|
7358
7407
|
} catch (err) {
|
|
7359
7408
|
logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
|
|
7360
7409
|
}
|
|
7361
|
-
const
|
|
7410
|
+
const { startSystemWatcher } = await import("./pages-watcher-P7QECRE2.js");
|
|
7411
|
+
startSystemWatcher();
|
|
7412
|
+
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
7362
7413
|
let tls;
|
|
7363
7414
|
if (opts.tailscale) {
|
|
7364
7415
|
const { getTailscaleTls } = await import("./tailscale-NY5MUMY3.js");
|
|
@@ -7382,11 +7433,11 @@ async function startDaemon(opts) {
|
|
|
7382
7433
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
7383
7434
|
process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
|
|
7384
7435
|
process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
|
|
7385
|
-
|
|
7436
|
+
writeFileSync10(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
7386
7437
|
const daemonConfig = { port, hostname, token };
|
|
7387
7438
|
if (internalPort) daemonConfig.internalPort = internalPort;
|
|
7388
7439
|
if (tls) daemonConfig.tls = true;
|
|
7389
|
-
|
|
7440
|
+
writeFileSync10(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
|
|
7390
7441
|
`, { mode: 420 });
|
|
7391
7442
|
const delivery = initDeliveryManager();
|
|
7392
7443
|
const manager = initMindManager();
|
|
@@ -7442,7 +7493,7 @@ async function startDaemon(opts) {
|
|
|
7442
7493
|
bridgeManager.startBridges(daemonPort).catch((err) => {
|
|
7443
7494
|
logger_default.warn("failed to start bridges", logger_default.errorData(err));
|
|
7444
7495
|
});
|
|
7445
|
-
import("./cloud-sync-
|
|
7496
|
+
import("./cloud-sync-KILFGV5Q.js").then(
|
|
7446
7497
|
({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
|
|
7447
7498
|
logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
|
|
7448
7499
|
})
|
|
@@ -7450,7 +7501,7 @@ async function startDaemon(opts) {
|
|
|
7450
7501
|
logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
|
|
7451
7502
|
});
|
|
7452
7503
|
try {
|
|
7453
|
-
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-
|
|
7504
|
+
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-WDHRO3XD.js");
|
|
7454
7505
|
backfillTemplateHashes();
|
|
7455
7506
|
notifyVersionUpdate().catch((err) => {
|
|
7456
7507
|
logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
|
|
@@ -7464,19 +7515,22 @@ async function startDaemon(opts) {
|
|
|
7464
7515
|
cleanExpiredSessions().catch((err) => {
|
|
7465
7516
|
logger_default.warn("failed to clean expired sessions", logger_default.errorData(err));
|
|
7466
7517
|
});
|
|
7518
|
+
cleanExpiredLogs().catch((err) => {
|
|
7519
|
+
logger_default.warn("failed to clean expired logs", logger_default.errorData(err));
|
|
7520
|
+
});
|
|
7467
7521
|
migrateMindRoles().catch((err) => {
|
|
7468
7522
|
logger_default.warn("failed to migrate mind roles", logger_default.errorData(err));
|
|
7469
7523
|
});
|
|
7470
7524
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
7471
7525
|
function cleanup() {
|
|
7472
7526
|
try {
|
|
7473
|
-
if (
|
|
7527
|
+
if (readFileSync13(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
7474
7528
|
unlinkSync2(DAEMON_PID_PATH);
|
|
7475
7529
|
}
|
|
7476
7530
|
} catch {
|
|
7477
7531
|
}
|
|
7478
7532
|
try {
|
|
7479
|
-
const data = JSON.parse(
|
|
7533
|
+
const data = JSON.parse(readFileSync13(DAEMON_JSON_PATH, "utf-8"));
|
|
7480
7534
|
if (data.token === token) {
|
|
7481
7535
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
7482
7536
|
}
|