volute 0.23.0 → 0.25.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 +5 -5
- package/dist/{activity-events-3WHHCOBB.js → activity-events-4O37J7PD.js} +2 -2
- package/dist/api.d.ts +419 -19
- package/dist/{channel-BOOMFULW.js → channel-HZOSHGNF.js} +1 -1
- package/dist/{chunk-QIXPN3OO.js → chunk-2767L2RZ.js} +5 -5
- package/dist/{chunk-SGPEZ32F.js → chunk-33XAVCS4.js} +16 -0
- package/dist/{chunk-VT5QODNE.js → chunk-3AIBT4TW.js} +4 -3
- package/dist/{chunk-A4S7H6G6.js → chunk-BFK6SOEJ.js} +1 -1
- package/dist/{chunk-RK627D57.js → chunk-BOTQ25QT.js} +3 -3
- package/dist/{chunk-TFS25FIM.js → chunk-DG7TO7EE.js} +31 -3
- package/dist/{chunk-HGCDWKSP.js → chunk-E7GOKNOT.js} +1 -1
- package/dist/{chunk-ISWZ6QUK.js → chunk-PMX4EIJK.js} +804 -115
- package/dist/{chunk-M5CNKH4J.js → chunk-SHSWYG2J.js} +7 -7
- package/dist/{chunk-XLC342FO.js → chunk-SIAG3QMM.js} +14 -1
- package/dist/{chunk-KFI7TQJ6.js → chunk-TRQEV3CD.js} +9 -5
- package/dist/{chunk-JG4CCJOA.js → chunk-ZSH4G2P5.js} +33 -15
- package/dist/cli.js +18 -18
- package/dist/{cloud-sync-PI47U2LT.js → cloud-sync-PPBBJDY6.js} +7 -9
- package/dist/{connector-PYT5UOTZ.js → connector-M6XFI6GM.js} +1 -1
- package/dist/{create-WIDA3M4C.js → create-VDQJER52.js} +1 -1
- package/dist/{daemon-client-ZHCDL4RS.js → daemon-client-JOVQZ52X.js} +1 -1
- package/dist/{daemon-restart-RMGOOGPE.js → daemon-restart-FDNOZEAD.js} +5 -5
- package/dist/daemon.js +1047 -981
- package/dist/{delete-LOIANQGD.js → delete-2MRR4JX5.js} +1 -1
- package/dist/{down-WSUASL5E.js → down-674SX2IZ.js} +2 -2
- package/dist/{env-4PHIHTF4.js → env-2FPOZK37.js} +1 -1
- package/dist/{export-XD6PJBQP.js → export-IKFAPRAO.js} +1 -1
- package/dist/{file-X4L5TTOL.js → file-KT3UIQM3.js} +1 -1
- package/dist/{history-HTEKRNID.js → history-46WZN5CN.js} +1 -1
- package/dist/{import-EAXTHHXL.js → import-TH26J76F.js} +2 -2
- package/dist/{log-SRO5Q6AD.js → log-6SGSSR3D.js} +1 -1
- package/dist/{logs-HNTNNBDW.js → logs-HRBONI5I.js} +1 -1
- package/dist/{merge-B6SYTGI7.js → merge-KSFJKX6T.js} +1 -1
- package/dist/{message-delivery-FHV4NO2F.js → message-delivery-XMGV3FUM.js} +6 -6
- package/dist/{mind-BTXR5B3C.js → mind-YVWAHL2A.js} +17 -17
- package/dist/{mind-activity-tracker-PGC3DBJ7.js → mind-activity-tracker-NMDDEV3K.js} +3 -3
- package/dist/{mind-manager-KMY4GA2J.js → mind-manager-4NDNAYAB.js} +2 -2
- package/dist/{mind-sleep-FWRBIFBS.js → mind-sleep-GHPTSAYN.js} +1 -1
- package/dist/{mind-wake-LJK2YU5X.js → mind-wake-BJDJFMDF.js} +1 -1
- package/dist/{package-CUBJ4PKS.js → package-3HF5MXU2.js} +2 -1
- package/dist/{pages-YSTRWJR4.js → pages-Y6DRWUOJ.js} +1 -1
- package/dist/{publish-BZNHKUUK.js → publish-EEKTZBHW.js} +1 -1
- package/dist/{pull-GRQAXM2E.js → pull-D32SPFVU.js} +1 -1
- package/dist/{restart-CIDAKGG2.js → restart-5BMNV7KU.js} +1 -1
- package/dist/{schedule-NLR3LZLY.js → schedule-YEFDLVMJ.js} +1 -1
- package/dist/{seed-3H2MRREW.js → seed-6FEKB3YC.js} +1 -1
- package/dist/{send-RP2TA7SG.js → send-IISDYFCL.js} +1 -1
- package/dist/{service-7BFXDI6J.js → service-FASYWLTC.js} +3 -3
- package/dist/{setup-SSIIXQMI.js → setup-BMLM2UTK.js} +1 -1
- package/dist/{shared-2OGT3NSL.js → shared-LWMNTTZN.js} +4 -4
- package/dist/{skill-Q2Y6PQ3L.js → skill-T3EMR6IR.js} +11 -3
- package/dist/skills/imagegen/SKILL.md +37 -0
- package/dist/skills/imagegen/references/INSTALL.md +13 -0
- package/dist/skills/imagegen/scripts/imagegen.ts +136 -0
- package/dist/skills/resonance/SKILL.md +73 -0
- package/dist/skills/resonance/assets/default-config.json +21 -0
- package/dist/skills/resonance/references/INSTALL.md +23 -0
- package/dist/skills/resonance/scripts/resonance.ts +1250 -0
- package/dist/skills/volute-mind/SKILL.md +94 -4
- package/dist/{sleep-manager-2TMQ65E4.js → sleep-manager-RKTFZPD3.js} +6 -6
- package/dist/{sprout-UKCYBGHK.js → sprout-QJVGJDSH.js} +3 -3
- package/dist/{start-JR6CUUWF.js → start-C7XITZ5O.js} +1 -1
- package/dist/{status-5XDGYHKP.js → status-LYS4NUOZ.js} +1 -1
- package/dist/{status-H2MKDN6L.js → status-SIRPLEZC.js} +4 -3
- package/dist/{stop-VKPGK25U.js → stop-CVKBSLXY.js} +1 -1
- package/dist/tailscale-AJ4VL5XK.js +49 -0
- package/dist/{up-Z5JRG2M2.js → up-CJ26KQLN.js} +2 -2
- package/dist/{update-ELC6MEUT.js → update-7XCZMYBT.js} +7 -7
- package/dist/{upgrade-GXW2EQY3.js → upgrade-7RUIXGOO.js} +1 -1
- package/dist/{variant-A4I7PHXS.js → variant-UGREB4G5.js} +4 -4
- package/dist/{version-notify-LKABEJSA.js → version-notify-AZQMC32A.js} +6 -6
- package/dist/web-assets/assets/index-CGPSVu19.js +69 -0
- package/dist/web-assets/assets/index-V_rNDsM8.css +1 -0
- package/dist/web-assets/favicon.png +0 -0
- package/dist/web-assets/index.html +5 -4
- package/dist/web-assets/logo.png +0 -0
- package/drizzle/0013_user_profiles.sql +3 -0
- package/drizzle/0014_conversation_reads.sql +7 -0
- package/drizzle/meta/0013_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +2 -1
- package/templates/_base/home/public/.gitkeep +0 -0
- package/templates/_base/src/lib/format-prefix.ts +18 -2
- package/templates/_base/src/lib/routing.ts +2 -1
- package/templates/_base/src/lib/types.ts +8 -0
- package/dist/chunk-G5KRTU2F.js +0 -76
- package/dist/web-assets/assets/index-CZ26vsyY.js +0 -69
- package/dist/web-assets/assets/index-DyyAvJwW.css +0 -1
package/dist/daemon.js
CHANGED
|
@@ -8,10 +8,6 @@ import {
|
|
|
8
8
|
sharedPull,
|
|
9
9
|
sharedStatus
|
|
10
10
|
} from "./chunk-PHHKNGA3.js";
|
|
11
|
-
import {
|
|
12
|
-
fireWebhook,
|
|
13
|
-
initWebhook
|
|
14
|
-
} from "./chunk-G5KRTU2F.js";
|
|
15
11
|
import {
|
|
16
12
|
applyInitFiles,
|
|
17
13
|
composeTemplate,
|
|
@@ -21,44 +17,82 @@ import {
|
|
|
21
17
|
listFiles
|
|
22
18
|
} from "./chunk-AKPFNL7L.js";
|
|
23
19
|
import {
|
|
20
|
+
addMessage,
|
|
21
|
+
approveUser,
|
|
22
|
+
changePassword,
|
|
23
|
+
countAdmins,
|
|
24
|
+
createChannel,
|
|
25
|
+
createConversation,
|
|
26
|
+
createUser,
|
|
27
|
+
deleteConversationForUser,
|
|
28
|
+
deleteMindUser as deleteMindUser2,
|
|
29
|
+
deleteUser,
|
|
24
30
|
deliverMessage,
|
|
25
31
|
extractTextContent,
|
|
32
|
+
findDMConversation,
|
|
33
|
+
fireWebhook,
|
|
26
34
|
getCachedRecentPages,
|
|
27
35
|
getCachedSites,
|
|
36
|
+
getChannelByName,
|
|
28
37
|
getConnectorManager,
|
|
38
|
+
getConversation,
|
|
29
39
|
getDeliveryManager,
|
|
30
40
|
getMailPoller,
|
|
41
|
+
getMessages,
|
|
42
|
+
getMessagesPaginated,
|
|
43
|
+
getOrCreateMindUser,
|
|
44
|
+
getParticipants,
|
|
31
45
|
getScheduler,
|
|
32
46
|
getTokenBudget,
|
|
33
47
|
getTypingMap,
|
|
48
|
+
getUnreadCounts,
|
|
49
|
+
getUser,
|
|
50
|
+
getUserByUsername,
|
|
34
51
|
initConnectorManager,
|
|
35
52
|
initDeliveryManager,
|
|
36
53
|
initMailPoller,
|
|
37
54
|
initScheduler,
|
|
38
55
|
initSleepManager,
|
|
39
56
|
initTokenBudget,
|
|
57
|
+
initWebhook,
|
|
58
|
+
isParticipant,
|
|
59
|
+
isParticipantOrOwner,
|
|
60
|
+
joinChannel,
|
|
61
|
+
leaveChannel,
|
|
62
|
+
listChannels,
|
|
63
|
+
listConversationsForUser,
|
|
64
|
+
listConversationsWithParticipants,
|
|
65
|
+
listPendingUsers,
|
|
66
|
+
listUsers,
|
|
67
|
+
listUsersByType,
|
|
68
|
+
markConversationRead,
|
|
40
69
|
publish,
|
|
41
70
|
publish2,
|
|
42
71
|
publishTypingForChannels,
|
|
43
72
|
recordInbound,
|
|
73
|
+
setUserRole,
|
|
44
74
|
startMindFull,
|
|
45
75
|
stopAllWatchers,
|
|
46
76
|
stopMindFull,
|
|
47
77
|
subscribe as subscribe2,
|
|
48
|
-
subscribe2 as subscribe3
|
|
49
|
-
|
|
78
|
+
subscribe2 as subscribe3,
|
|
79
|
+
updateUserProfile,
|
|
80
|
+
verifyUser
|
|
81
|
+
} from "./chunk-PMX4EIJK.js";
|
|
50
82
|
import {
|
|
51
|
-
|
|
83
|
+
deleteSystemsConfig,
|
|
84
|
+
readSystemsConfig,
|
|
85
|
+
writeSystemsConfig
|
|
52
86
|
} from "./chunk-HFCBO2GL.js";
|
|
53
87
|
import {
|
|
54
88
|
getActiveMinds,
|
|
55
89
|
onMindEvent,
|
|
56
90
|
stopAll
|
|
57
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-E7GOKNOT.js";
|
|
58
92
|
import {
|
|
59
93
|
broadcast,
|
|
60
94
|
subscribe
|
|
61
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-BFK6SOEJ.js";
|
|
62
96
|
import {
|
|
63
97
|
PROMPT_DEFAULTS,
|
|
64
98
|
PROMPT_KEYS,
|
|
@@ -69,17 +103,17 @@ import {
|
|
|
69
103
|
getPromptIfCustom,
|
|
70
104
|
initMindManager,
|
|
71
105
|
substitute
|
|
72
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-SHSWYG2J.js";
|
|
73
107
|
import {
|
|
74
108
|
findOpenClawSession,
|
|
75
109
|
importOpenClawConnectors,
|
|
76
110
|
importPiSession,
|
|
77
111
|
parseNameFromIdentity
|
|
78
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-BOTQ25QT.js";
|
|
79
113
|
import {
|
|
80
114
|
readVoluteConfig,
|
|
81
115
|
writeVoluteConfig
|
|
82
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-SIAG3QMM.js";
|
|
83
117
|
import {
|
|
84
118
|
loadMergedEnv,
|
|
85
119
|
mindEnvPath,
|
|
@@ -105,18 +139,15 @@ import {
|
|
|
105
139
|
syncBuiltinSkills,
|
|
106
140
|
uninstallSkill,
|
|
107
141
|
updateSkill
|
|
108
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-DG7TO7EE.js";
|
|
109
143
|
import {
|
|
110
144
|
activity,
|
|
111
|
-
conversationParticipants,
|
|
112
145
|
conversations,
|
|
113
146
|
getDb,
|
|
114
|
-
messages,
|
|
115
147
|
mindHistory,
|
|
116
148
|
sessions,
|
|
117
|
-
systemPrompts
|
|
118
|
-
|
|
119
|
-
} from "./chunk-SGPEZ32F.js";
|
|
149
|
+
systemPrompts
|
|
150
|
+
} from "./chunk-33XAVCS4.js";
|
|
120
151
|
import {
|
|
121
152
|
logBuffer,
|
|
122
153
|
logger_default
|
|
@@ -179,9 +210,9 @@ import {
|
|
|
179
210
|
|
|
180
211
|
// src/daemon.ts
|
|
181
212
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
182
|
-
import { mkdirSync as
|
|
213
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
183
214
|
import { homedir as homedir2 } from "os";
|
|
184
|
-
import { resolve as
|
|
215
|
+
import { resolve as resolve19 } from "path";
|
|
185
216
|
import { format } from "util";
|
|
186
217
|
|
|
187
218
|
// src/lib/migrate-agents-to-minds.ts
|
|
@@ -331,6 +362,17 @@ function migrateDotVoluteDir(name) {
|
|
|
331
362
|
console.warn(`[migrate] both .volute/ and .mind/ exist for ${name}, skipping rename`);
|
|
332
363
|
}
|
|
333
364
|
}
|
|
365
|
+
function migratePagesDirToPublic(name) {
|
|
366
|
+
const dir = mindDir(name);
|
|
367
|
+
const oldPagesDir = resolve2(dir, "home", "pages");
|
|
368
|
+
const newPublicDir = resolve2(dir, "home", "public");
|
|
369
|
+
const newPagesDir = resolve2(newPublicDir, "pages");
|
|
370
|
+
if (existsSync2(oldPagesDir) && !existsSync2(newPagesDir)) {
|
|
371
|
+
mkdirSync(newPublicDir, { recursive: true });
|
|
372
|
+
renameSync2(oldPagesDir, newPagesDir);
|
|
373
|
+
logger_default.info(`migrated pages/ \u2192 public/pages/ for ${name}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
334
376
|
function migrateMindState(name) {
|
|
335
377
|
const src = resolve2(mindDir(name), ".mind");
|
|
336
378
|
if (!existsSync2(src)) return;
|
|
@@ -359,153 +401,20 @@ function migrateMindState(name) {
|
|
|
359
401
|
|
|
360
402
|
// src/web/middleware/auth.ts
|
|
361
403
|
import { timingSafeEqual } from "crypto";
|
|
362
|
-
import { eq
|
|
404
|
+
import { eq, lt } from "drizzle-orm";
|
|
363
405
|
import { getCookie } from "hono/cookie";
|
|
364
406
|
import { createMiddleware } from "hono/factory";
|
|
365
|
-
|
|
366
|
-
// src/lib/auth.ts
|
|
367
|
-
import { compareSync, hashSync } from "bcryptjs";
|
|
368
|
-
import { and, count, eq } from "drizzle-orm";
|
|
369
|
-
async function createUser(username, password) {
|
|
370
|
-
const db = await getDb();
|
|
371
|
-
const hash = hashSync(password, 10);
|
|
372
|
-
const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
|
|
373
|
-
const role = value === 0 ? "admin" : "pending";
|
|
374
|
-
const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
|
|
375
|
-
id: users.id,
|
|
376
|
-
username: users.username,
|
|
377
|
-
role: users.role,
|
|
378
|
-
user_type: users.user_type,
|
|
379
|
-
created_at: users.created_at
|
|
380
|
-
});
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
async function verifyUser(username, password) {
|
|
384
|
-
const db = await getDb();
|
|
385
|
-
const row = await db.select().from(users).where(eq(users.username, username)).get();
|
|
386
|
-
if (!row) return null;
|
|
387
|
-
if (row.user_type === "mind") return null;
|
|
388
|
-
if (!compareSync(password, row.password_hash)) return null;
|
|
389
|
-
const { password_hash: _, ...user } = row;
|
|
390
|
-
return user;
|
|
391
|
-
}
|
|
392
|
-
async function getUser(id) {
|
|
393
|
-
const db = await getDb();
|
|
394
|
-
const row = await db.select({
|
|
395
|
-
id: users.id,
|
|
396
|
-
username: users.username,
|
|
397
|
-
role: users.role,
|
|
398
|
-
user_type: users.user_type,
|
|
399
|
-
created_at: users.created_at
|
|
400
|
-
}).from(users).where(eq(users.id, id)).get();
|
|
401
|
-
return row ?? null;
|
|
402
|
-
}
|
|
403
|
-
async function getUserByUsername(username) {
|
|
404
|
-
const db = await getDb();
|
|
405
|
-
const row = await db.select({
|
|
406
|
-
id: users.id,
|
|
407
|
-
username: users.username,
|
|
408
|
-
role: users.role,
|
|
409
|
-
user_type: users.user_type,
|
|
410
|
-
created_at: users.created_at
|
|
411
|
-
}).from(users).where(eq(users.username, username)).get();
|
|
412
|
-
return row ?? null;
|
|
413
|
-
}
|
|
414
|
-
async function listUsers() {
|
|
415
|
-
const db = await getDb();
|
|
416
|
-
return db.select({
|
|
417
|
-
id: users.id,
|
|
418
|
-
username: users.username,
|
|
419
|
-
role: users.role,
|
|
420
|
-
user_type: users.user_type,
|
|
421
|
-
created_at: users.created_at
|
|
422
|
-
}).from(users).orderBy(users.created_at).all();
|
|
423
|
-
}
|
|
424
|
-
async function listPendingUsers() {
|
|
425
|
-
const db = await getDb();
|
|
426
|
-
return db.select({
|
|
427
|
-
id: users.id,
|
|
428
|
-
username: users.username,
|
|
429
|
-
role: users.role,
|
|
430
|
-
user_type: users.user_type,
|
|
431
|
-
created_at: users.created_at
|
|
432
|
-
}).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
|
|
433
|
-
}
|
|
434
|
-
async function listUsersByType(userType) {
|
|
435
|
-
const db = await getDb();
|
|
436
|
-
return db.select({
|
|
437
|
-
id: users.id,
|
|
438
|
-
username: users.username,
|
|
439
|
-
role: users.role,
|
|
440
|
-
user_type: users.user_type,
|
|
441
|
-
created_at: users.created_at
|
|
442
|
-
}).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
|
|
443
|
-
}
|
|
444
|
-
async function getOrCreateMindUser(mindName) {
|
|
445
|
-
const db = await getDb();
|
|
446
|
-
const existing = await db.select({
|
|
447
|
-
id: users.id,
|
|
448
|
-
username: users.username,
|
|
449
|
-
role: users.role,
|
|
450
|
-
user_type: users.user_type,
|
|
451
|
-
created_at: users.created_at
|
|
452
|
-
}).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
|
|
453
|
-
if (existing) return existing;
|
|
454
|
-
try {
|
|
455
|
-
const [result] = await db.insert(users).values({
|
|
456
|
-
username: mindName,
|
|
457
|
-
password_hash: "!mind",
|
|
458
|
-
role: "mind",
|
|
459
|
-
user_type: "mind"
|
|
460
|
-
}).returning({
|
|
461
|
-
id: users.id,
|
|
462
|
-
username: users.username,
|
|
463
|
-
role: users.role,
|
|
464
|
-
user_type: users.user_type,
|
|
465
|
-
created_at: users.created_at
|
|
466
|
-
});
|
|
467
|
-
return result;
|
|
468
|
-
} catch (err) {
|
|
469
|
-
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
470
|
-
const retried = await db.select({
|
|
471
|
-
id: users.id,
|
|
472
|
-
username: users.username,
|
|
473
|
-
role: users.role,
|
|
474
|
-
user_type: users.user_type,
|
|
475
|
-
created_at: users.created_at
|
|
476
|
-
}).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
|
|
477
|
-
if (retried) return retried;
|
|
478
|
-
}
|
|
479
|
-
throw err;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
async function deleteMindUser2(mindName) {
|
|
483
|
-
const db = await getDb();
|
|
484
|
-
await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
|
|
485
|
-
}
|
|
486
|
-
async function changePassword(userId, currentPassword, newPassword) {
|
|
487
|
-
const db = await getDb();
|
|
488
|
-
const row = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
489
|
-
if (!row) return false;
|
|
490
|
-
if (!compareSync(currentPassword, row.password_hash)) return false;
|
|
491
|
-
const hash = hashSync(newPassword, 10);
|
|
492
|
-
await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
async function approveUser(id) {
|
|
496
|
-
const db = await getDb();
|
|
497
|
-
await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// src/web/middleware/auth.ts
|
|
501
407
|
function isValidDaemonToken(token) {
|
|
502
408
|
const expected = process.env.VOLUTE_DAEMON_TOKEN;
|
|
503
409
|
if (!expected || token.length !== expected.length) return false;
|
|
504
410
|
return timingSafeEqual(Buffer.from(token), Buffer.from(expected));
|
|
505
411
|
}
|
|
506
|
-
var SESSION_MAX_AGE =
|
|
412
|
+
var SESSION_MAX_AGE = 365 * 24 * 60 * 60 * 1e3;
|
|
507
413
|
var SESSION_CACHE_TTL = 5 * 60 * 1e3;
|
|
508
414
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
415
|
+
function invalidateSessionCache(sessionId) {
|
|
416
|
+
sessionCache.delete(sessionId);
|
|
417
|
+
}
|
|
509
418
|
async function createSession(userId) {
|
|
510
419
|
const db = await getDb();
|
|
511
420
|
const sessionId = crypto.randomUUID();
|
|
@@ -515,14 +424,14 @@ async function createSession(userId) {
|
|
|
515
424
|
async function deleteSession(sessionId) {
|
|
516
425
|
sessionCache.delete(sessionId);
|
|
517
426
|
const db = await getDb();
|
|
518
|
-
await db.delete(sessions).where(
|
|
427
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
519
428
|
}
|
|
520
429
|
async function getSessionUserId(sessionId) {
|
|
521
430
|
const db = await getDb();
|
|
522
|
-
const row = await db.select().from(sessions).where(
|
|
431
|
+
const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
|
|
523
432
|
if (!row) return void 0;
|
|
524
433
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
525
|
-
await db.delete(sessions).where(
|
|
434
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
526
435
|
return void 0;
|
|
527
436
|
}
|
|
528
437
|
return row.userId;
|
|
@@ -544,7 +453,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
544
453
|
if (authHeader?.startsWith("Bearer ")) {
|
|
545
454
|
const token = authHeader.slice(7);
|
|
546
455
|
if (token && isValidDaemonToken(token)) {
|
|
547
|
-
c.set("user", {
|
|
456
|
+
c.set("user", {
|
|
457
|
+
id: 0,
|
|
458
|
+
username: "cli",
|
|
459
|
+
role: "admin",
|
|
460
|
+
user_type: "brain",
|
|
461
|
+
display_name: null,
|
|
462
|
+
description: null,
|
|
463
|
+
avatar: null
|
|
464
|
+
});
|
|
548
465
|
await next();
|
|
549
466
|
return;
|
|
550
467
|
}
|
|
@@ -575,306 +492,22 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
575
492
|
});
|
|
576
493
|
|
|
577
494
|
// src/web/server.ts
|
|
578
|
-
import { existsSync as
|
|
579
|
-
import { readFile as
|
|
580
|
-
import {
|
|
495
|
+
import { existsSync as existsSync13 } from "fs";
|
|
496
|
+
import { readFile as readFile4, stat as stat4 } from "fs/promises";
|
|
497
|
+
import { createServer as createHttpsServer } from "https";
|
|
498
|
+
import { dirname, extname as extname5, resolve as resolve18 } from "path";
|
|
581
499
|
import { serve } from "@hono/node-server";
|
|
582
500
|
|
|
583
501
|
// src/web/app.ts
|
|
584
|
-
import { Hono as
|
|
502
|
+
import { Hono as Hono29 } from "hono";
|
|
585
503
|
import { bodyLimit } from "hono/body-limit";
|
|
586
504
|
import { csrf } from "hono/csrf";
|
|
587
505
|
import { HTTPException } from "hono/http-exception";
|
|
588
506
|
|
|
589
507
|
// src/web/api/activity.ts
|
|
590
|
-
import { desc
|
|
508
|
+
import { desc } from "drizzle-orm";
|
|
591
509
|
import { Hono } from "hono";
|
|
592
510
|
import { streamSSE } from "hono/streaming";
|
|
593
|
-
|
|
594
|
-
// src/lib/events/conversations.ts
|
|
595
|
-
import { randomUUID } from "crypto";
|
|
596
|
-
import { and as and2, desc, eq as eq3, inArray, isNull, lt as lt2, sql } from "drizzle-orm";
|
|
597
|
-
async function createConversation(mindName, channel, opts) {
|
|
598
|
-
const db = await getDb();
|
|
599
|
-
const id = randomUUID();
|
|
600
|
-
const type = opts?.type ?? "dm";
|
|
601
|
-
const name = opts?.name ?? null;
|
|
602
|
-
await db.transaction(async (tx) => {
|
|
603
|
-
await tx.insert(conversations).values({
|
|
604
|
-
id,
|
|
605
|
-
mind_name: mindName,
|
|
606
|
-
channel,
|
|
607
|
-
type,
|
|
608
|
-
name,
|
|
609
|
-
user_id: opts?.userId ?? null,
|
|
610
|
-
title: opts?.title ?? null
|
|
611
|
-
});
|
|
612
|
-
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
613
|
-
await tx.insert(conversationParticipants).values(
|
|
614
|
-
opts.participantIds.map((uid, i) => ({
|
|
615
|
-
conversation_id: id,
|
|
616
|
-
user_id: uid,
|
|
617
|
-
role: i === 0 ? "owner" : "member"
|
|
618
|
-
}))
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
fireWebhook({
|
|
623
|
-
event: "conversation_created",
|
|
624
|
-
mind: mindName ?? "",
|
|
625
|
-
data: { id, mindName, channel, type, name, title: opts?.title ?? null }
|
|
626
|
-
});
|
|
627
|
-
return {
|
|
628
|
-
id,
|
|
629
|
-
mind_name: mindName,
|
|
630
|
-
channel,
|
|
631
|
-
type,
|
|
632
|
-
name,
|
|
633
|
-
user_id: opts?.userId ?? null,
|
|
634
|
-
title: opts?.title ?? null,
|
|
635
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
636
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
async function getConversation(id) {
|
|
640
|
-
const db = await getDb();
|
|
641
|
-
const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
|
|
642
|
-
return row ?? null;
|
|
643
|
-
}
|
|
644
|
-
async function addParticipant(conversationId, userId, role = "member") {
|
|
645
|
-
const db = await getDb();
|
|
646
|
-
await db.insert(conversationParticipants).values({
|
|
647
|
-
conversation_id: conversationId,
|
|
648
|
-
user_id: userId,
|
|
649
|
-
role
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
async function removeParticipant(conversationId, userId) {
|
|
653
|
-
const db = await getDb();
|
|
654
|
-
await db.delete(conversationParticipants).where(
|
|
655
|
-
and2(
|
|
656
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
657
|
-
eq3(conversationParticipants.user_id, userId)
|
|
658
|
-
)
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
async function getParticipants(conversationId) {
|
|
662
|
-
const db = await getDb();
|
|
663
|
-
const rows = await db.select({
|
|
664
|
-
userId: conversationParticipants.user_id,
|
|
665
|
-
username: users.username,
|
|
666
|
-
userType: users.user_type,
|
|
667
|
-
role: conversationParticipants.role
|
|
668
|
-
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
|
|
669
|
-
return rows;
|
|
670
|
-
}
|
|
671
|
-
async function isParticipant(conversationId, userId) {
|
|
672
|
-
const db = await getDb();
|
|
673
|
-
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
674
|
-
and2(
|
|
675
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
676
|
-
eq3(conversationParticipants.user_id, userId)
|
|
677
|
-
)
|
|
678
|
-
).get();
|
|
679
|
-
return row != null;
|
|
680
|
-
}
|
|
681
|
-
async function listConversationsForUser(userId) {
|
|
682
|
-
const db = await getDb();
|
|
683
|
-
const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
|
|
684
|
-
if (participantRows.length === 0) return [];
|
|
685
|
-
const convIds = participantRows.map((r) => r.conversation_id);
|
|
686
|
-
return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
687
|
-
}
|
|
688
|
-
async function isParticipantOrOwner(conversationId, userId) {
|
|
689
|
-
if (await isParticipant(conversationId, userId)) return true;
|
|
690
|
-
const db = await getDb();
|
|
691
|
-
const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
|
|
692
|
-
return row != null;
|
|
693
|
-
}
|
|
694
|
-
async function deleteConversationForUser(id, userId) {
|
|
695
|
-
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
696
|
-
await deleteConversation(id);
|
|
697
|
-
return true;
|
|
698
|
-
}
|
|
699
|
-
async function addMessage(conversationId, role, senderName, content) {
|
|
700
|
-
const db = await getDb();
|
|
701
|
-
const serialized = JSON.stringify(content);
|
|
702
|
-
const [result] = await db.insert(messages).values({ conversation_id: conversationId, role, sender_name: senderName, content: serialized }).returning({ id: messages.id, created_at: messages.created_at });
|
|
703
|
-
await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
|
|
704
|
-
if (role === "user") {
|
|
705
|
-
const firstText = content.find((b) => b.type === "text");
|
|
706
|
-
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
707
|
-
if (title) {
|
|
708
|
-
await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
const msg = {
|
|
712
|
-
id: result.id,
|
|
713
|
-
conversation_id: conversationId,
|
|
714
|
-
role,
|
|
715
|
-
sender_name: senderName,
|
|
716
|
-
content,
|
|
717
|
-
created_at: result.created_at
|
|
718
|
-
};
|
|
719
|
-
publish2(conversationId, {
|
|
720
|
-
type: "message",
|
|
721
|
-
id: msg.id,
|
|
722
|
-
role: msg.role,
|
|
723
|
-
senderName: msg.sender_name,
|
|
724
|
-
content: msg.content,
|
|
725
|
-
createdAt: msg.created_at
|
|
726
|
-
});
|
|
727
|
-
const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq3(conversations.id, conversationId)).get();
|
|
728
|
-
fireWebhook({
|
|
729
|
-
event: "message_created",
|
|
730
|
-
mind: conv?.mind_name ?? "",
|
|
731
|
-
data: {
|
|
732
|
-
conversationId,
|
|
733
|
-
messageId: result.id,
|
|
734
|
-
role,
|
|
735
|
-
senderName,
|
|
736
|
-
content: content.filter((b) => b.type !== "image"),
|
|
737
|
-
createdAt: result.created_at
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
return msg;
|
|
741
|
-
}
|
|
742
|
-
async function getMessages(conversationId) {
|
|
743
|
-
const db = await getDb();
|
|
744
|
-
const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
745
|
-
return rows.map(parseMessageRow);
|
|
746
|
-
}
|
|
747
|
-
async function getMessagesPaginated(conversationId, opts) {
|
|
748
|
-
const db = await getDb();
|
|
749
|
-
const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
|
|
750
|
-
const conditions = [eq3(messages.conversation_id, conversationId)];
|
|
751
|
-
if (opts?.before != null) {
|
|
752
|
-
conditions.push(lt2(messages.id, opts.before));
|
|
753
|
-
}
|
|
754
|
-
const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
|
|
755
|
-
const hasMore = rows.length > limit;
|
|
756
|
-
const page = rows.slice(0, limit).reverse();
|
|
757
|
-
return {
|
|
758
|
-
messages: page.map(parseMessageRow),
|
|
759
|
-
hasMore
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
function parseMessageRow(row) {
|
|
763
|
-
let content;
|
|
764
|
-
try {
|
|
765
|
-
const parsed = JSON.parse(row.content);
|
|
766
|
-
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
767
|
-
} catch {
|
|
768
|
-
content = [{ type: "text", text: row.content }];
|
|
769
|
-
}
|
|
770
|
-
return { ...row, role: row.role, content };
|
|
771
|
-
}
|
|
772
|
-
async function listConversationsWithParticipants(userId) {
|
|
773
|
-
const convs = await listConversationsForUser(userId);
|
|
774
|
-
if (convs.length === 0) return [];
|
|
775
|
-
const db = await getDb();
|
|
776
|
-
const convIds = convs.map((c) => c.id);
|
|
777
|
-
const rows = await db.select({
|
|
778
|
-
conversationId: conversationParticipants.conversation_id,
|
|
779
|
-
userId: users.id,
|
|
780
|
-
username: users.username,
|
|
781
|
-
userType: users.user_type,
|
|
782
|
-
role: conversationParticipants.role
|
|
783
|
-
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
784
|
-
const byConv = /* @__PURE__ */ new Map();
|
|
785
|
-
for (const r of rows) {
|
|
786
|
-
let arr = byConv.get(r.conversationId);
|
|
787
|
-
if (!arr) {
|
|
788
|
-
arr = [];
|
|
789
|
-
byConv.set(r.conversationId, arr);
|
|
790
|
-
}
|
|
791
|
-
arr.push({
|
|
792
|
-
userId: r.userId,
|
|
793
|
-
username: r.username,
|
|
794
|
-
userType: r.userType,
|
|
795
|
-
role: r.role
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
const lastMsgIds = await db.select({
|
|
799
|
-
conversationId: messages.conversation_id,
|
|
800
|
-
maxId: sql`MAX(${messages.id})`
|
|
801
|
-
}).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
|
|
802
|
-
const byLastMsg = /* @__PURE__ */ new Map();
|
|
803
|
-
if (lastMsgIds.length > 0) {
|
|
804
|
-
const msgRows = await db.select().from(messages).where(
|
|
805
|
-
inArray(
|
|
806
|
-
messages.id,
|
|
807
|
-
lastMsgIds.map((r) => r.maxId)
|
|
808
|
-
)
|
|
809
|
-
);
|
|
810
|
-
for (const m of msgRows) {
|
|
811
|
-
let text = "";
|
|
812
|
-
try {
|
|
813
|
-
const parsed = JSON.parse(m.content);
|
|
814
|
-
const blocks = Array.isArray(parsed) ? parsed : [];
|
|
815
|
-
const textBlock = blocks.find((b) => b.type === "text");
|
|
816
|
-
if (textBlock && "text" in textBlock) text = textBlock.text;
|
|
817
|
-
} catch {
|
|
818
|
-
text = m.content;
|
|
819
|
-
}
|
|
820
|
-
byLastMsg.set(m.conversation_id, {
|
|
821
|
-
role: m.role,
|
|
822
|
-
senderName: m.sender_name,
|
|
823
|
-
text,
|
|
824
|
-
createdAt: m.created_at
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return convs.map((c) => ({
|
|
829
|
-
...c,
|
|
830
|
-
participants: byConv.get(c.id) ?? [],
|
|
831
|
-
lastMessage: byLastMsg.get(c.id)
|
|
832
|
-
}));
|
|
833
|
-
}
|
|
834
|
-
async function findDMConversation(mindName, participantIds) {
|
|
835
|
-
const db = await getDb();
|
|
836
|
-
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
|
|
837
|
-
for (const conv of mindConvs) {
|
|
838
|
-
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
|
|
839
|
-
if (rows.length !== 2) continue;
|
|
840
|
-
const ids = new Set(rows.map((r) => r.user_id));
|
|
841
|
-
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
842
|
-
return conv.id;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
return null;
|
|
846
|
-
}
|
|
847
|
-
async function deleteConversation(id) {
|
|
848
|
-
const db = await getDb();
|
|
849
|
-
await db.delete(conversations).where(eq3(conversations.id, id));
|
|
850
|
-
}
|
|
851
|
-
async function createChannel(name, creatorId) {
|
|
852
|
-
const participantIds = creatorId ? [creatorId] : [];
|
|
853
|
-
return createConversation(null, "volute", {
|
|
854
|
-
type: "channel",
|
|
855
|
-
name,
|
|
856
|
-
title: name,
|
|
857
|
-
participantIds
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
async function getChannelByName(name) {
|
|
861
|
-
const db = await getDb();
|
|
862
|
-
const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
|
|
863
|
-
return row ?? null;
|
|
864
|
-
}
|
|
865
|
-
async function listChannels() {
|
|
866
|
-
const db = await getDb();
|
|
867
|
-
return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
|
|
868
|
-
}
|
|
869
|
-
async function joinChannel(conversationId, userId) {
|
|
870
|
-
if (await isParticipant(conversationId, userId)) return;
|
|
871
|
-
await addParticipant(conversationId, userId);
|
|
872
|
-
}
|
|
873
|
-
async function leaveChannel(conversationId, userId) {
|
|
874
|
-
await removeParticipant(conversationId, userId);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// src/web/api/activity.ts
|
|
878
511
|
var app = new Hono().get("/events", async (c) => {
|
|
879
512
|
const user = c.get("user");
|
|
880
513
|
return streamSSE(c, async (stream) => {
|
|
@@ -883,7 +516,7 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
883
516
|
let recentActivity = [];
|
|
884
517
|
try {
|
|
885
518
|
const db = await getDb();
|
|
886
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
519
|
+
recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
|
|
887
520
|
recentActivity = recentActivity.map((row) => ({
|
|
888
521
|
...row,
|
|
889
522
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -933,8 +566,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
933
566
|
});
|
|
934
567
|
}, 15e3);
|
|
935
568
|
cleanups.push(() => clearInterval(keepAlive));
|
|
936
|
-
await new Promise((
|
|
937
|
-
stream.onAbort(() =>
|
|
569
|
+
await new Promise((resolve20) => {
|
|
570
|
+
stream.onAbort(() => resolve20());
|
|
938
571
|
});
|
|
939
572
|
} finally {
|
|
940
573
|
for (const cleanup of cleanups) {
|
|
@@ -949,10 +582,18 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
949
582
|
var activity_default = app;
|
|
950
583
|
|
|
951
584
|
// src/web/api/auth.ts
|
|
585
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
586
|
+
import { extname, resolve as resolve3 } from "path";
|
|
952
587
|
import { zValidator } from "@hono/zod-validator";
|
|
953
588
|
import { Hono as Hono2 } from "hono";
|
|
954
589
|
import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
|
|
955
590
|
import { z } from "zod";
|
|
591
|
+
var SESSION_COOKIE_OPTIONS = {
|
|
592
|
+
path: "/",
|
|
593
|
+
httpOnly: true,
|
|
594
|
+
sameSite: "Lax",
|
|
595
|
+
maxAge: Math.floor(SESSION_MAX_AGE / 1e3)
|
|
596
|
+
};
|
|
956
597
|
var credentialsSchema = z.object({
|
|
957
598
|
username: z.string().min(1),
|
|
958
599
|
password: z.string().min(1)
|
|
@@ -961,12 +602,86 @@ var changePasswordSchema = z.object({
|
|
|
961
602
|
currentPassword: z.string().min(1),
|
|
962
603
|
newPassword: z.string().min(1)
|
|
963
604
|
});
|
|
605
|
+
var profileSchema = z.object({
|
|
606
|
+
display_name: z.string().max(100).nullable().optional(),
|
|
607
|
+
description: z.string().max(500).nullable().optional()
|
|
608
|
+
});
|
|
609
|
+
var AVATAR_MIME = {
|
|
610
|
+
".png": "image/png",
|
|
611
|
+
".jpg": "image/jpeg",
|
|
612
|
+
".jpeg": "image/jpeg",
|
|
613
|
+
".gif": "image/gif",
|
|
614
|
+
".webp": "image/webp"
|
|
615
|
+
};
|
|
616
|
+
var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
|
|
617
|
+
function avatarsDir() {
|
|
618
|
+
return resolve3(voluteHome(), "avatars");
|
|
619
|
+
}
|
|
964
620
|
var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
965
621
|
const user = c.get("user");
|
|
966
622
|
const { currentPassword, newPassword } = c.req.valid("json");
|
|
967
623
|
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
968
624
|
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
969
625
|
return c.json({ ok: true });
|
|
626
|
+
}).put("/profile", zValidator("json", profileSchema), async (c) => {
|
|
627
|
+
const user = c.get("user");
|
|
628
|
+
const body = c.req.valid("json");
|
|
629
|
+
await updateUserProfile(user.id, body);
|
|
630
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
631
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
632
|
+
broadcast({
|
|
633
|
+
type: "profile_updated",
|
|
634
|
+
mind: user.username,
|
|
635
|
+
summary: `${user.username} profile updated`
|
|
636
|
+
});
|
|
637
|
+
return c.json({ ok: true });
|
|
638
|
+
}).post("/avatar", async (c) => {
|
|
639
|
+
const user = c.get("user");
|
|
640
|
+
const body = await c.req.parseBody();
|
|
641
|
+
const file = body.file;
|
|
642
|
+
if (!(file instanceof File)) {
|
|
643
|
+
return c.json({ error: "No file uploaded" }, 400);
|
|
644
|
+
}
|
|
645
|
+
if (file.size > MAX_AVATAR_SIZE) {
|
|
646
|
+
return c.json({ error: "File too large (max 2MB)" }, 400);
|
|
647
|
+
}
|
|
648
|
+
const ext = extname(file.name).toLowerCase();
|
|
649
|
+
if (!AVATAR_MIME[ext]) {
|
|
650
|
+
return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
|
|
651
|
+
}
|
|
652
|
+
const dir = avatarsDir();
|
|
653
|
+
mkdirSync2(dir, { recursive: true });
|
|
654
|
+
const filename = `avatar-${user.id}${ext}`;
|
|
655
|
+
const buffer2 = Buffer.from(await file.arrayBuffer());
|
|
656
|
+
writeFileSync2(resolve3(dir, filename), buffer2);
|
|
657
|
+
if (user.avatar && user.avatar !== filename) {
|
|
658
|
+
const oldPath = resolve3(dir, user.avatar);
|
|
659
|
+
rmSync(oldPath, { force: true });
|
|
660
|
+
}
|
|
661
|
+
await updateUserProfile(user.id, { avatar: filename });
|
|
662
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
663
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
664
|
+
broadcast({
|
|
665
|
+
type: "profile_updated",
|
|
666
|
+
mind: user.username,
|
|
667
|
+
summary: `${user.username} avatar updated`
|
|
668
|
+
});
|
|
669
|
+
return c.json({ ok: true, avatar: filename });
|
|
670
|
+
}).delete("/avatar", async (c) => {
|
|
671
|
+
const user = c.get("user");
|
|
672
|
+
if (user.avatar) {
|
|
673
|
+
const path = resolve3(avatarsDir(), user.avatar);
|
|
674
|
+
rmSync(path, { force: true });
|
|
675
|
+
}
|
|
676
|
+
await updateUserProfile(user.id, { avatar: null });
|
|
677
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
678
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
679
|
+
broadcast({
|
|
680
|
+
type: "profile_updated",
|
|
681
|
+
mind: user.username,
|
|
682
|
+
summary: `${user.username} avatar removed`
|
|
683
|
+
});
|
|
684
|
+
return c.json({ ok: true });
|
|
970
685
|
});
|
|
971
686
|
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
972
687
|
const user = c.get("user");
|
|
@@ -988,8 +703,63 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
|
988
703
|
const user = c.get("user");
|
|
989
704
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
990
705
|
const id = parseInt(c.req.param("id"), 10);
|
|
706
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
991
707
|
await approveUser(id);
|
|
992
708
|
return c.json({ ok: true });
|
|
709
|
+
}).post(
|
|
710
|
+
"/users/:id/role",
|
|
711
|
+
zValidator("json", z.object({ role: z.enum(["admin", "user"]) })),
|
|
712
|
+
async (c) => {
|
|
713
|
+
const user = c.get("user");
|
|
714
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
715
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
716
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
717
|
+
const { role } = c.req.valid("json");
|
|
718
|
+
if (role !== "admin") {
|
|
719
|
+
const adminCount = await countAdmins();
|
|
720
|
+
if (adminCount <= 1) {
|
|
721
|
+
const target = await getUser(id);
|
|
722
|
+
if (target?.role === "admin") {
|
|
723
|
+
return c.json({ error: "Cannot remove the last admin" }, 400);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
await setUserRole(id, role);
|
|
728
|
+
return c.json({ ok: true });
|
|
729
|
+
}
|
|
730
|
+
).put("/users/:id/profile", zValidator("json", profileSchema), async (c) => {
|
|
731
|
+
const user = c.get("user");
|
|
732
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
733
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
734
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
735
|
+
const body = c.req.valid("json");
|
|
736
|
+
await updateUserProfile(id, body);
|
|
737
|
+
const updatedUser = await getUser(id);
|
|
738
|
+
if (updatedUser) {
|
|
739
|
+
broadcast({
|
|
740
|
+
type: "profile_updated",
|
|
741
|
+
mind: updatedUser.username,
|
|
742
|
+
summary: `${updatedUser.username} profile updated`
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
return c.json({ ok: true });
|
|
746
|
+
}).delete("/users/:id", async (c) => {
|
|
747
|
+
const user = c.get("user");
|
|
748
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
749
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
750
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
751
|
+
if (id === user.id) return c.json({ error: "Cannot delete yourself" }, 400);
|
|
752
|
+
const target = await getUser(id);
|
|
753
|
+
if (!target) return c.json({ error: "User not found" }, 404);
|
|
754
|
+
if (target.role === "admin") {
|
|
755
|
+
const adminCount = await countAdmins();
|
|
756
|
+
if (adminCount <= 1) return c.json({ error: "Cannot delete the last admin" }, 400);
|
|
757
|
+
}
|
|
758
|
+
if (target.user_type === "mind") {
|
|
759
|
+
return c.json({ error: "Use the mind deletion API to delete minds" }, 400);
|
|
760
|
+
}
|
|
761
|
+
await deleteUser(id);
|
|
762
|
+
return c.json({ ok: true });
|
|
993
763
|
});
|
|
994
764
|
var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
|
|
995
765
|
const { username, password } = c.req.valid("json");
|
|
@@ -1000,7 +770,7 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1000
770
|
const user = await createUser(username, password);
|
|
1001
771
|
if (user.role === "admin") {
|
|
1002
772
|
const sessionId = await createSession(user.id);
|
|
1003
|
-
setCookie(c, "volute_session", sessionId,
|
|
773
|
+
setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
|
|
1004
774
|
}
|
|
1005
775
|
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1006
776
|
}).post("/login", zValidator("json", credentialsSchema), async (c) => {
|
|
@@ -1010,7 +780,7 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1010
780
|
return c.json({ error: "Invalid credentials" }, 401);
|
|
1011
781
|
}
|
|
1012
782
|
const sessionId = await createSession(user.id);
|
|
1013
|
-
setCookie(c, "volute_session", sessionId,
|
|
783
|
+
setCookie(c, "volute_session", sessionId, SESSION_COOKIE_OPTIONS);
|
|
1014
784
|
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1015
785
|
}).post("/logout", async (c) => {
|
|
1016
786
|
const sessionId = getCookie2(c, "volute_session");
|
|
@@ -1026,7 +796,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1026
796
|
if (userId == null) return c.json({ error: "Not logged in" }, 401);
|
|
1027
797
|
const user = await getUser(userId);
|
|
1028
798
|
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
1029
|
-
return c.json({
|
|
799
|
+
return c.json({
|
|
800
|
+
id: user.id,
|
|
801
|
+
username: user.username,
|
|
802
|
+
role: user.role,
|
|
803
|
+
display_name: user.display_name,
|
|
804
|
+
description: user.description,
|
|
805
|
+
avatar: user.avatar
|
|
806
|
+
});
|
|
807
|
+
}).get("/avatars/:filename", async (c) => {
|
|
808
|
+
const filename = c.req.param("filename");
|
|
809
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
810
|
+
return c.json({ error: "Invalid filename" }, 400);
|
|
811
|
+
}
|
|
812
|
+
const dir = avatarsDir();
|
|
813
|
+
const filePath = resolve3(dir, filename);
|
|
814
|
+
if (!filePath.startsWith(`${dir}/`)) return c.json({ error: "Invalid path" }, 400);
|
|
815
|
+
if (!existsSync3(filePath)) return c.json({ error: "Not found" }, 404);
|
|
816
|
+
const ext = extname(filename).toLowerCase();
|
|
817
|
+
const mime = AVATAR_MIME[ext];
|
|
818
|
+
if (!mime) return c.json({ error: "Invalid file type" }, 400);
|
|
819
|
+
const data = readFileSync2(filePath);
|
|
820
|
+
return c.body(data, 200, {
|
|
821
|
+
"Content-Type": mime,
|
|
822
|
+
"Cache-Control": "public, max-age=3600",
|
|
823
|
+
"X-Content-Type-Options": "nosniff"
|
|
824
|
+
});
|
|
1030
825
|
}).route("/", admin).route("/", authenticated);
|
|
1031
826
|
var auth_default = app2;
|
|
1032
827
|
|
|
@@ -1067,8 +862,8 @@ async function read(env, channelSlug, limit) {
|
|
|
1067
862
|
if (!res.ok) {
|
|
1068
863
|
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
1069
864
|
}
|
|
1070
|
-
const
|
|
1071
|
-
return
|
|
865
|
+
const messages = await res.json();
|
|
866
|
+
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
1072
867
|
}
|
|
1073
868
|
async function send(env, channelSlug, message, images) {
|
|
1074
869
|
const token = requireToken(env);
|
|
@@ -1300,8 +1095,8 @@ async function listConversations2(env) {
|
|
|
1300
1095
|
const userMap = /* @__PURE__ */ new Map();
|
|
1301
1096
|
const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
|
|
1302
1097
|
if (imChannels.length > 0) {
|
|
1303
|
-
const
|
|
1304
|
-
for (const u of
|
|
1098
|
+
const users = await listUsers3(env);
|
|
1099
|
+
for (const u of users) {
|
|
1305
1100
|
userMap.set(u.id, u.username);
|
|
1306
1101
|
}
|
|
1307
1102
|
}
|
|
@@ -1496,16 +1291,16 @@ __export(volute_exports, {
|
|
|
1496
1291
|
read: () => read4,
|
|
1497
1292
|
send: () => send4
|
|
1498
1293
|
});
|
|
1499
|
-
import { existsSync as
|
|
1500
|
-
import { resolve as
|
|
1294
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1295
|
+
import { resolve as resolve4 } from "path";
|
|
1501
1296
|
function getDaemonConfig() {
|
|
1502
|
-
const configPath2 =
|
|
1503
|
-
if (!
|
|
1297
|
+
const configPath2 = resolve4(voluteHome(), "daemon.json");
|
|
1298
|
+
if (!existsSync4(configPath2)) {
|
|
1504
1299
|
throw new Error("Volute daemon is not running");
|
|
1505
1300
|
}
|
|
1506
1301
|
let config;
|
|
1507
1302
|
try {
|
|
1508
|
-
config = JSON.parse(
|
|
1303
|
+
config = JSON.parse(readFileSync3(configPath2, "utf-8"));
|
|
1509
1304
|
} catch (err) {
|
|
1510
1305
|
throw new Error(`Failed to parse ${configPath2}: ${err}`);
|
|
1511
1306
|
}
|
|
@@ -1531,8 +1326,8 @@ async function read4(env, channelSlug, limit) {
|
|
|
1531
1326
|
if (!res.ok) {
|
|
1532
1327
|
throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
|
|
1533
1328
|
}
|
|
1534
|
-
const
|
|
1535
|
-
return
|
|
1329
|
+
const messages = await res.json();
|
|
1330
|
+
return messages.slice(-limit).map((m) => {
|
|
1536
1331
|
const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
|
|
1537
1332
|
return `${m.sender_name ?? m.role}: ${text}`;
|
|
1538
1333
|
}).join("\n");
|
|
@@ -1759,8 +1554,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
|
|
|
1759
1554
|
return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
|
|
1760
1555
|
const env = buildEnv(name);
|
|
1761
1556
|
try {
|
|
1762
|
-
const
|
|
1763
|
-
return c.json(
|
|
1557
|
+
const users = await driver.listUsers(env);
|
|
1558
|
+
return c.json(users);
|
|
1764
1559
|
} catch (err) {
|
|
1765
1560
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1766
1561
|
}
|
|
@@ -1942,14 +1737,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
|
1942
1737
|
var env_default = app5;
|
|
1943
1738
|
|
|
1944
1739
|
// src/web/api/file-sharing.ts
|
|
1945
|
-
import { readFileSync as
|
|
1946
|
-
import { resolve as
|
|
1740
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
1741
|
+
import { resolve as resolve6 } from "path";
|
|
1947
1742
|
import { Hono as Hono6 } from "hono";
|
|
1948
1743
|
|
|
1949
1744
|
// src/lib/file-sharing.ts
|
|
1950
1745
|
import { randomBytes } from "crypto";
|
|
1951
|
-
import { existsSync as
|
|
1952
|
-
import { basename, join, normalize, resolve as
|
|
1746
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1747
|
+
import { basename, join, normalize, resolve as resolve5 } from "path";
|
|
1953
1748
|
function validateFilePath(filePath) {
|
|
1954
1749
|
if (!filePath) return "File path is required";
|
|
1955
1750
|
const normalized = normalize(filePath);
|
|
@@ -1962,13 +1757,13 @@ function validateFilePath(filePath) {
|
|
|
1962
1757
|
return null;
|
|
1963
1758
|
}
|
|
1964
1759
|
function configPath(dir) {
|
|
1965
|
-
return
|
|
1760
|
+
return resolve5(dir, "home", ".config", "file-sharing.json");
|
|
1966
1761
|
}
|
|
1967
1762
|
function readFileSharingConfig(dir) {
|
|
1968
1763
|
const p = configPath(dir);
|
|
1969
|
-
if (!
|
|
1764
|
+
if (!existsSync5(p)) return {};
|
|
1970
1765
|
try {
|
|
1971
|
-
return JSON.parse(
|
|
1766
|
+
return JSON.parse(readFileSync4(p, "utf-8"));
|
|
1972
1767
|
} catch (err) {
|
|
1973
1768
|
console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
|
|
1974
1769
|
return {};
|
|
@@ -1976,8 +1771,8 @@ function readFileSharingConfig(dir) {
|
|
|
1976
1771
|
}
|
|
1977
1772
|
function writeFileSharingConfig(dir, config) {
|
|
1978
1773
|
const p = configPath(dir);
|
|
1979
|
-
|
|
1980
|
-
|
|
1774
|
+
mkdirSync3(resolve5(p, ".."), { recursive: true });
|
|
1775
|
+
writeFileSync3(p, `${JSON.stringify(config, null, 2)}
|
|
1981
1776
|
`);
|
|
1982
1777
|
}
|
|
1983
1778
|
function isTrustedSender(dir, sender) {
|
|
@@ -2000,7 +1795,7 @@ function removeTrust(dir, sender) {
|
|
|
2000
1795
|
writeFileSharingConfig(dir, config);
|
|
2001
1796
|
}
|
|
2002
1797
|
function pendingDir(receiver) {
|
|
2003
|
-
return
|
|
1798
|
+
return resolve5(stateDir(receiver), "pending-files");
|
|
2004
1799
|
}
|
|
2005
1800
|
function validateId(id) {
|
|
2006
1801
|
if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
@@ -2019,8 +1814,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2019
1814
|
throw new Error("Invalid sender name");
|
|
2020
1815
|
}
|
|
2021
1816
|
const id = generateId(sender);
|
|
2022
|
-
const dir =
|
|
2023
|
-
|
|
1817
|
+
const dir = resolve5(pendingDir(receiver), id);
|
|
1818
|
+
mkdirSync3(dir, { recursive: true });
|
|
2024
1819
|
const metadata = {
|
|
2025
1820
|
id,
|
|
2026
1821
|
sender,
|
|
@@ -2029,22 +1824,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2029
1824
|
size: content.length,
|
|
2030
1825
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2031
1826
|
};
|
|
2032
|
-
|
|
1827
|
+
writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
|
|
2033
1828
|
`);
|
|
2034
|
-
|
|
1829
|
+
writeFileSync3(resolve5(dir, "data"), content);
|
|
2035
1830
|
return { id };
|
|
2036
1831
|
}
|
|
2037
1832
|
function listPending(receiver) {
|
|
2038
1833
|
const dir = pendingDir(receiver);
|
|
2039
|
-
if (!
|
|
1834
|
+
if (!existsSync5(dir)) return [];
|
|
2040
1835
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2041
1836
|
const result = [];
|
|
2042
1837
|
for (const entry of entries) {
|
|
2043
1838
|
if (!entry.isDirectory()) continue;
|
|
2044
|
-
const metaPath =
|
|
2045
|
-
if (!
|
|
1839
|
+
const metaPath = resolve5(dir, entry.name, "metadata.json");
|
|
1840
|
+
if (!existsSync5(metaPath)) continue;
|
|
2046
1841
|
try {
|
|
2047
|
-
result.push(JSON.parse(
|
|
1842
|
+
result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
|
|
2048
1843
|
} catch (err) {
|
|
2049
1844
|
console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
|
|
2050
1845
|
}
|
|
@@ -2053,10 +1848,10 @@ function listPending(receiver) {
|
|
|
2053
1848
|
}
|
|
2054
1849
|
function getPending(receiver, id) {
|
|
2055
1850
|
validateId(id);
|
|
2056
|
-
const metaPath =
|
|
2057
|
-
if (!
|
|
1851
|
+
const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
|
|
1852
|
+
if (!existsSync5(metaPath)) return null;
|
|
2058
1853
|
try {
|
|
2059
|
-
return JSON.parse(
|
|
1854
|
+
return JSON.parse(readFileSync4(metaPath, "utf-8"));
|
|
2060
1855
|
} catch (err) {
|
|
2061
1856
|
console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
|
|
2062
1857
|
return null;
|
|
@@ -2071,27 +1866,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
|
|
|
2071
1866
|
if (sender.includes("/") || sender.includes("\\")) {
|
|
2072
1867
|
throw new Error("Invalid sender name");
|
|
2073
1868
|
}
|
|
2074
|
-
const destDir =
|
|
2075
|
-
|
|
2076
|
-
const destPath =
|
|
2077
|
-
|
|
1869
|
+
const destDir = resolve5(receiverDir, "home", inbox, sender);
|
|
1870
|
+
mkdirSync3(destDir, { recursive: true });
|
|
1871
|
+
const destPath = resolve5(destDir, basename(filename));
|
|
1872
|
+
writeFileSync3(destPath, content);
|
|
2078
1873
|
return join(inbox, sender, basename(filename));
|
|
2079
1874
|
}
|
|
2080
1875
|
function acceptPending(receiver, id, receiverDir) {
|
|
2081
1876
|
const meta = getPending(receiver, id);
|
|
2082
1877
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2083
|
-
const dataPath =
|
|
2084
|
-
const content =
|
|
1878
|
+
const dataPath = resolve5(pendingDir(receiver), id, "data");
|
|
1879
|
+
const content = readFileSync4(dataPath);
|
|
2085
1880
|
const config = readFileSharingConfig(receiverDir);
|
|
2086
1881
|
const inboxPath = config.inboxPath ?? "inbox";
|
|
2087
1882
|
const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
|
|
2088
|
-
|
|
1883
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2089
1884
|
return { sender: meta.sender, filename: meta.filename, destPath };
|
|
2090
1885
|
}
|
|
2091
1886
|
function rejectPending(receiver, id) {
|
|
2092
1887
|
const meta = getPending(receiver, id);
|
|
2093
1888
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2094
|
-
|
|
1889
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2095
1890
|
return { sender: meta.sender, filename: meta.filename };
|
|
2096
1891
|
}
|
|
2097
1892
|
function formatFileSize(bytes) {
|
|
@@ -2132,21 +1927,21 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2132
1927
|
const pathErr = validateFilePath(body.filePath);
|
|
2133
1928
|
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2134
1929
|
const senderDir = mindDir(senderName);
|
|
2135
|
-
const filePath =
|
|
2136
|
-
const
|
|
2137
|
-
const
|
|
2138
|
-
if (!
|
|
2139
|
-
if (
|
|
1930
|
+
const filePath = resolve6(senderDir, "home", body.filePath);
|
|
1931
|
+
const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
1932
|
+
const stat5 = statSync(filePath, { throwIfNoEntry: false });
|
|
1933
|
+
if (!stat5) return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
1934
|
+
if (stat5.size > MAX_FILE_SIZE2) {
|
|
2140
1935
|
return c.json(
|
|
2141
1936
|
{
|
|
2142
|
-
error: `File too large (${formatFileSize(
|
|
1937
|
+
error: `File too large (${formatFileSize(stat5.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
|
|
2143
1938
|
},
|
|
2144
1939
|
413
|
|
2145
1940
|
);
|
|
2146
1941
|
}
|
|
2147
1942
|
let content;
|
|
2148
1943
|
try {
|
|
2149
|
-
content =
|
|
1944
|
+
content = readFileSync5(filePath);
|
|
2150
1945
|
} catch {
|
|
2151
1946
|
return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2152
1947
|
}
|
|
@@ -2248,31 +2043,31 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2248
2043
|
var file_sharing_default = app6;
|
|
2249
2044
|
|
|
2250
2045
|
// src/web/api/files.ts
|
|
2251
|
-
import { existsSync as
|
|
2046
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2252
2047
|
import { readdir, readFile, realpath, stat } from "fs/promises";
|
|
2253
|
-
import { extname, resolve as
|
|
2048
|
+
import { extname as extname2, resolve as resolve7 } from "path";
|
|
2254
2049
|
import { Hono as Hono7 } from "hono";
|
|
2255
2050
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2256
|
-
var
|
|
2051
|
+
var AVATAR_MIME2 = {
|
|
2257
2052
|
".png": "image/png",
|
|
2258
2053
|
".jpg": "image/jpeg",
|
|
2259
2054
|
".jpeg": "image/jpeg",
|
|
2260
2055
|
".gif": "image/gif",
|
|
2261
2056
|
".webp": "image/webp"
|
|
2262
2057
|
};
|
|
2263
|
-
var
|
|
2058
|
+
var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
|
|
2264
2059
|
var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
2265
2060
|
const name = c.req.param("name");
|
|
2266
2061
|
const entry = findMind(name);
|
|
2267
2062
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2268
2063
|
const dir = mindDir(name);
|
|
2269
2064
|
const config = readVoluteConfig(dir);
|
|
2270
|
-
if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
|
|
2271
|
-
const ext =
|
|
2272
|
-
const mime =
|
|
2065
|
+
if (!config?.profile?.avatar) return c.json({ error: "No avatar configured" }, 404);
|
|
2066
|
+
const ext = extname2(config.profile.avatar).toLowerCase();
|
|
2067
|
+
const mime = AVATAR_MIME2[ext];
|
|
2273
2068
|
if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
|
|
2274
|
-
const homeDir =
|
|
2275
|
-
const avatarPath =
|
|
2069
|
+
const homeDir = resolve7(dir, "home");
|
|
2070
|
+
const avatarPath = resolve7(homeDir, config.profile.avatar);
|
|
2276
2071
|
if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
|
|
2277
2072
|
let realAvatarPath;
|
|
2278
2073
|
try {
|
|
@@ -2287,7 +2082,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2287
2082
|
}
|
|
2288
2083
|
try {
|
|
2289
2084
|
const fileStat = await stat(realAvatarPath);
|
|
2290
|
-
if (fileStat.size >
|
|
2085
|
+
if (fileStat.size > MAX_AVATAR_SIZE2) return c.json({ error: "Avatar file too large" }, 400);
|
|
2291
2086
|
const body = await readFile(realAvatarPath);
|
|
2292
2087
|
return c.body(body, 200, {
|
|
2293
2088
|
"Content-Type": mime,
|
|
@@ -2301,8 +2096,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2301
2096
|
const entry = findMind(name);
|
|
2302
2097
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2303
2098
|
const dir = mindDir(name);
|
|
2304
|
-
const homeDir =
|
|
2305
|
-
if (!
|
|
2099
|
+
const homeDir = resolve7(dir, "home");
|
|
2100
|
+
if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2306
2101
|
const allFiles = await readdir(homeDir);
|
|
2307
2102
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2308
2103
|
return c.json(files);
|
|
@@ -2315,8 +2110,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2315
2110
|
const entry = findMind(name);
|
|
2316
2111
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2317
2112
|
const dir = mindDir(name);
|
|
2318
|
-
const filePath =
|
|
2319
|
-
if (!
|
|
2113
|
+
const filePath = resolve7(dir, "home", filename);
|
|
2114
|
+
if (!existsSync6(filePath)) {
|
|
2320
2115
|
return c.json({ error: "File not found" }, 404);
|
|
2321
2116
|
}
|
|
2322
2117
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2329,19 +2124,19 @@ import { Hono as Hono8 } from "hono";
|
|
|
2329
2124
|
|
|
2330
2125
|
// src/lib/identity.ts
|
|
2331
2126
|
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
2332
|
-
import { existsSync as
|
|
2333
|
-
import { resolve as
|
|
2127
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2128
|
+
import { resolve as resolve8 } from "path";
|
|
2334
2129
|
function generateIdentity(mindDir2) {
|
|
2335
|
-
const identityDir =
|
|
2336
|
-
|
|
2130
|
+
const identityDir = resolve8(mindDir2, ".mind/identity");
|
|
2131
|
+
mkdirSync4(identityDir, { recursive: true });
|
|
2337
2132
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
2338
2133
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2339
2134
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2340
2135
|
});
|
|
2341
|
-
const privatePath =
|
|
2342
|
-
const publicPath =
|
|
2343
|
-
|
|
2344
|
-
|
|
2136
|
+
const privatePath = resolve8(identityDir, "private.pem");
|
|
2137
|
+
const publicPath = resolve8(identityDir, "public.pem");
|
|
2138
|
+
writeFileSync4(privatePath, privateKey, { mode: 384 });
|
|
2139
|
+
writeFileSync4(publicPath, publicKey, { mode: 420 });
|
|
2345
2140
|
const config = readVoluteConfig(mindDir2) ?? {};
|
|
2346
2141
|
config.identity = {
|
|
2347
2142
|
privateKey: ".mind/identity/private.pem",
|
|
@@ -2354,17 +2149,17 @@ function getPrivateKey(mindDir2) {
|
|
|
2354
2149
|
const config = readVoluteConfig(mindDir2);
|
|
2355
2150
|
const relPath = config?.identity?.privateKey;
|
|
2356
2151
|
if (!relPath) return null;
|
|
2357
|
-
const fullPath =
|
|
2358
|
-
if (!
|
|
2359
|
-
return
|
|
2152
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2153
|
+
if (!existsSync7(fullPath)) return null;
|
|
2154
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2360
2155
|
}
|
|
2361
2156
|
function getPublicKey(mindDir2) {
|
|
2362
2157
|
const config = readVoluteConfig(mindDir2);
|
|
2363
2158
|
const relPath = config?.identity?.publicKey;
|
|
2364
2159
|
if (!relPath) return null;
|
|
2365
|
-
const fullPath =
|
|
2366
|
-
if (!
|
|
2367
|
-
return
|
|
2160
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2161
|
+
if (!existsSync7(fullPath)) return null;
|
|
2162
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2368
2163
|
}
|
|
2369
2164
|
function getFingerprint(publicKeyPem) {
|
|
2370
2165
|
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
@@ -2417,16 +2212,16 @@ var keys_default = app8;
|
|
|
2417
2212
|
|
|
2418
2213
|
// src/web/api/logs.ts
|
|
2419
2214
|
import { spawn } from "child_process";
|
|
2420
|
-
import { existsSync as
|
|
2421
|
-
import { resolve as
|
|
2215
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2216
|
+
import { resolve as resolve9 } from "path";
|
|
2422
2217
|
import { Hono as Hono9 } from "hono";
|
|
2423
2218
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
2424
2219
|
var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
2425
2220
|
const name = c.req.param("name");
|
|
2426
2221
|
const entry = findMind(name);
|
|
2427
2222
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2428
|
-
const logFile =
|
|
2429
|
-
if (!
|
|
2223
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2224
|
+
if (!existsSync8(logFile)) {
|
|
2430
2225
|
return c.json({ error: "No log file found" }, 404);
|
|
2431
2226
|
}
|
|
2432
2227
|
return streamSSE2(c, async (stream) => {
|
|
@@ -2444,17 +2239,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2444
2239
|
stream.onAbort(() => {
|
|
2445
2240
|
tail.kill();
|
|
2446
2241
|
});
|
|
2447
|
-
await new Promise((
|
|
2448
|
-
tail.on("exit",
|
|
2449
|
-
stream.onAbort(
|
|
2242
|
+
await new Promise((resolve20) => {
|
|
2243
|
+
tail.on("exit", resolve20);
|
|
2244
|
+
stream.onAbort(resolve20);
|
|
2450
2245
|
});
|
|
2451
2246
|
});
|
|
2452
2247
|
}).get("/:name/logs/tail", async (c) => {
|
|
2453
2248
|
const name = c.req.param("name");
|
|
2454
2249
|
const entry = findMind(name);
|
|
2455
2250
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2456
|
-
const logFile =
|
|
2457
|
-
if (!
|
|
2251
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2252
|
+
if (!existsSync8(logFile)) {
|
|
2458
2253
|
return c.json({ error: "No log file found" }, 404);
|
|
2459
2254
|
}
|
|
2460
2255
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -2464,8 +2259,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2464
2259
|
tail.stdout.on("data", (data) => {
|
|
2465
2260
|
output += data.toString();
|
|
2466
2261
|
});
|
|
2467
|
-
await new Promise((
|
|
2468
|
-
tail.on("exit",
|
|
2262
|
+
await new Promise((resolve20) => {
|
|
2263
|
+
tail.on("exit", resolve20);
|
|
2469
2264
|
});
|
|
2470
2265
|
return c.text(output);
|
|
2471
2266
|
});
|
|
@@ -2493,12 +2288,12 @@ var app10 = new Hono10().get("/:name/skills", async (c) => {
|
|
|
2493
2288
|
const { skillId } = c.req.valid("json");
|
|
2494
2289
|
const dir = mindDir(name);
|
|
2495
2290
|
try {
|
|
2496
|
-
await installSkill(name, dir, skillId);
|
|
2291
|
+
const result = await installSkill(name, dir, skillId);
|
|
2292
|
+
return c.json({ ok: true, ...result });
|
|
2497
2293
|
} catch (e) {
|
|
2498
2294
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2499
2295
|
return c.json({ error: msg }, 400);
|
|
2500
2296
|
}
|
|
2501
|
-
return c.json({ ok: true });
|
|
2502
2297
|
}
|
|
2503
2298
|
).post(
|
|
2504
2299
|
"/:name/skills/update",
|
|
@@ -2555,33 +2350,33 @@ var mind_skills_default = app10;
|
|
|
2555
2350
|
// src/web/api/minds.ts
|
|
2556
2351
|
import {
|
|
2557
2352
|
cpSync,
|
|
2558
|
-
existsSync as
|
|
2559
|
-
mkdirSync as
|
|
2353
|
+
existsSync as existsSync10,
|
|
2354
|
+
mkdirSync as mkdirSync6,
|
|
2560
2355
|
readdirSync as readdirSync4,
|
|
2561
|
-
readFileSync as
|
|
2562
|
-
rmSync as
|
|
2563
|
-
writeFileSync as
|
|
2356
|
+
readFileSync as readFileSync9,
|
|
2357
|
+
rmSync as rmSync4,
|
|
2358
|
+
writeFileSync as writeFileSync7
|
|
2564
2359
|
} from "fs";
|
|
2565
|
-
import { resolve as
|
|
2360
|
+
import { resolve as resolve12 } from "path";
|
|
2566
2361
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2567
|
-
import { and
|
|
2362
|
+
import { and, desc as desc2, eq as eq2, sql } from "drizzle-orm";
|
|
2568
2363
|
import { Hono as Hono11 } from "hono";
|
|
2569
2364
|
import { z as z3 } from "zod";
|
|
2570
2365
|
|
|
2571
2366
|
// src/lib/consolidate.ts
|
|
2572
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2573
|
-
import { resolve as
|
|
2367
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2368
|
+
import { resolve as resolve10 } from "path";
|
|
2574
2369
|
async function consolidateMemory(mindDir2) {
|
|
2575
|
-
const soulPath =
|
|
2576
|
-
const memoryPath =
|
|
2577
|
-
const memoryDir =
|
|
2578
|
-
const soul =
|
|
2370
|
+
const soulPath = resolve10(mindDir2, "home/SOUL.md");
|
|
2371
|
+
const memoryPath = resolve10(mindDir2, "home/MEMORY.md");
|
|
2372
|
+
const memoryDir = resolve10(mindDir2, "home/memory");
|
|
2373
|
+
const soul = readFileSync7(soulPath, "utf-8");
|
|
2579
2374
|
const logs = [];
|
|
2580
2375
|
try {
|
|
2581
2376
|
const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
2582
2377
|
for (const filename of files) {
|
|
2583
2378
|
const date = filename.replace(".md", "");
|
|
2584
|
-
const content2 =
|
|
2379
|
+
const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
|
|
2585
2380
|
if (content2) {
|
|
2586
2381
|
logs.push(`### ${date}
|
|
2587
2382
|
|
|
@@ -2631,7 +2426,7 @@ ${content2}`);
|
|
|
2631
2426
|
const data = await res.json();
|
|
2632
2427
|
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
2633
2428
|
if (content) {
|
|
2634
|
-
|
|
2429
|
+
writeFileSync5(memoryPath, `${content}
|
|
2635
2430
|
`);
|
|
2636
2431
|
console.log("MEMORY.md created successfully.");
|
|
2637
2432
|
} else {
|
|
@@ -2640,28 +2435,28 @@ ${content2}`);
|
|
|
2640
2435
|
}
|
|
2641
2436
|
|
|
2642
2437
|
// src/lib/convert-session.ts
|
|
2643
|
-
import { randomUUID
|
|
2644
|
-
import { mkdirSync as
|
|
2438
|
+
import { randomUUID } from "crypto";
|
|
2439
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
2645
2440
|
import { homedir } from "os";
|
|
2646
|
-
import { resolve as
|
|
2441
|
+
import { resolve as resolve11 } from "path";
|
|
2647
2442
|
function convertSession(opts) {
|
|
2648
|
-
const lines =
|
|
2649
|
-
const sessionId =
|
|
2443
|
+
const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
|
|
2444
|
+
const sessionId = randomUUID();
|
|
2650
2445
|
const idMap = /* @__PURE__ */ new Map();
|
|
2651
|
-
const
|
|
2446
|
+
const messages = [];
|
|
2652
2447
|
for (const line of lines) {
|
|
2653
2448
|
const event = JSON.parse(line);
|
|
2654
2449
|
if (event.type === "message" && event.message) {
|
|
2655
|
-
|
|
2450
|
+
messages.push(event);
|
|
2656
2451
|
}
|
|
2657
2452
|
}
|
|
2658
2453
|
const sdkEvents = [];
|
|
2659
2454
|
let lastSdkUuid = null;
|
|
2660
|
-
for (let i = 0; i <
|
|
2661
|
-
const event =
|
|
2455
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2456
|
+
const event = messages[i];
|
|
2662
2457
|
const msg = event.message;
|
|
2663
2458
|
if (msg.role === "user") {
|
|
2664
|
-
const uuid =
|
|
2459
|
+
const uuid = randomUUID();
|
|
2665
2460
|
idMap.set(event.id, uuid);
|
|
2666
2461
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2667
2462
|
const sdkEvent = {
|
|
@@ -2685,7 +2480,7 @@ function convertSession(opts) {
|
|
|
2685
2480
|
} else if (msg.role === "assistant") {
|
|
2686
2481
|
const content = convertAssistantContent(msg.content);
|
|
2687
2482
|
if (content.length === 0) continue;
|
|
2688
|
-
const uuid =
|
|
2483
|
+
const uuid = randomUUID();
|
|
2689
2484
|
idMap.set(event.id, uuid);
|
|
2690
2485
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2691
2486
|
const stopReason = mapStopReason(msg.stopReason);
|
|
@@ -2700,12 +2495,12 @@ function convertSession(opts) {
|
|
|
2700
2495
|
isSidechain: false,
|
|
2701
2496
|
userType: "external",
|
|
2702
2497
|
type: "assistant",
|
|
2703
|
-
requestId: `req_imported_${
|
|
2498
|
+
requestId: `req_imported_${randomUUID()}`,
|
|
2704
2499
|
message: {
|
|
2705
2500
|
role: "assistant",
|
|
2706
2501
|
content,
|
|
2707
2502
|
type: "message",
|
|
2708
|
-
id: `msg_imported_${
|
|
2503
|
+
id: `msg_imported_${randomUUID()}`,
|
|
2709
2504
|
model: mapModel(msg.model),
|
|
2710
2505
|
stop_reason: stopReason,
|
|
2711
2506
|
stop_sequence: null,
|
|
@@ -2719,8 +2514,8 @@ function convertSession(opts) {
|
|
|
2719
2514
|
let lastToolResultId = event.id;
|
|
2720
2515
|
let lastTimestamp = event.timestamp;
|
|
2721
2516
|
let j = i;
|
|
2722
|
-
while (j <
|
|
2723
|
-
const tr =
|
|
2517
|
+
while (j < messages.length && messages[j].message.role === "toolResult") {
|
|
2518
|
+
const tr = messages[j];
|
|
2724
2519
|
const trMsg = tr.message;
|
|
2725
2520
|
lastToolResultId = tr.id;
|
|
2726
2521
|
lastTimestamp = tr.timestamp;
|
|
@@ -2733,7 +2528,7 @@ function convertSession(opts) {
|
|
|
2733
2528
|
j++;
|
|
2734
2529
|
}
|
|
2735
2530
|
i = j - 1;
|
|
2736
|
-
const uuid =
|
|
2531
|
+
const uuid = randomUUID();
|
|
2737
2532
|
idMap.set(lastToolResultId, uuid);
|
|
2738
2533
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
|
|
2739
2534
|
const sdkEvent = {
|
|
@@ -2759,10 +2554,10 @@ function convertSession(opts) {
|
|
|
2759
2554
|
}
|
|
2760
2555
|
}
|
|
2761
2556
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
2762
|
-
const sdkDir =
|
|
2763
|
-
|
|
2764
|
-
const sdkPath =
|
|
2765
|
-
|
|
2557
|
+
const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
|
|
2558
|
+
mkdirSync5(sdkDir, { recursive: true });
|
|
2559
|
+
const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
|
|
2560
|
+
writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
|
|
2766
2561
|
`);
|
|
2767
2562
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
2768
2563
|
return sessionId;
|
|
@@ -2814,7 +2609,7 @@ function convertAssistantContent(content) {
|
|
|
2814
2609
|
}
|
|
2815
2610
|
|
|
2816
2611
|
// src/lib/variant-cleanup.ts
|
|
2817
|
-
import { existsSync as
|
|
2612
|
+
import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
|
|
2818
2613
|
async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
|
|
2819
2614
|
if (opts?.stop) {
|
|
2820
2615
|
try {
|
|
@@ -2822,11 +2617,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
|
|
|
2822
2617
|
} catch {
|
|
2823
2618
|
}
|
|
2824
2619
|
}
|
|
2825
|
-
if (
|
|
2620
|
+
if (existsSync9(variantPath)) {
|
|
2826
2621
|
try {
|
|
2827
2622
|
await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
|
|
2828
2623
|
} catch {
|
|
2829
|
-
|
|
2624
|
+
rmSync3(variantPath, { recursive: true, force: true });
|
|
2830
2625
|
try {
|
|
2831
2626
|
await gitExec(["worktree", "prune"], { cwd: projectRoot });
|
|
2832
2627
|
} catch {
|
|
@@ -2853,7 +2648,7 @@ async function getMindStatus(name, port) {
|
|
|
2853
2648
|
const manager = getMindManager();
|
|
2854
2649
|
let status = "stopped";
|
|
2855
2650
|
try {
|
|
2856
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
2651
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
2857
2652
|
if (getSleepManagerIfReady()?.isSleeping(name)) {
|
|
2858
2653
|
status = "sleeping";
|
|
2859
2654
|
}
|
|
@@ -2888,9 +2683,9 @@ async function getMindStatus(name, port) {
|
|
|
2888
2683
|
return {
|
|
2889
2684
|
status,
|
|
2890
2685
|
channels,
|
|
2891
|
-
displayName: config?.displayName,
|
|
2892
|
-
description: config?.description,
|
|
2893
|
-
avatar: config?.avatar
|
|
2686
|
+
displayName: config?.profile?.displayName,
|
|
2687
|
+
description: config?.profile?.description,
|
|
2688
|
+
avatar: config?.profile?.avatar
|
|
2894
2689
|
};
|
|
2895
2690
|
}
|
|
2896
2691
|
var TEMPLATE_BRANCH = "volute/template";
|
|
@@ -2911,7 +2706,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
|
|
|
2911
2706
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
2912
2707
|
}
|
|
2913
2708
|
async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
2914
|
-
const tempWorktree =
|
|
2709
|
+
const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
|
|
2915
2710
|
let branchExists = false;
|
|
2916
2711
|
try {
|
|
2917
2712
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -2922,8 +2717,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2922
2717
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2923
2718
|
} catch {
|
|
2924
2719
|
}
|
|
2925
|
-
if (
|
|
2926
|
-
|
|
2720
|
+
if (existsSync10(tempWorktree)) {
|
|
2721
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2927
2722
|
}
|
|
2928
2723
|
const templatesRoot = findTemplatesRoot();
|
|
2929
2724
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -2943,9 +2738,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2943
2738
|
});
|
|
2944
2739
|
}
|
|
2945
2740
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
2946
|
-
const initDir =
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
2741
|
+
const initDir = resolve12(tempWorktree, ".init");
|
|
2742
|
+
if (existsSync10(initDir)) {
|
|
2743
|
+
rmSync4(initDir, { recursive: true, force: true });
|
|
2949
2744
|
}
|
|
2950
2745
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
2951
2746
|
try {
|
|
@@ -2958,10 +2753,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2958
2753
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2959
2754
|
} catch {
|
|
2960
2755
|
}
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
2756
|
+
if (existsSync10(tempWorktree)) {
|
|
2757
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2963
2758
|
}
|
|
2964
|
-
|
|
2759
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
2965
2760
|
}
|
|
2966
2761
|
}
|
|
2967
2762
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -2984,14 +2779,14 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
2984
2779
|
async function npmInstallAsMind(cwd, mindName) {
|
|
2985
2780
|
if (isIsolationEnabled()) {
|
|
2986
2781
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
2987
|
-
await exec(cmd, args, { cwd, env: { ...process.env, HOME:
|
|
2782
|
+
await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve12(cwd, "home") } });
|
|
2988
2783
|
} else {
|
|
2989
2784
|
await exec("npm", ["install"], { cwd });
|
|
2990
2785
|
}
|
|
2991
2786
|
}
|
|
2992
2787
|
async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
2993
|
-
const extractedMindDir =
|
|
2994
|
-
if (!
|
|
2788
|
+
const extractedMindDir = resolve12(tempDir, "mind");
|
|
2789
|
+
if (!existsSync10(extractedMindDir)) {
|
|
2995
2790
|
return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
|
|
2996
2791
|
}
|
|
2997
2792
|
if (!manifest?.includes || !manifest.name || !manifest.template) {
|
|
@@ -3009,21 +2804,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3009
2804
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3010
2805
|
ensureVoluteHome();
|
|
3011
2806
|
const dest = mindDir(name);
|
|
3012
|
-
if (
|
|
2807
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3013
2808
|
try {
|
|
3014
2809
|
cpSync(extractedMindDir, dest, { recursive: true });
|
|
3015
2810
|
if (!manifest.includes.identity) {
|
|
3016
2811
|
generateIdentity(dest);
|
|
3017
2812
|
}
|
|
3018
2813
|
const state = stateDir(name);
|
|
3019
|
-
|
|
3020
|
-
const channelsJson =
|
|
3021
|
-
if (
|
|
3022
|
-
cpSync(channelsJson,
|
|
2814
|
+
mkdirSync6(state, { recursive: true });
|
|
2815
|
+
const channelsJson = resolve12(tempDir, "state/channels.json");
|
|
2816
|
+
if (existsSync10(channelsJson)) {
|
|
2817
|
+
cpSync(channelsJson, resolve12(state, "channels.json"));
|
|
3023
2818
|
}
|
|
3024
|
-
const envJson =
|
|
3025
|
-
if (
|
|
3026
|
-
cpSync(envJson,
|
|
2819
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2820
|
+
if (existsSync10(envJson)) {
|
|
2821
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3027
2822
|
}
|
|
3028
2823
|
const port = nextPort();
|
|
3029
2824
|
addMind(name, port, manifest.stage, manifest.template);
|
|
@@ -3032,36 +2827,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3032
2827
|
} catch (err) {
|
|
3033
2828
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3034
2829
|
}
|
|
3035
|
-
const homeDir =
|
|
2830
|
+
const homeDir = resolve12(dest, "home");
|
|
3036
2831
|
ensureVoluteGroup();
|
|
3037
2832
|
createMindUser(name, homeDir);
|
|
3038
2833
|
chownMindDir(dest, name);
|
|
3039
2834
|
await npmInstallAsMind(dest, name);
|
|
3040
2835
|
await importHistoryFromArchive(name, tempDir);
|
|
3041
2836
|
importSessionsFromArchive(dest, tempDir);
|
|
3042
|
-
if (!
|
|
2837
|
+
if (!existsSync10(resolve12(dest, ".git"))) {
|
|
3043
2838
|
try {
|
|
3044
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
2839
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3045
2840
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3046
2841
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3047
2842
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3048
2843
|
await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
|
|
3049
2844
|
} catch (err) {
|
|
3050
2845
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3051
|
-
|
|
2846
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3052
2847
|
}
|
|
3053
2848
|
}
|
|
3054
2849
|
chownMindDir(dest, name);
|
|
3055
|
-
|
|
2850
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3056
2851
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3057
2852
|
} catch (err) {
|
|
3058
|
-
if (
|
|
2853
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3059
2854
|
try {
|
|
3060
2855
|
removeMind(name);
|
|
3061
2856
|
} catch (cleanupErr) {
|
|
3062
2857
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3063
2858
|
}
|
|
3064
|
-
|
|
2859
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3065
2860
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3066
2861
|
}
|
|
3067
2862
|
}
|
|
@@ -3072,7 +2867,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3072
2867
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3073
2868
|
ensureVoluteHome();
|
|
3074
2869
|
const dest = mindDir(name);
|
|
3075
|
-
if (
|
|
2870
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3076
2871
|
const templatesRoot = findTemplatesRoot();
|
|
3077
2872
|
const { composedDir, manifest: templateManifest } = composeTemplate(
|
|
3078
2873
|
templatesRoot,
|
|
@@ -3081,40 +2876,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3081
2876
|
try {
|
|
3082
2877
|
copyTemplateToDir(composedDir, dest, name, templateManifest);
|
|
3083
2878
|
applyInitFiles(dest);
|
|
3084
|
-
const extractedHome =
|
|
3085
|
-
if (
|
|
3086
|
-
cpSync(extractedHome,
|
|
2879
|
+
const extractedHome = resolve12(extractedMindDir, "home");
|
|
2880
|
+
if (existsSync10(extractedHome)) {
|
|
2881
|
+
cpSync(extractedHome, resolve12(dest, "home"), { recursive: true });
|
|
3087
2882
|
}
|
|
3088
|
-
const extractedMindInternal =
|
|
3089
|
-
if (
|
|
3090
|
-
cpSync(extractedMindInternal,
|
|
2883
|
+
const extractedMindInternal = resolve12(extractedMindDir, ".mind");
|
|
2884
|
+
if (existsSync10(extractedMindInternal)) {
|
|
2885
|
+
cpSync(extractedMindInternal, resolve12(dest, ".mind"), { recursive: true });
|
|
3091
2886
|
}
|
|
3092
|
-
const identityDir =
|
|
2887
|
+
const identityDir = resolve12(dest, ".mind/identity");
|
|
3093
2888
|
let publicKeyPem;
|
|
3094
|
-
if (!manifest.includes.identity || !
|
|
2889
|
+
if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
|
|
3095
2890
|
({ publicKeyPem } = generateIdentity(dest));
|
|
3096
2891
|
} else {
|
|
3097
|
-
publicKeyPem =
|
|
2892
|
+
publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
|
|
3098
2893
|
}
|
|
3099
|
-
const promptsPath =
|
|
3100
|
-
if (!
|
|
2894
|
+
const promptsPath = resolve12(dest, "home/.config/prompts.json");
|
|
2895
|
+
if (!existsSync10(promptsPath)) {
|
|
3101
2896
|
const mindPrompts = await getMindPromptDefaults();
|
|
3102
|
-
|
|
2897
|
+
writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
|
|
3103
2898
|
`);
|
|
3104
2899
|
}
|
|
3105
2900
|
const state = stateDir(name);
|
|
3106
|
-
|
|
3107
|
-
const channelsJson =
|
|
3108
|
-
if (
|
|
3109
|
-
cpSync(channelsJson,
|
|
2901
|
+
mkdirSync6(state, { recursive: true });
|
|
2902
|
+
const channelsJson = resolve12(tempDir, "state/channels.json");
|
|
2903
|
+
if (existsSync10(channelsJson)) {
|
|
2904
|
+
cpSync(channelsJson, resolve12(state, "channels.json"));
|
|
3110
2905
|
}
|
|
3111
|
-
const envJson =
|
|
3112
|
-
if (
|
|
3113
|
-
cpSync(envJson,
|
|
2906
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2907
|
+
if (existsSync10(envJson)) {
|
|
2908
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3114
2909
|
}
|
|
3115
2910
|
const port = nextPort();
|
|
3116
2911
|
addMind(name, port, manifest.stage, manifest.template);
|
|
3117
|
-
const homeDir =
|
|
2912
|
+
const homeDir = resolve12(dest, "home");
|
|
3118
2913
|
ensureVoluteGroup();
|
|
3119
2914
|
createMindUser(name, homeDir);
|
|
3120
2915
|
chownMindDir(dest, name);
|
|
@@ -3127,7 +2922,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3127
2922
|
await initTemplateBranch(dest, composedDir, templateManifest, name, env);
|
|
3128
2923
|
} catch (err) {
|
|
3129
2924
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3130
|
-
|
|
2925
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3131
2926
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3132
2927
|
}
|
|
3133
2928
|
try {
|
|
@@ -3151,7 +2946,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3151
2946
|
publishPublicKey(name, publicKeyPem).catch(
|
|
3152
2947
|
(err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
|
|
3153
2948
|
);
|
|
3154
|
-
|
|
2949
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3155
2950
|
return c.json({
|
|
3156
2951
|
ok: true,
|
|
3157
2952
|
name,
|
|
@@ -3162,24 +2957,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3162
2957
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3163
2958
|
});
|
|
3164
2959
|
} catch (err) {
|
|
3165
|
-
if (
|
|
2960
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3166
2961
|
try {
|
|
3167
2962
|
removeMind(name);
|
|
3168
2963
|
} catch (cleanupErr) {
|
|
3169
2964
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3170
2965
|
}
|
|
3171
|
-
|
|
2966
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3172
2967
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3173
2968
|
} finally {
|
|
3174
|
-
|
|
2969
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3175
2970
|
}
|
|
3176
2971
|
}
|
|
3177
2972
|
async function importHistoryFromArchive(name, tempDir) {
|
|
3178
|
-
const historyJsonl =
|
|
3179
|
-
if (!
|
|
2973
|
+
const historyJsonl = resolve12(tempDir, "history.jsonl");
|
|
2974
|
+
if (!existsSync10(historyJsonl)) return;
|
|
3180
2975
|
try {
|
|
3181
2976
|
const db = await getDb();
|
|
3182
|
-
const lines =
|
|
2977
|
+
const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
|
|
3183
2978
|
let imported = 0;
|
|
3184
2979
|
let failed = 0;
|
|
3185
2980
|
for (const line of lines) {
|
|
@@ -3215,13 +3010,13 @@ async function importHistoryFromArchive(name, tempDir) {
|
|
|
3215
3010
|
}
|
|
3216
3011
|
}
|
|
3217
3012
|
function importSessionsFromArchive(dest, tempDir) {
|
|
3218
|
-
const sessionsDir =
|
|
3219
|
-
if (!
|
|
3013
|
+
const sessionsDir = resolve12(tempDir, "sessions");
|
|
3014
|
+
if (!existsSync10(sessionsDir)) return;
|
|
3220
3015
|
try {
|
|
3221
|
-
const destSessions =
|
|
3222
|
-
|
|
3016
|
+
const destSessions = resolve12(dest, ".mind/sessions");
|
|
3017
|
+
mkdirSync6(destSessions, { recursive: true });
|
|
3223
3018
|
for (const file of readdirSync4(sessionsDir)) {
|
|
3224
|
-
cpSync(
|
|
3019
|
+
cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
|
|
3225
3020
|
}
|
|
3226
3021
|
} catch (err) {
|
|
3227
3022
|
logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
|
|
@@ -3244,7 +3039,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3244
3039
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3245
3040
|
ensureVoluteHome();
|
|
3246
3041
|
const dest = mindDir(name);
|
|
3247
|
-
if (
|
|
3042
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3248
3043
|
const templatesRoot = findTemplatesRoot();
|
|
3249
3044
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3250
3045
|
try {
|
|
@@ -3254,19 +3049,19 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3254
3049
|
if (body.description) {
|
|
3255
3050
|
const seedConfig = readVoluteConfig(dest);
|
|
3256
3051
|
if (!seedConfig) throw new Error("Failed to read volute.json after identity generation");
|
|
3257
|
-
seedConfig.
|
|
3052
|
+
seedConfig.profile = { ...seedConfig.profile, description: body.description };
|
|
3258
3053
|
writeVoluteConfig(dest, seedConfig);
|
|
3259
3054
|
}
|
|
3260
3055
|
if (body.model) {
|
|
3261
|
-
const configPath2 =
|
|
3262
|
-
const existing =
|
|
3056
|
+
const configPath2 = resolve12(dest, "home/.config/config.json");
|
|
3057
|
+
const existing = existsSync10(configPath2) ? JSON.parse(readFileSync9(configPath2, "utf-8")) : {};
|
|
3263
3058
|
existing.model = body.model;
|
|
3264
|
-
|
|
3059
|
+
writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
|
|
3265
3060
|
`);
|
|
3266
3061
|
}
|
|
3267
3062
|
const mindPrompts = await getMindPromptDefaults();
|
|
3268
|
-
|
|
3269
|
-
|
|
3063
|
+
writeFileSync7(
|
|
3064
|
+
resolve12(dest, "home/.config/prompts.json"),
|
|
3270
3065
|
`${JSON.stringify(mindPrompts, null, 2)}
|
|
3271
3066
|
`
|
|
3272
3067
|
);
|
|
@@ -3277,7 +3072,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3277
3072
|
} catch (err) {
|
|
3278
3073
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3279
3074
|
}
|
|
3280
|
-
const homeDir =
|
|
3075
|
+
const homeDir = resolve12(dest, "home");
|
|
3281
3076
|
ensureVoluteGroup();
|
|
3282
3077
|
createMindUser(name, homeDir);
|
|
3283
3078
|
chownMindDir(dest, name);
|
|
@@ -3290,7 +3085,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3290
3085
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
3291
3086
|
} catch (err) {
|
|
3292
3087
|
logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
|
|
3293
|
-
|
|
3088
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3294
3089
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3295
3090
|
}
|
|
3296
3091
|
try {
|
|
@@ -3305,7 +3100,7 @@ The human who planted you described you as: "${body.description}"
|
|
|
3305
3100
|
` : "";
|
|
3306
3101
|
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
3307
3102
|
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
3308
|
-
|
|
3103
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
|
|
3309
3104
|
}
|
|
3310
3105
|
const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
|
|
3311
3106
|
const skillWarnings = [];
|
|
@@ -3320,11 +3115,11 @@ The human who planted you described you as: "${body.description}"
|
|
|
3320
3115
|
if (body.stage !== "seed") {
|
|
3321
3116
|
const customSoul = await getPromptIfCustom("default_soul");
|
|
3322
3117
|
if (customSoul) {
|
|
3323
|
-
|
|
3118
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3324
3119
|
}
|
|
3325
3120
|
const customMemory = await getPromptIfCustom("default_memory");
|
|
3326
3121
|
if (customMemory) {
|
|
3327
|
-
|
|
3122
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
|
|
3328
3123
|
}
|
|
3329
3124
|
}
|
|
3330
3125
|
publishPublicKey(name, publicKeyPem).catch(
|
|
@@ -3351,14 +3146,14 @@ The human who planted you described you as: "${body.description}"
|
|
|
3351
3146
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3352
3147
|
});
|
|
3353
3148
|
} catch (err) {
|
|
3354
|
-
if (
|
|
3149
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3355
3150
|
try {
|
|
3356
3151
|
removeMind(name);
|
|
3357
3152
|
} catch {
|
|
3358
3153
|
}
|
|
3359
3154
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
3360
3155
|
} finally {
|
|
3361
|
-
|
|
3156
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3362
3157
|
}
|
|
3363
3158
|
}).post("/import", requireAdmin, async (c) => {
|
|
3364
3159
|
let body;
|
|
@@ -3371,13 +3166,13 @@ The human who planted you described you as: "${body.description}"
|
|
|
3371
3166
|
return importFromArchive(c, body.archivePath, body.name, body.manifest);
|
|
3372
3167
|
}
|
|
3373
3168
|
const wsDir = body.workspacePath;
|
|
3374
|
-
if (!wsDir || !
|
|
3169
|
+
if (!wsDir || !existsSync10(resolve12(wsDir, "SOUL.md")) || !existsSync10(resolve12(wsDir, "IDENTITY.md"))) {
|
|
3375
3170
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
3376
3171
|
}
|
|
3377
|
-
const soul =
|
|
3378
|
-
const identity =
|
|
3379
|
-
const userPath =
|
|
3380
|
-
const user =
|
|
3172
|
+
const soul = readFileSync9(resolve12(wsDir, "SOUL.md"), "utf-8");
|
|
3173
|
+
const identity = readFileSync9(resolve12(wsDir, "IDENTITY.md"), "utf-8");
|
|
3174
|
+
const userPath = resolve12(wsDir, "USER.md");
|
|
3175
|
+
const user = existsSync10(userPath) ? readFileSync9(userPath, "utf-8") : "";
|
|
3381
3176
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
3382
3177
|
const template = body.template ?? "claude";
|
|
3383
3178
|
const nameErr = validateMindName(name);
|
|
@@ -3397,33 +3192,33 @@ ${user.trimEnd()}
|
|
|
3397
3192
|
` : "";
|
|
3398
3193
|
ensureVoluteHome();
|
|
3399
3194
|
const dest = mindDir(name);
|
|
3400
|
-
if (
|
|
3195
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3401
3196
|
const templatesRoot = findTemplatesRoot();
|
|
3402
3197
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3403
3198
|
try {
|
|
3404
3199
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
3405
3200
|
applyInitFiles(dest);
|
|
3406
3201
|
const { publicKeyPem: importPublicKey } = generateIdentity(dest);
|
|
3407
|
-
|
|
3408
|
-
const wsMemoryPath =
|
|
3409
|
-
const hasMemory =
|
|
3202
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
|
|
3203
|
+
const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
|
|
3204
|
+
const hasMemory = existsSync10(wsMemoryPath);
|
|
3410
3205
|
if (hasMemory) {
|
|
3411
|
-
const existingMemory =
|
|
3412
|
-
|
|
3413
|
-
|
|
3206
|
+
const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
|
|
3207
|
+
writeFileSync7(
|
|
3208
|
+
resolve12(dest, "home/MEMORY.md"),
|
|
3414
3209
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
3415
3210
|
);
|
|
3416
3211
|
} else if (user) {
|
|
3417
|
-
|
|
3212
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
3418
3213
|
`);
|
|
3419
3214
|
}
|
|
3420
|
-
const wsMemoryDir =
|
|
3215
|
+
const wsMemoryDir = resolve12(wsDir, "memory");
|
|
3421
3216
|
let dailyLogCount = 0;
|
|
3422
|
-
if (
|
|
3423
|
-
const destMemoryDir =
|
|
3217
|
+
if (existsSync10(wsMemoryDir)) {
|
|
3218
|
+
const destMemoryDir = resolve12(dest, "home/memory");
|
|
3424
3219
|
const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
3425
3220
|
for (const file of files) {
|
|
3426
|
-
cpSync(
|
|
3221
|
+
cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
|
|
3427
3222
|
}
|
|
3428
3223
|
dailyLogCount = files.length;
|
|
3429
3224
|
}
|
|
@@ -3434,7 +3229,7 @@ ${user.trimEnd()}
|
|
|
3434
3229
|
} catch (err) {
|
|
3435
3230
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3436
3231
|
}
|
|
3437
|
-
const homeDir =
|
|
3232
|
+
const homeDir = resolve12(dest, "home");
|
|
3438
3233
|
ensureVoluteGroup();
|
|
3439
3234
|
createMindUser(name, homeDir);
|
|
3440
3235
|
chownMindDir(dest, name);
|
|
@@ -3442,20 +3237,20 @@ ${user.trimEnd()}
|
|
|
3442
3237
|
if (!hasMemory && dailyLogCount > 0) {
|
|
3443
3238
|
await consolidateMemory(dest);
|
|
3444
3239
|
}
|
|
3445
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3240
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3446
3241
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3447
3242
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3448
3243
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3449
3244
|
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
|
|
3450
|
-
const sessionFile = body.sessionPath ?
|
|
3451
|
-
if (sessionFile &&
|
|
3245
|
+
const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
|
|
3246
|
+
if (sessionFile && existsSync10(sessionFile)) {
|
|
3452
3247
|
if (template === "pi") {
|
|
3453
3248
|
importPiSession(sessionFile, dest);
|
|
3454
3249
|
} else if (template === "claude") {
|
|
3455
3250
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
3456
|
-
const mindRuntimeDir =
|
|
3457
|
-
|
|
3458
|
-
|
|
3251
|
+
const mindRuntimeDir = resolve12(dest, ".mind");
|
|
3252
|
+
mkdirSync6(mindRuntimeDir, { recursive: true });
|
|
3253
|
+
writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
|
|
3459
3254
|
}
|
|
3460
3255
|
}
|
|
3461
3256
|
importOpenClawConnectors(name, dest);
|
|
@@ -3470,14 +3265,14 @@ ${user.trimEnd()}
|
|
|
3470
3265
|
);
|
|
3471
3266
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3472
3267
|
} catch (err) {
|
|
3473
|
-
if (
|
|
3268
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3474
3269
|
try {
|
|
3475
3270
|
removeMind(name);
|
|
3476
3271
|
} catch {
|
|
3477
3272
|
}
|
|
3478
3273
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3479
3274
|
} finally {
|
|
3480
|
-
|
|
3275
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3481
3276
|
}
|
|
3482
3277
|
}).get("/", async (c) => {
|
|
3483
3278
|
const entries = readRegistry();
|
|
@@ -3486,7 +3281,7 @@ ${user.trimEnd()}
|
|
|
3486
3281
|
const db = await getDb();
|
|
3487
3282
|
const lastActiveRows = await db.select({
|
|
3488
3283
|
mind: mindHistory.mind,
|
|
3489
|
-
lastActiveAt:
|
|
3284
|
+
lastActiveAt: sql`MAX(${mindHistory.created_at})`
|
|
3490
3285
|
}).from(mindHistory).groupBy(mindHistory.mind);
|
|
3491
3286
|
lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
|
|
3492
3287
|
} catch {
|
|
@@ -3494,7 +3289,7 @@ ${user.trimEnd()}
|
|
|
3494
3289
|
const minds = await Promise.all(
|
|
3495
3290
|
entries.map(async (entry) => {
|
|
3496
3291
|
const mindStatus = await getMindStatus(entry.name, entry.port);
|
|
3497
|
-
const hasPages =
|
|
3292
|
+
const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "public", "pages"));
|
|
3498
3293
|
return {
|
|
3499
3294
|
...entry,
|
|
3500
3295
|
...mindStatus,
|
|
@@ -3512,7 +3307,7 @@ ${user.trimEnd()}
|
|
|
3512
3307
|
const name = c.req.param("name");
|
|
3513
3308
|
const entry = findMind(name);
|
|
3514
3309
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3515
|
-
if (!
|
|
3310
|
+
if (!existsSync10(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
|
|
3516
3311
|
const mindStatus = await getMindStatus(name, entry.port);
|
|
3517
3312
|
const variants = readVariants(name);
|
|
3518
3313
|
const manager = getMindManager();
|
|
@@ -3527,7 +3322,7 @@ ${user.trimEnd()}
|
|
|
3527
3322
|
return { name: v.name, port: v.port, status: variantStatus };
|
|
3528
3323
|
})
|
|
3529
3324
|
);
|
|
3530
|
-
const hasPages =
|
|
3325
|
+
const hasPages = existsSync10(resolve12(mindDir(name), "home", "public", "pages"));
|
|
3531
3326
|
return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
|
|
3532
3327
|
}).post("/:name/start", requireAdmin, async (c) => {
|
|
3533
3328
|
const name = c.req.param("name");
|
|
@@ -3541,7 +3336,7 @@ ${user.trimEnd()}
|
|
|
3541
3336
|
targetPort = variant.port;
|
|
3542
3337
|
} else {
|
|
3543
3338
|
const dir = mindDir(baseName);
|
|
3544
|
-
if (!
|
|
3339
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3545
3340
|
}
|
|
3546
3341
|
if (getMindManager().isRunning(name)) {
|
|
3547
3342
|
return c.json({ error: "Mind already running" }, 409);
|
|
@@ -3564,7 +3359,7 @@ ${user.trimEnd()}
|
|
|
3564
3359
|
targetPort = variant.port;
|
|
3565
3360
|
} else {
|
|
3566
3361
|
const dir = mindDir(baseName);
|
|
3567
|
-
if (!
|
|
3362
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3568
3363
|
}
|
|
3569
3364
|
let context;
|
|
3570
3365
|
const contentType = c.req.header("content-type");
|
|
@@ -3591,7 +3386,7 @@ ${user.trimEnd()}
|
|
|
3591
3386
|
const variant = findVariant(baseName, mergeVariantName);
|
|
3592
3387
|
if (variant) {
|
|
3593
3388
|
const projectRoot = mindDir(baseName);
|
|
3594
|
-
if (
|
|
3389
|
+
if (existsSync10(variant.path)) {
|
|
3595
3390
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3596
3391
|
if (status) {
|
|
3597
3392
|
try {
|
|
@@ -3627,13 +3422,13 @@ ${user.trimEnd()}
|
|
|
3627
3422
|
}
|
|
3628
3423
|
}
|
|
3629
3424
|
}
|
|
3630
|
-
if (context) {
|
|
3425
|
+
if (context && context.type !== "reload") {
|
|
3631
3426
|
manager.setPendingContext(name, context);
|
|
3632
3427
|
}
|
|
3633
3428
|
if (context?.type === "sprouted" && !variantName) {
|
|
3634
3429
|
try {
|
|
3635
3430
|
const db = await getDb();
|
|
3636
|
-
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(
|
|
3431
|
+
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq2(conversations.mind_name, baseName)).all();
|
|
3637
3432
|
for (const conv of activeConvs) {
|
|
3638
3433
|
await addMessage(conv.id, "assistant", "system", [
|
|
3639
3434
|
{ type: "text", text: "[seed has sprouted]" }
|
|
@@ -3671,7 +3466,7 @@ ${user.trimEnd()}
|
|
|
3671
3466
|
const name = c.req.param("name");
|
|
3672
3467
|
const entry = findMind(name);
|
|
3673
3468
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3674
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3469
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
3675
3470
|
const sm = getSleepManagerIfReady();
|
|
3676
3471
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3677
3472
|
return c.json(sm.getState(name));
|
|
@@ -3679,7 +3474,7 @@ ${user.trimEnd()}
|
|
|
3679
3474
|
const name = c.req.param("name");
|
|
3680
3475
|
const entry = findMind(name);
|
|
3681
3476
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3682
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3477
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
3683
3478
|
const sm = getSleepManagerIfReady();
|
|
3684
3479
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3685
3480
|
if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
|
|
@@ -3699,7 +3494,7 @@ ${user.trimEnd()}
|
|
|
3699
3494
|
const name = c.req.param("name");
|
|
3700
3495
|
const entry = findMind(name);
|
|
3701
3496
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3702
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3497
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
3703
3498
|
const sm = getSleepManagerIfReady();
|
|
3704
3499
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3705
3500
|
if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
|
|
@@ -3709,7 +3504,7 @@ ${user.trimEnd()}
|
|
|
3709
3504
|
const name = c.req.param("name");
|
|
3710
3505
|
const entry = findMind(name);
|
|
3711
3506
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3712
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3507
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
3713
3508
|
const sm = getSleepManagerIfReady();
|
|
3714
3509
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3715
3510
|
const flushed = await sm.flushQueuedMessages(name);
|
|
@@ -3742,11 +3537,11 @@ ${user.trimEnd()}
|
|
|
3742
3537
|
removeMind(name);
|
|
3743
3538
|
await deleteMindUser2(name);
|
|
3744
3539
|
const state = stateDir(name);
|
|
3745
|
-
if (
|
|
3746
|
-
|
|
3540
|
+
if (existsSync10(state)) {
|
|
3541
|
+
rmSync4(state, { recursive: true, force: true });
|
|
3747
3542
|
}
|
|
3748
|
-
if (force &&
|
|
3749
|
-
|
|
3543
|
+
if (force && existsSync10(dir)) {
|
|
3544
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
3750
3545
|
deleteMindUser(name);
|
|
3751
3546
|
}
|
|
3752
3547
|
fireWebhook({
|
|
@@ -3760,7 +3555,7 @@ ${user.trimEnd()}
|
|
|
3760
3555
|
const entry = findMind(mindName);
|
|
3761
3556
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3762
3557
|
const dir = mindDir(mindName);
|
|
3763
|
-
if (!
|
|
3558
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3764
3559
|
let body = {};
|
|
3765
3560
|
try {
|
|
3766
3561
|
body = await c.req.json();
|
|
@@ -3769,15 +3564,15 @@ ${user.trimEnd()}
|
|
|
3769
3564
|
const template = body.template ?? entry.template ?? "claude";
|
|
3770
3565
|
const UPGRADE_VARIANT = "upgrade";
|
|
3771
3566
|
if (body.abort) {
|
|
3772
|
-
const worktreeDir2 =
|
|
3773
|
-
if (!
|
|
3567
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3568
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3774
3569
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3775
3570
|
}
|
|
3776
3571
|
try {
|
|
3777
3572
|
try {
|
|
3778
|
-
const gitDirContent =
|
|
3573
|
+
const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
|
|
3779
3574
|
const gitDir = gitDirContent.replace("gitdir: ", "");
|
|
3780
|
-
if (
|
|
3575
|
+
if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
|
|
3781
3576
|
await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
|
|
3782
3577
|
}
|
|
3783
3578
|
} catch {
|
|
@@ -3792,8 +3587,8 @@ ${user.trimEnd()}
|
|
|
3792
3587
|
}
|
|
3793
3588
|
}
|
|
3794
3589
|
if (body.continue) {
|
|
3795
|
-
const worktreeDir2 =
|
|
3796
|
-
if (!
|
|
3590
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3591
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3797
3592
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3798
3593
|
}
|
|
3799
3594
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
|
|
@@ -3837,23 +3632,23 @@ ${user.trimEnd()}
|
|
|
3837
3632
|
);
|
|
3838
3633
|
}
|
|
3839
3634
|
}
|
|
3840
|
-
const worktreeDir =
|
|
3841
|
-
if (
|
|
3635
|
+
const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3636
|
+
if (existsSync10(worktreeDir)) {
|
|
3842
3637
|
return c.json(
|
|
3843
3638
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
3844
3639
|
409
|
|
3845
3640
|
);
|
|
3846
3641
|
}
|
|
3847
|
-
if (!
|
|
3642
|
+
if (!existsSync10(resolve12(dir, ".git"))) {
|
|
3848
3643
|
try {
|
|
3849
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3644
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
|
|
3850
3645
|
await gitExec(["init"], { cwd: dir, mindName, env });
|
|
3851
3646
|
await configureGitIdentity(mindName, { cwd: dir, mindName, env });
|
|
3852
3647
|
await gitExec(["add", "-A"], { cwd: dir, mindName, env });
|
|
3853
3648
|
await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
|
|
3854
3649
|
chownMindDir(dir, mindName);
|
|
3855
3650
|
} catch (err) {
|
|
3856
|
-
|
|
3651
|
+
rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
|
|
3857
3652
|
return c.json(
|
|
3858
3653
|
{
|
|
3859
3654
|
error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3867,7 +3662,7 @@ ${user.trimEnd()}
|
|
|
3867
3662
|
await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
|
|
3868
3663
|
} catch {
|
|
3869
3664
|
}
|
|
3870
|
-
if (!
|
|
3665
|
+
if (!existsSync10(resolve12(dir, "home", "shared"))) {
|
|
3871
3666
|
try {
|
|
3872
3667
|
await addSharedWorktree(mindName, dir);
|
|
3873
3668
|
} catch (err) {
|
|
@@ -3878,9 +3673,9 @@ ${user.trimEnd()}
|
|
|
3878
3673
|
}
|
|
3879
3674
|
}
|
|
3880
3675
|
await updateTemplateBranch(dir, template, mindName);
|
|
3881
|
-
const parentDir =
|
|
3882
|
-
if (!
|
|
3883
|
-
|
|
3676
|
+
const parentDir = resolve12(dir, ".variants");
|
|
3677
|
+
if (!existsSync10(parentDir)) {
|
|
3678
|
+
mkdirSync6(parentDir, { recursive: true });
|
|
3884
3679
|
}
|
|
3885
3680
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
3886
3681
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -3927,7 +3722,7 @@ ${user.trimEnd()}
|
|
|
3927
3722
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
3928
3723
|
}
|
|
3929
3724
|
try {
|
|
3930
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3725
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
3931
3726
|
const sm = getSleepManagerIfReady();
|
|
3932
3727
|
if (sm?.isSleeping(baseName)) {
|
|
3933
3728
|
const body2 = await c.req.text();
|
|
@@ -4025,7 +3820,7 @@ ${user.trimEnd()}
|
|
|
4025
3820
|
if (seedEntry?.stage === "seed") {
|
|
4026
3821
|
try {
|
|
4027
3822
|
const db = await getDb();
|
|
4028
|
-
const countResult = await db.select({ count:
|
|
3823
|
+
const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
|
|
4029
3824
|
const msgCount = countResult[0]?.count ?? 0;
|
|
4030
3825
|
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
4031
3826
|
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.]";
|
|
@@ -4070,13 +3865,13 @@ ${user.trimEnd()}
|
|
|
4070
3865
|
const entry = findMind(name);
|
|
4071
3866
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4072
3867
|
const dir = mindDir(name);
|
|
4073
|
-
if (!
|
|
3868
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4074
3869
|
let config = readVoluteConfig(dir);
|
|
4075
3870
|
if (!config && entry.template === "pi") {
|
|
4076
|
-
const piConfigPath =
|
|
4077
|
-
if (
|
|
3871
|
+
const piConfigPath = resolve12(dir, "home/.config/config.json");
|
|
3872
|
+
if (existsSync10(piConfigPath)) {
|
|
4078
3873
|
try {
|
|
4079
|
-
config = JSON.parse(
|
|
3874
|
+
config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
|
|
4080
3875
|
} catch {
|
|
4081
3876
|
}
|
|
4082
3877
|
}
|
|
@@ -4113,7 +3908,7 @@ ${user.trimEnd()}
|
|
|
4113
3908
|
const entry = findMind(name);
|
|
4114
3909
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4115
3910
|
const dir = mindDir(name);
|
|
4116
|
-
if (!
|
|
3911
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4117
3912
|
const body = c.req.valid("json");
|
|
4118
3913
|
const existing = readVoluteConfig(dir) ?? {};
|
|
4119
3914
|
if (body.model !== void 0) existing.model = body.model;
|
|
@@ -4294,22 +4089,22 @@ ${user.trimEnd()}
|
|
|
4294
4089
|
const db = await getDb();
|
|
4295
4090
|
const rows = await db.select({
|
|
4296
4091
|
session: mindHistory.session,
|
|
4297
|
-
started_at:
|
|
4298
|
-
event_count:
|
|
4299
|
-
message_count:
|
|
4300
|
-
tool_count:
|
|
4301
|
-
}).from(mindHistory).where(
|
|
4092
|
+
started_at: sql`MIN(${mindHistory.created_at})`,
|
|
4093
|
+
event_count: sql`COUNT(*)`,
|
|
4094
|
+
message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
|
|
4095
|
+
tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
|
|
4096
|
+
}).from(mindHistory).where(and(eq2(mindHistory.mind, name), sql`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql`MIN(${mindHistory.created_at}) DESC`);
|
|
4302
4097
|
return c.json(rows);
|
|
4303
4098
|
}).get("/:name/history/channels", async (c) => {
|
|
4304
4099
|
const name = c.req.param("name");
|
|
4305
4100
|
const db = await getDb();
|
|
4306
|
-
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(
|
|
4101
|
+
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq2(mindHistory.mind, name));
|
|
4307
4102
|
return c.json(rows.map((r) => r.channel));
|
|
4308
4103
|
}).get("/:name/history/export", async (c) => {
|
|
4309
4104
|
const name = c.req.param("name");
|
|
4310
4105
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
4311
4106
|
const db = await getDb();
|
|
4312
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4107
|
+
const rows = await db.select().from(mindHistory).where(eq2(mindHistory.mind, name));
|
|
4313
4108
|
return c.json(rows);
|
|
4314
4109
|
}).get("/:name/history", async (c) => {
|
|
4315
4110
|
const name = c.req.param("name");
|
|
@@ -4319,24 +4114,24 @@ ${user.trimEnd()}
|
|
|
4319
4114
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
4320
4115
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
4321
4116
|
const db = await getDb();
|
|
4322
|
-
const conditions = [
|
|
4117
|
+
const conditions = [eq2(mindHistory.mind, name)];
|
|
4323
4118
|
if (channel) {
|
|
4324
|
-
conditions.push(
|
|
4119
|
+
conditions.push(eq2(mindHistory.channel, channel));
|
|
4325
4120
|
}
|
|
4326
4121
|
if (session) {
|
|
4327
|
-
conditions.push(
|
|
4122
|
+
conditions.push(eq2(mindHistory.session, session));
|
|
4328
4123
|
}
|
|
4329
4124
|
if (!full) {
|
|
4330
|
-
conditions.push(
|
|
4125
|
+
conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
4331
4126
|
}
|
|
4332
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4127
|
+
const rows = await db.select().from(mindHistory).where(and(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
|
|
4333
4128
|
return c.json(rows);
|
|
4334
4129
|
});
|
|
4335
4130
|
var minds_default = app11;
|
|
4336
4131
|
|
|
4337
4132
|
// src/web/api/pages.ts
|
|
4338
4133
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4339
|
-
import { extname as
|
|
4134
|
+
import { extname as extname3, resolve as resolve13 } from "path";
|
|
4340
4135
|
import { Hono as Hono12 } from "hono";
|
|
4341
4136
|
var MIME_TYPES = {
|
|
4342
4137
|
".html": "text/html",
|
|
@@ -4358,17 +4153,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4358
4153
|
const name = c.req.param("name");
|
|
4359
4154
|
let pagesRoot;
|
|
4360
4155
|
if (name === "_system") {
|
|
4361
|
-
pagesRoot =
|
|
4156
|
+
pagesRoot = resolve13(voluteHome(), "shared", "pages");
|
|
4362
4157
|
} else {
|
|
4363
4158
|
if (!findMind(name)) return c.text("Not found", 404);
|
|
4364
|
-
pagesRoot =
|
|
4159
|
+
pagesRoot = resolve13(mindDir(name), "home", "public", "pages");
|
|
4365
4160
|
}
|
|
4366
4161
|
const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
|
|
4367
|
-
const requestedPath =
|
|
4162
|
+
const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
|
|
4368
4163
|
if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
|
|
4369
4164
|
let fileStat = await stat2(requestedPath).catch(() => null);
|
|
4370
4165
|
if (fileStat?.isDirectory()) {
|
|
4371
|
-
const indexPath =
|
|
4166
|
+
const indexPath = resolve13(requestedPath, "index.html");
|
|
4372
4167
|
fileStat = await stat2(indexPath).catch(() => null);
|
|
4373
4168
|
if (fileStat?.isFile()) {
|
|
4374
4169
|
const body = await readFile2(indexPath);
|
|
@@ -4377,7 +4172,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4377
4172
|
return c.text("Not found", 404);
|
|
4378
4173
|
}
|
|
4379
4174
|
if (fileStat?.isFile()) {
|
|
4380
|
-
const ext =
|
|
4175
|
+
const ext = extname3(requestedPath);
|
|
4381
4176
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
4382
4177
|
const body = await readFile2(requestedPath);
|
|
4383
4178
|
return c.body(body, 200, { "Content-Type": mime });
|
|
@@ -4388,7 +4183,7 @@ var pages_default = app12;
|
|
|
4388
4183
|
|
|
4389
4184
|
// src/web/api/prompts.ts
|
|
4390
4185
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
4391
|
-
import { eq as
|
|
4186
|
+
import { eq as eq3, sql as sql2 } from "drizzle-orm";
|
|
4392
4187
|
import { Hono as Hono13 } from "hono";
|
|
4393
4188
|
import { z as z4 } from "zod";
|
|
4394
4189
|
var app13 = new Hono13().get("/", async (c) => {
|
|
@@ -4421,9 +4216,9 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4421
4216
|
}
|
|
4422
4217
|
const { content } = c.req.valid("json");
|
|
4423
4218
|
const db = await getDb();
|
|
4424
|
-
await db.insert(systemPrompts).values({ key, content, updated_at:
|
|
4219
|
+
await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
|
|
4425
4220
|
target: systemPrompts.key,
|
|
4426
|
-
set: { content, updated_at:
|
|
4221
|
+
set: { content, updated_at: sql2`(datetime('now'))` }
|
|
4427
4222
|
});
|
|
4428
4223
|
return c.json({ ok: true });
|
|
4429
4224
|
}).delete("/:key", requireAdmin, async (c) => {
|
|
@@ -4432,14 +4227,103 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4432
4227
|
return c.json({ error: "Unknown prompt key" }, 404);
|
|
4433
4228
|
}
|
|
4434
4229
|
const db = await getDb();
|
|
4435
|
-
await db.delete(systemPrompts).where(
|
|
4230
|
+
await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
|
|
4436
4231
|
return c.json({ ok: true });
|
|
4437
4232
|
});
|
|
4438
4233
|
var prompts_default = app13;
|
|
4439
4234
|
|
|
4235
|
+
// src/web/api/public-files.ts
|
|
4236
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
4237
|
+
import { extname as extname4, resolve as resolve14 } from "path";
|
|
4238
|
+
import { Hono as Hono14 } from "hono";
|
|
4239
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
4240
|
+
function resolvePublicRoot(name) {
|
|
4241
|
+
if (name === "_system") return resolve14(voluteHome(), "shared");
|
|
4242
|
+
if (!findMind(name)) return null;
|
|
4243
|
+
return resolve14(mindDir(name), "home", "public");
|
|
4244
|
+
}
|
|
4245
|
+
function hasDotSegment(relativePath) {
|
|
4246
|
+
return relativePath.split("/").some((seg) => seg.startsWith("."));
|
|
4247
|
+
}
|
|
4248
|
+
var MIME_TYPES2 = {
|
|
4249
|
+
".html": "text/html",
|
|
4250
|
+
".js": "application/javascript",
|
|
4251
|
+
".css": "text/css",
|
|
4252
|
+
".json": "application/json",
|
|
4253
|
+
".svg": "image/svg+xml",
|
|
4254
|
+
".png": "image/png",
|
|
4255
|
+
".jpg": "image/jpeg",
|
|
4256
|
+
".jpeg": "image/jpeg",
|
|
4257
|
+
".gif": "image/gif",
|
|
4258
|
+
".ico": "image/x-icon",
|
|
4259
|
+
".woff": "font/woff",
|
|
4260
|
+
".woff2": "font/woff2",
|
|
4261
|
+
".txt": "text/plain",
|
|
4262
|
+
".xml": "application/xml",
|
|
4263
|
+
".md": "text/markdown",
|
|
4264
|
+
".webp": "image/webp"
|
|
4265
|
+
};
|
|
4266
|
+
async function listDir(dirPath) {
|
|
4267
|
+
let entries;
|
|
4268
|
+
try {
|
|
4269
|
+
entries = await readdir2(dirPath, { withFileTypes: true });
|
|
4270
|
+
} catch (err) {
|
|
4271
|
+
if (err?.code === "ENOENT") return [];
|
|
4272
|
+
throw err;
|
|
4273
|
+
}
|
|
4274
|
+
return entries.filter((e) => !e.name.startsWith(".")).map((e) => ({
|
|
4275
|
+
name: e.name,
|
|
4276
|
+
type: e.isDirectory() ? "directory" : "file"
|
|
4277
|
+
}));
|
|
4278
|
+
}
|
|
4279
|
+
var app14 = new Hono14().get("/:name/", async (c) => {
|
|
4280
|
+
const name = c.req.param("name");
|
|
4281
|
+
const publicRoot = resolvePublicRoot(name);
|
|
4282
|
+
if (!publicRoot) return c.json({ error: "Not found" }, 404);
|
|
4283
|
+
return c.json(await listDir(publicRoot));
|
|
4284
|
+
}).get("/:name/*", async (c) => {
|
|
4285
|
+
const name = c.req.param("name");
|
|
4286
|
+
const publicRoot = resolvePublicRoot(name);
|
|
4287
|
+
if (!publicRoot) return c.text("Not found", 404);
|
|
4288
|
+
const wildcard = c.req.path.replace(`/public/${name}`, "") || "/";
|
|
4289
|
+
const relativePath = wildcard.slice(1);
|
|
4290
|
+
const requestedPath = resolve14(publicRoot, relativePath);
|
|
4291
|
+
if (!requestedPath.startsWith(publicRoot)) return c.text("Forbidden", 403);
|
|
4292
|
+
if (hasDotSegment(relativePath)) return c.text("Forbidden", 403);
|
|
4293
|
+
let fileStat;
|
|
4294
|
+
try {
|
|
4295
|
+
fileStat = await stat3(requestedPath);
|
|
4296
|
+
} catch (err) {
|
|
4297
|
+
if (err?.code === "ENOENT") return c.text("Not found", 404);
|
|
4298
|
+
if (err?.code === "EACCES") return c.text("Forbidden", 403);
|
|
4299
|
+
return c.text("Internal server error", 500);
|
|
4300
|
+
}
|
|
4301
|
+
if (fileStat.isDirectory()) {
|
|
4302
|
+
if (wildcard.endsWith("/")) {
|
|
4303
|
+
return c.json(await listDir(requestedPath));
|
|
4304
|
+
}
|
|
4305
|
+
return c.text("Not found", 404);
|
|
4306
|
+
}
|
|
4307
|
+
if (fileStat.isFile()) {
|
|
4308
|
+
if (fileStat.size > MAX_FILE_SIZE) return c.text("File too large", 413);
|
|
4309
|
+
const ext = extname4(requestedPath);
|
|
4310
|
+
const mime = MIME_TYPES2[ext] || "application/octet-stream";
|
|
4311
|
+
try {
|
|
4312
|
+
const body = await readFile3(requestedPath);
|
|
4313
|
+
return c.body(body, 200, { "Content-Type": mime });
|
|
4314
|
+
} catch (err) {
|
|
4315
|
+
if (err?.code === "ENOENT") return c.text("Not found", 404);
|
|
4316
|
+
if (err?.code === "EACCES") return c.text("Forbidden", 403);
|
|
4317
|
+
return c.text("Failed to read file", 500);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return c.text("Not found", 404);
|
|
4321
|
+
});
|
|
4322
|
+
var public_files_default = app14;
|
|
4323
|
+
|
|
4440
4324
|
// src/web/api/schedules.ts
|
|
4441
4325
|
import { CronExpressionParser } from "cron-parser";
|
|
4442
|
-
import { Hono as
|
|
4326
|
+
import { Hono as Hono15 } from "hono";
|
|
4443
4327
|
var slog = logger_default.child("schedules");
|
|
4444
4328
|
function readSchedules(name) {
|
|
4445
4329
|
return readVoluteConfig(mindDir(name))?.schedules ?? [];
|
|
@@ -4456,7 +4340,7 @@ function writeSchedules(name, schedules) {
|
|
|
4456
4340
|
data: { schedules }
|
|
4457
4341
|
});
|
|
4458
4342
|
}
|
|
4459
|
-
var
|
|
4343
|
+
var app15 = new Hono15().get("/:name/schedules", (c) => {
|
|
4460
4344
|
const name = c.req.param("name");
|
|
4461
4345
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
4462
4346
|
return c.json(readSchedules(name));
|
|
@@ -4559,11 +4443,11 @@ var app14 = new Hono14().get("/:name/schedules", (c) => {
|
|
|
4559
4443
|
return c.json({ error: "Failed to reach mind" }, 502);
|
|
4560
4444
|
}
|
|
4561
4445
|
});
|
|
4562
|
-
var schedules_default =
|
|
4446
|
+
var schedules_default = app15;
|
|
4563
4447
|
|
|
4564
4448
|
// src/web/api/shared.ts
|
|
4565
|
-
import { Hono as
|
|
4566
|
-
var
|
|
4449
|
+
import { Hono as Hono16 } from "hono";
|
|
4450
|
+
var app16 = new Hono16().post("/:name/shared/merge", requireAdmin, async (c) => {
|
|
4567
4451
|
const name = c.req.param("name");
|
|
4568
4452
|
const entry = findMind(name);
|
|
4569
4453
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -4612,15 +4496,15 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
|
|
|
4612
4496
|
return c.json({ error: err instanceof Error ? err.message : "Failed to get status" }, 500);
|
|
4613
4497
|
}
|
|
4614
4498
|
});
|
|
4615
|
-
var shared_default =
|
|
4499
|
+
var shared_default = app16;
|
|
4616
4500
|
|
|
4617
4501
|
// src/web/api/skills.ts
|
|
4618
|
-
import { existsSync as
|
|
4502
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
|
|
4619
4503
|
import { tmpdir } from "os";
|
|
4620
|
-
import { join as join2, resolve as
|
|
4504
|
+
import { join as join2, resolve as resolve15 } from "path";
|
|
4621
4505
|
import AdmZip from "adm-zip";
|
|
4622
|
-
import { Hono as
|
|
4623
|
-
var
|
|
4506
|
+
import { Hono as Hono17 } from "hono";
|
|
4507
|
+
var app17 = new Hono17().get("/", async (c) => {
|
|
4624
4508
|
const skills = await listSharedSkills();
|
|
4625
4509
|
return c.json(skills);
|
|
4626
4510
|
}).get("/:id", async (c) => {
|
|
@@ -4644,19 +4528,19 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4644
4528
|
try {
|
|
4645
4529
|
const zip = new AdmZip(buffer2);
|
|
4646
4530
|
for (const entry of zip.getEntries()) {
|
|
4647
|
-
const target =
|
|
4531
|
+
const target = resolve15(tmpDir, entry.entryName);
|
|
4648
4532
|
if (!target.startsWith(tmpDir)) {
|
|
4649
4533
|
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
4650
4534
|
}
|
|
4651
4535
|
}
|
|
4652
4536
|
zip.extractAllTo(tmpDir, true);
|
|
4653
4537
|
let skillDir = null;
|
|
4654
|
-
if (
|
|
4538
|
+
if (existsSync11(join2(tmpDir, "SKILL.md"))) {
|
|
4655
4539
|
skillDir = tmpDir;
|
|
4656
4540
|
} else {
|
|
4657
4541
|
const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4658
4542
|
for (const entry of entries) {
|
|
4659
|
-
if (
|
|
4543
|
+
if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
|
|
4660
4544
|
skillDir = join2(tmpDir, entry.name);
|
|
4661
4545
|
break;
|
|
4662
4546
|
}
|
|
@@ -4673,7 +4557,7 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4673
4557
|
}
|
|
4674
4558
|
throw e;
|
|
4675
4559
|
} finally {
|
|
4676
|
-
|
|
4560
|
+
rmSync5(tmpDir, { recursive: true, force: true });
|
|
4677
4561
|
}
|
|
4678
4562
|
}).delete("/:id", requireAdmin, async (c) => {
|
|
4679
4563
|
const id = c.req.param("id");
|
|
@@ -4685,12 +4569,15 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4685
4569
|
}
|
|
4686
4570
|
return c.json({ ok: true });
|
|
4687
4571
|
});
|
|
4688
|
-
var skills_default =
|
|
4572
|
+
var skills_default = app17;
|
|
4689
4573
|
|
|
4690
4574
|
// src/web/api/system.ts
|
|
4691
|
-
import {
|
|
4575
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
4576
|
+
import { Hono as Hono18 } from "hono";
|
|
4692
4577
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
4693
|
-
|
|
4578
|
+
import { z as z5 } from "zod";
|
|
4579
|
+
var DEFAULT_API_URL = "https://volute.systems";
|
|
4580
|
+
var app18 = new Hono18().post("/restart", requireAdmin, (c) => {
|
|
4694
4581
|
setTimeout(() => process.exit(1), 200);
|
|
4695
4582
|
return c.json({ ok: true });
|
|
4696
4583
|
}).post("/stop", requireAdmin, (c) => {
|
|
@@ -4707,29 +4594,107 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
|
|
|
4707
4594
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
4708
4595
|
});
|
|
4709
4596
|
});
|
|
4710
|
-
await new Promise((
|
|
4597
|
+
await new Promise((resolve20) => {
|
|
4711
4598
|
stream.onAbort(() => {
|
|
4712
4599
|
unsubscribe();
|
|
4713
|
-
|
|
4600
|
+
resolve20();
|
|
4714
4601
|
});
|
|
4715
4602
|
});
|
|
4716
4603
|
});
|
|
4717
4604
|
}).get("/info", (c) => {
|
|
4718
4605
|
const config = readSystemsConfig();
|
|
4719
4606
|
return c.json({ system: config?.system ?? null });
|
|
4607
|
+
}).post(
|
|
4608
|
+
"/register",
|
|
4609
|
+
requireAdmin,
|
|
4610
|
+
zValidator5("json", z5.object({ name: z5.string().min(1) })),
|
|
4611
|
+
async (c) => {
|
|
4612
|
+
const existing = readSystemsConfig();
|
|
4613
|
+
if (existing) {
|
|
4614
|
+
return c.json({ error: `Already registered as "${existing.system}"` }, 400);
|
|
4615
|
+
}
|
|
4616
|
+
const { name } = c.req.valid("json");
|
|
4617
|
+
const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
|
|
4618
|
+
let apiKey;
|
|
4619
|
+
let system;
|
|
4620
|
+
try {
|
|
4621
|
+
const res = await fetch(`${apiUrl}/api/register`, {
|
|
4622
|
+
method: "POST",
|
|
4623
|
+
headers: { "Content-Type": "application/json" },
|
|
4624
|
+
body: JSON.stringify({ name: name.trim() })
|
|
4625
|
+
});
|
|
4626
|
+
if (!res.ok) {
|
|
4627
|
+
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
4628
|
+
return c.json({ error: err.error }, 502);
|
|
4629
|
+
}
|
|
4630
|
+
({ apiKey, system } = await res.json());
|
|
4631
|
+
} catch (err) {
|
|
4632
|
+
return c.json({ error: `Connection failed: ${err.message}` }, 502);
|
|
4633
|
+
}
|
|
4634
|
+
try {
|
|
4635
|
+
writeSystemsConfig({ apiKey, system, apiUrl });
|
|
4636
|
+
} catch (err) {
|
|
4637
|
+
return c.json(
|
|
4638
|
+
{
|
|
4639
|
+
error: `Registered as "${system}" but failed to save config: ${err.message}`
|
|
4640
|
+
},
|
|
4641
|
+
500
|
|
4642
|
+
);
|
|
4643
|
+
}
|
|
4644
|
+
return c.json({ system });
|
|
4645
|
+
}
|
|
4646
|
+
).post(
|
|
4647
|
+
"/login",
|
|
4648
|
+
requireAdmin,
|
|
4649
|
+
zValidator5("json", z5.object({ key: z5.string().min(1) })),
|
|
4650
|
+
async (c) => {
|
|
4651
|
+
const existing = readSystemsConfig();
|
|
4652
|
+
if (existing) {
|
|
4653
|
+
return c.json({ error: `Already logged in as "${existing.system}"` }, 400);
|
|
4654
|
+
}
|
|
4655
|
+
const { key } = c.req.valid("json");
|
|
4656
|
+
const apiUrl = process.env.VOLUTE_SYSTEMS_URL || DEFAULT_API_URL;
|
|
4657
|
+
let system;
|
|
4658
|
+
try {
|
|
4659
|
+
const res = await fetch(`${apiUrl}/api/whoami`, {
|
|
4660
|
+
headers: { Authorization: `Bearer ${key.trim()}` }
|
|
4661
|
+
});
|
|
4662
|
+
if (!res.ok) {
|
|
4663
|
+
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
4664
|
+
return c.json({ error: err.error }, 502);
|
|
4665
|
+
}
|
|
4666
|
+
({ system } = await res.json());
|
|
4667
|
+
} catch (err) {
|
|
4668
|
+
return c.json({ error: `Connection failed: ${err.message}` }, 502);
|
|
4669
|
+
}
|
|
4670
|
+
try {
|
|
4671
|
+
writeSystemsConfig({ apiKey: key.trim(), system, apiUrl });
|
|
4672
|
+
} catch (err) {
|
|
4673
|
+
return c.json(
|
|
4674
|
+
{
|
|
4675
|
+
error: `Logged in as "${system}" but failed to save config: ${err.message}`
|
|
4676
|
+
},
|
|
4677
|
+
500
|
|
4678
|
+
);
|
|
4679
|
+
}
|
|
4680
|
+
return c.json({ system });
|
|
4681
|
+
}
|
|
4682
|
+
).post("/logout", requireAdmin, (c) => {
|
|
4683
|
+
deleteSystemsConfig();
|
|
4684
|
+
return c.json({ ok: true });
|
|
4720
4685
|
});
|
|
4721
|
-
var system_default =
|
|
4686
|
+
var system_default = app18;
|
|
4722
4687
|
|
|
4723
4688
|
// src/web/api/typing.ts
|
|
4724
|
-
import { zValidator as
|
|
4725
|
-
import { Hono as
|
|
4726
|
-
import { z as
|
|
4727
|
-
var typingSchema =
|
|
4728
|
-
channel:
|
|
4729
|
-
sender:
|
|
4730
|
-
active:
|
|
4689
|
+
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
4690
|
+
import { Hono as Hono19 } from "hono";
|
|
4691
|
+
import { z as z6 } from "zod";
|
|
4692
|
+
var typingSchema = z6.object({
|
|
4693
|
+
channel: z6.string().min(1),
|
|
4694
|
+
sender: z6.string().min(1),
|
|
4695
|
+
active: z6.boolean()
|
|
4731
4696
|
});
|
|
4732
|
-
var
|
|
4697
|
+
var app19 = new Hono19().post("/:name/typing", zValidator6("json", typingSchema), (c) => {
|
|
4733
4698
|
const { channel, sender, active } = c.req.valid("json");
|
|
4734
4699
|
const map = getTypingMap();
|
|
4735
4700
|
if (active) {
|
|
@@ -4751,13 +4716,13 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
|
|
|
4751
4716
|
const map = getTypingMap();
|
|
4752
4717
|
return c.json({ typing: map.get(channel) });
|
|
4753
4718
|
});
|
|
4754
|
-
var typing_default =
|
|
4719
|
+
var typing_default = app19;
|
|
4755
4720
|
|
|
4756
4721
|
// src/web/api/update.ts
|
|
4757
4722
|
import { spawn as spawn2 } from "child_process";
|
|
4758
|
-
import { Hono as
|
|
4723
|
+
import { Hono as Hono20 } from "hono";
|
|
4759
4724
|
var bin;
|
|
4760
|
-
var
|
|
4725
|
+
var app20 = new Hono20().get("/update", async (c) => {
|
|
4761
4726
|
const result = await checkForUpdate();
|
|
4762
4727
|
return c.json(result);
|
|
4763
4728
|
}).post("/update", requireAdmin, async (c) => {
|
|
@@ -4772,21 +4737,21 @@ var app19 = new Hono19().get("/update", async (c) => {
|
|
|
4772
4737
|
child.unref();
|
|
4773
4738
|
return c.json({ ok: true, message: "Updating..." });
|
|
4774
4739
|
});
|
|
4775
|
-
var update_default =
|
|
4740
|
+
var update_default = app20;
|
|
4776
4741
|
|
|
4777
4742
|
// src/web/api/v1/chat.ts
|
|
4778
|
-
import { zValidator as
|
|
4779
|
-
import { Hono as
|
|
4743
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
4744
|
+
import { Hono as Hono21 } from "hono";
|
|
4780
4745
|
import { streamSSE as streamSSE4 } from "hono/streaming";
|
|
4781
|
-
import { z as
|
|
4746
|
+
import { z as z7 } from "zod";
|
|
4782
4747
|
async function fanOutToMinds(opts) {
|
|
4783
4748
|
const participants = await getParticipants(opts.conversationId);
|
|
4784
4749
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
4785
4750
|
const participantNames = participants.map((p) => p.username);
|
|
4786
4751
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
4787
4752
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
4788
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
4789
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4753
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-4NDNAYAB.js");
|
|
4754
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
4790
4755
|
const manager = getMindManager2();
|
|
4791
4756
|
const sm = getSleepManagerIfReady();
|
|
4792
4757
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -4835,18 +4800,18 @@ async function fanOutToMinds(opts) {
|
|
|
4835
4800
|
});
|
|
4836
4801
|
}
|
|
4837
4802
|
}
|
|
4838
|
-
var mindChatSchema =
|
|
4839
|
-
message:
|
|
4840
|
-
conversationId:
|
|
4841
|
-
sender:
|
|
4842
|
-
images:
|
|
4803
|
+
var mindChatSchema = z7.object({
|
|
4804
|
+
message: z7.string().optional(),
|
|
4805
|
+
conversationId: z7.string().optional(),
|
|
4806
|
+
sender: z7.string().optional(),
|
|
4807
|
+
images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
|
|
4843
4808
|
});
|
|
4844
|
-
var unifiedChatSchema =
|
|
4845
|
-
message:
|
|
4846
|
-
conversationId:
|
|
4847
|
-
images:
|
|
4809
|
+
var unifiedChatSchema = z7.object({
|
|
4810
|
+
message: z7.string().optional(),
|
|
4811
|
+
conversationId: z7.string(),
|
|
4812
|
+
images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
|
|
4848
4813
|
});
|
|
4849
|
-
var
|
|
4814
|
+
var app21 = new Hono21().use("*", authMiddleware).post("/minds/:name/chat", zValidator7("json", mindChatSchema), async (c) => {
|
|
4850
4815
|
const name = c.req.param("name");
|
|
4851
4816
|
const [baseName] = name.split("@", 2);
|
|
4852
4817
|
const entry = findMind(baseName);
|
|
@@ -4900,11 +4865,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4900
4865
|
}
|
|
4901
4866
|
}
|
|
4902
4867
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
4868
|
+
const isDM = conv?.type === "dm";
|
|
4903
4869
|
await fanOutToMinds({
|
|
4904
4870
|
conversationId,
|
|
4905
4871
|
contentBlocks,
|
|
4906
4872
|
senderName,
|
|
4907
4873
|
convTitle,
|
|
4874
|
+
isDM,
|
|
4875
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
4876
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
4908
4877
|
targetName: (username) => username === baseName ? name : username
|
|
4909
4878
|
});
|
|
4910
4879
|
return c.json({ ok: true, conversationId });
|
|
@@ -4925,15 +4894,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4925
4894
|
if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
|
|
4926
4895
|
});
|
|
4927
4896
|
}, 15e3);
|
|
4928
|
-
await new Promise((
|
|
4897
|
+
await new Promise((resolve20) => {
|
|
4929
4898
|
stream.onAbort(() => {
|
|
4930
4899
|
unsubscribe();
|
|
4931
4900
|
clearInterval(keepAlive);
|
|
4932
|
-
|
|
4901
|
+
resolve20();
|
|
4933
4902
|
});
|
|
4934
4903
|
});
|
|
4935
4904
|
});
|
|
4936
|
-
}).post("/chat",
|
|
4905
|
+
}).post("/chat", zValidator7("json", unifiedChatSchema), async (c) => {
|
|
4937
4906
|
const user = c.get("user");
|
|
4938
4907
|
const body = c.req.valid("json");
|
|
4939
4908
|
if (!body.message && (!body.images || body.images.length === 0)) {
|
|
@@ -4965,17 +4934,17 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4965
4934
|
});
|
|
4966
4935
|
return c.json({ ok: true, conversationId: body.conversationId });
|
|
4967
4936
|
});
|
|
4968
|
-
var chat_default =
|
|
4937
|
+
var chat_default = app21;
|
|
4969
4938
|
|
|
4970
4939
|
// src/web/api/v1/conversations.ts
|
|
4971
|
-
import { zValidator as
|
|
4972
|
-
import { Hono as
|
|
4973
|
-
import { z as
|
|
4974
|
-
var createSchema =
|
|
4975
|
-
title:
|
|
4976
|
-
participantNames:
|
|
4940
|
+
import { zValidator as zValidator8 } from "@hono/zod-validator";
|
|
4941
|
+
import { Hono as Hono22 } from "hono";
|
|
4942
|
+
import { z as z8 } from "zod";
|
|
4943
|
+
var createSchema = z8.object({
|
|
4944
|
+
title: z8.string().optional(),
|
|
4945
|
+
participantNames: z8.array(z8.string()).min(1)
|
|
4977
4946
|
});
|
|
4978
|
-
var
|
|
4947
|
+
var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
4979
4948
|
const user = c.get("user");
|
|
4980
4949
|
const convs = await listConversationsWithParticipants(user.id);
|
|
4981
4950
|
return c.json(convs);
|
|
@@ -5006,7 +4975,7 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5006
4975
|
}
|
|
5007
4976
|
const participants = await getParticipants(id);
|
|
5008
4977
|
return c.json(participants);
|
|
5009
|
-
}).post("/",
|
|
4978
|
+
}).post("/", zValidator8("json", createSchema), async (c) => {
|
|
5010
4979
|
const user = c.get("user");
|
|
5011
4980
|
const body = c.req.valid("json");
|
|
5012
4981
|
const participantIds = /* @__PURE__ */ new Set();
|
|
@@ -5036,6 +5005,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5036
5005
|
participantIds: [...participantIds]
|
|
5037
5006
|
});
|
|
5038
5007
|
return c.json(conv, 201);
|
|
5008
|
+
}).post("/:id/read", async (c) => {
|
|
5009
|
+
const id = c.req.param("id");
|
|
5010
|
+
const user = c.get("user");
|
|
5011
|
+
if (user.id === 0) return c.json({ ok: true });
|
|
5012
|
+
if (!await isParticipantOrOwner(id, user.id)) {
|
|
5013
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
5014
|
+
}
|
|
5015
|
+
await markConversationRead(user.id, id);
|
|
5016
|
+
return c.json({ ok: true });
|
|
5039
5017
|
}).delete("/:id", async (c) => {
|
|
5040
5018
|
const id = c.req.param("id");
|
|
5041
5019
|
const user = c.get("user");
|
|
@@ -5043,13 +5021,36 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5043
5021
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
5044
5022
|
return c.json({ ok: true });
|
|
5045
5023
|
});
|
|
5046
|
-
var conversations_default =
|
|
5024
|
+
var conversations_default = app22;
|
|
5047
5025
|
|
|
5048
5026
|
// src/web/api/v1/events.ts
|
|
5049
|
-
import { desc as
|
|
5050
|
-
import { Hono as
|
|
5027
|
+
import { desc as desc3 } from "drizzle-orm";
|
|
5028
|
+
import { Hono as Hono23 } from "hono";
|
|
5051
5029
|
import { streamSSE as streamSSE5 } from "hono/streaming";
|
|
5052
5030
|
|
|
5031
|
+
// src/lib/events/brain-presence.ts
|
|
5032
|
+
var connections = /* @__PURE__ */ new Map();
|
|
5033
|
+
function addConnection(username) {
|
|
5034
|
+
const count = connections.get(username) ?? 0;
|
|
5035
|
+
connections.set(username, count + 1);
|
|
5036
|
+
if (count === 0) {
|
|
5037
|
+
broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
function removeConnection(username) {
|
|
5041
|
+
const count = connections.get(username);
|
|
5042
|
+
if (count == null) return;
|
|
5043
|
+
if (count <= 1) {
|
|
5044
|
+
connections.delete(username);
|
|
5045
|
+
broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
|
|
5046
|
+
} else {
|
|
5047
|
+
connections.set(username, count - 1);
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
function getOnlineBrains() {
|
|
5051
|
+
return [...connections.keys()];
|
|
5052
|
+
}
|
|
5053
|
+
|
|
5053
5054
|
// src/lib/events/event-sequencer.ts
|
|
5054
5055
|
var BUFFER_SIZE = 1e3;
|
|
5055
5056
|
var MAX_AGE_MS = 5 * 60 * 1e3;
|
|
@@ -5071,12 +5072,16 @@ function getEventsSince(sinceId) {
|
|
|
5071
5072
|
}
|
|
5072
5073
|
|
|
5073
5074
|
// src/web/api/v1/events.ts
|
|
5074
|
-
var
|
|
5075
|
+
var app23 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
|
|
5075
5076
|
const user = c.get("user");
|
|
5076
5077
|
const since = c.req.query("since");
|
|
5077
5078
|
const sinceId = since ? Number(since) : 0;
|
|
5078
5079
|
return streamSSE5(c, async (stream) => {
|
|
5079
5080
|
const cleanups = [];
|
|
5081
|
+
if (user.user_type === "brain") {
|
|
5082
|
+
addConnection(user.username);
|
|
5083
|
+
cleanups.push(() => removeConnection(user.username));
|
|
5084
|
+
}
|
|
5080
5085
|
try {
|
|
5081
5086
|
if (sinceId > 0) {
|
|
5082
5087
|
const missed = getEventsSince(sinceId);
|
|
@@ -5090,7 +5095,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5090
5095
|
let recentActivity = [];
|
|
5091
5096
|
try {
|
|
5092
5097
|
const db = await getDb();
|
|
5093
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
5098
|
+
recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
|
|
5094
5099
|
recentActivity = recentActivity.map((row) => ({
|
|
5095
5100
|
...row,
|
|
5096
5101
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -5101,6 +5106,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5101
5106
|
let conversations2 = [];
|
|
5102
5107
|
try {
|
|
5103
5108
|
conversations2 = await listConversationsWithParticipants(user.id);
|
|
5109
|
+
if (conversations2.length > 0) {
|
|
5110
|
+
const convIds = conversations2.map((c2) => c2.id);
|
|
5111
|
+
const unreads = await getUnreadCounts(user.id, convIds);
|
|
5112
|
+
for (const conv of conversations2) {
|
|
5113
|
+
conv.unreadCount = unreads[conv.id] ?? 0;
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5104
5116
|
} catch (err) {
|
|
5105
5117
|
logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
|
|
5106
5118
|
}
|
|
@@ -5112,7 +5124,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5112
5124
|
conversations: conversations2,
|
|
5113
5125
|
sites,
|
|
5114
5126
|
recentPages,
|
|
5115
|
-
activeMinds: getActiveMinds()
|
|
5127
|
+
activeMinds: getActiveMinds(),
|
|
5128
|
+
onlineBrains: getOnlineBrains()
|
|
5116
5129
|
};
|
|
5117
5130
|
const snapshotId = bufferEvent(snapshotData);
|
|
5118
5131
|
await stream.writeSSE({
|
|
@@ -5153,8 +5166,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5153
5166
|
});
|
|
5154
5167
|
}, 15e3);
|
|
5155
5168
|
cleanups.push(() => clearInterval(keepAlive));
|
|
5156
|
-
await new Promise((
|
|
5157
|
-
stream.onAbort(() =>
|
|
5169
|
+
await new Promise((resolve20) => {
|
|
5170
|
+
stream.onAbort(() => resolve20());
|
|
5158
5171
|
});
|
|
5159
5172
|
} finally {
|
|
5160
5173
|
for (const cleanup of cleanups) {
|
|
@@ -5166,19 +5179,19 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5166
5179
|
}
|
|
5167
5180
|
});
|
|
5168
5181
|
});
|
|
5169
|
-
var events_default =
|
|
5182
|
+
var events_default = app23;
|
|
5170
5183
|
|
|
5171
5184
|
// src/web/api/variants.ts
|
|
5172
|
-
import { existsSync as
|
|
5173
|
-
import { resolve as
|
|
5174
|
-
import { Hono as
|
|
5185
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
5186
|
+
import { resolve as resolve17 } from "path";
|
|
5187
|
+
import { Hono as Hono24 } from "hono";
|
|
5175
5188
|
|
|
5176
5189
|
// src/lib/spawn-server.ts
|
|
5177
5190
|
import { spawn as spawn3 } from "child_process";
|
|
5178
|
-
import { closeSync, mkdirSync as
|
|
5179
|
-
import { resolve as
|
|
5191
|
+
import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
|
|
5192
|
+
import { resolve as resolve16 } from "path";
|
|
5180
5193
|
function tsxBin(cwd) {
|
|
5181
|
-
return
|
|
5194
|
+
return resolve16(cwd, "node_modules", ".bin", "tsx");
|
|
5182
5195
|
}
|
|
5183
5196
|
function spawnServer(cwd, port, options) {
|
|
5184
5197
|
if (options?.detached) {
|
|
@@ -5191,31 +5204,31 @@ function spawnAttached(cwd, port) {
|
|
|
5191
5204
|
cwd,
|
|
5192
5205
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5193
5206
|
});
|
|
5194
|
-
return new Promise((
|
|
5195
|
-
const timeout = setTimeout(() =>
|
|
5207
|
+
return new Promise((resolve20) => {
|
|
5208
|
+
const timeout = setTimeout(() => resolve20(null), 3e4);
|
|
5196
5209
|
function checkOutput(data) {
|
|
5197
5210
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
5198
5211
|
if (match) {
|
|
5199
5212
|
clearTimeout(timeout);
|
|
5200
|
-
|
|
5213
|
+
resolve20({ child, actualPort: parseInt(match[1], 10) });
|
|
5201
5214
|
}
|
|
5202
5215
|
}
|
|
5203
5216
|
child.stdout?.on("data", checkOutput);
|
|
5204
5217
|
child.stderr?.on("data", checkOutput);
|
|
5205
5218
|
child.on("error", () => {
|
|
5206
5219
|
clearTimeout(timeout);
|
|
5207
|
-
|
|
5220
|
+
resolve20(null);
|
|
5208
5221
|
});
|
|
5209
5222
|
child.on("exit", () => {
|
|
5210
5223
|
clearTimeout(timeout);
|
|
5211
|
-
|
|
5224
|
+
resolve20(null);
|
|
5212
5225
|
});
|
|
5213
5226
|
});
|
|
5214
5227
|
}
|
|
5215
5228
|
function spawnDetached(cwd, port, logDir) {
|
|
5216
|
-
const logsDir = logDir ??
|
|
5217
|
-
|
|
5218
|
-
const logPath =
|
|
5229
|
+
const logsDir = logDir ?? resolve16(cwd, ".mind", "logs");
|
|
5230
|
+
mkdirSync7(logsDir, { recursive: true });
|
|
5231
|
+
const logPath = resolve16(logsDir, "mind.log");
|
|
5219
5232
|
const logFd = openSync(logPath, "a");
|
|
5220
5233
|
const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
5221
5234
|
cwd,
|
|
@@ -5235,7 +5248,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
5235
5248
|
}
|
|
5236
5249
|
const interval = setInterval(() => {
|
|
5237
5250
|
try {
|
|
5238
|
-
const content =
|
|
5251
|
+
const content = readFileSync10(logPath, "utf-8");
|
|
5239
5252
|
const match = content.match(/listening on :(\d+)/);
|
|
5240
5253
|
if (match) {
|
|
5241
5254
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -5287,7 +5300,7 @@ async function verify2(port) {
|
|
|
5287
5300
|
}
|
|
5288
5301
|
|
|
5289
5302
|
// src/web/api/variants.ts
|
|
5290
|
-
var
|
|
5303
|
+
var app24 = new Hono24().get("/:name/variants", async (c) => {
|
|
5291
5304
|
const name = c.req.param("name");
|
|
5292
5305
|
const entry = findMind(name);
|
|
5293
5306
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -5327,11 +5340,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5327
5340
|
const err = validateBranchName(variantName);
|
|
5328
5341
|
if (err) return c.json({ error: err }, 400);
|
|
5329
5342
|
const projectRoot = mindDir(mindName);
|
|
5330
|
-
const variantDir =
|
|
5331
|
-
if (
|
|
5343
|
+
const variantDir = resolve17(projectRoot, ".variants", variantName);
|
|
5344
|
+
if (existsSync12(variantDir)) {
|
|
5332
5345
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
5333
5346
|
}
|
|
5334
|
-
|
|
5347
|
+
mkdirSync8(resolve17(projectRoot, ".variants"), { recursive: true });
|
|
5335
5348
|
try {
|
|
5336
5349
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
5337
5350
|
} catch (e) {
|
|
@@ -5344,7 +5357,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5344
5357
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5345
5358
|
await exec(cmd, args, {
|
|
5346
5359
|
cwd: variantDir,
|
|
5347
|
-
env: { ...process.env, HOME:
|
|
5360
|
+
env: { ...process.env, HOME: resolve17(variantDir, "home") }
|
|
5348
5361
|
});
|
|
5349
5362
|
} else {
|
|
5350
5363
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -5354,7 +5367,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5354
5367
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
5355
5368
|
}
|
|
5356
5369
|
if (body.soul) {
|
|
5357
|
-
|
|
5370
|
+
writeFileSync8(resolve17(variantDir, "home/SOUL.md"), body.soul);
|
|
5358
5371
|
}
|
|
5359
5372
|
const variantPort = body.port ?? nextPort();
|
|
5360
5373
|
const variant = {
|
|
@@ -5392,7 +5405,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5392
5405
|
} catch {
|
|
5393
5406
|
}
|
|
5394
5407
|
const projectRoot = mindDir(mindName);
|
|
5395
|
-
if (
|
|
5408
|
+
if (existsSync12(variant.path)) {
|
|
5396
5409
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
5397
5410
|
if (status) {
|
|
5398
5411
|
try {
|
|
@@ -5465,7 +5478,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5465
5478
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5466
5479
|
await exec(cmd, args, {
|
|
5467
5480
|
cwd: projectRoot,
|
|
5468
|
-
env: { ...process.env, HOME:
|
|
5481
|
+
env: { ...process.env, HOME: resolve17(projectRoot, "home") }
|
|
5469
5482
|
});
|
|
5470
5483
|
} else {
|
|
5471
5484
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -5503,19 +5516,19 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5503
5516
|
await cleanupVariant(mindName, variantName, projectRoot, variant.path, { stop: true });
|
|
5504
5517
|
return c.json({ ok: true });
|
|
5505
5518
|
});
|
|
5506
|
-
var variants_default =
|
|
5519
|
+
var variants_default = app24;
|
|
5507
5520
|
|
|
5508
5521
|
// src/web/api/volute/channels.ts
|
|
5509
|
-
import { zValidator as
|
|
5510
|
-
import { Hono as
|
|
5511
|
-
import { z as
|
|
5512
|
-
var createSchema2 =
|
|
5513
|
-
name:
|
|
5522
|
+
import { zValidator as zValidator9 } from "@hono/zod-validator";
|
|
5523
|
+
import { Hono as Hono25 } from "hono";
|
|
5524
|
+
import { z as z9 } from "zod";
|
|
5525
|
+
var createSchema2 = z9.object({
|
|
5526
|
+
name: z9.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
|
|
5514
5527
|
});
|
|
5515
|
-
var inviteSchema =
|
|
5516
|
-
username:
|
|
5528
|
+
var inviteSchema = z9.object({
|
|
5529
|
+
username: z9.string().min(1)
|
|
5517
5530
|
});
|
|
5518
|
-
var
|
|
5531
|
+
var app25 = new Hono25().get("/", async (c) => {
|
|
5519
5532
|
const user = c.get("user");
|
|
5520
5533
|
const channels = await listChannels();
|
|
5521
5534
|
const results = await Promise.all(
|
|
@@ -5526,7 +5539,7 @@ var app24 = new Hono24().get("/", async (c) => {
|
|
|
5526
5539
|
})
|
|
5527
5540
|
);
|
|
5528
5541
|
return c.json(results);
|
|
5529
|
-
}).post("/",
|
|
5542
|
+
}).post("/", zValidator9("json", createSchema2), async (c) => {
|
|
5530
5543
|
const user = c.get("user");
|
|
5531
5544
|
const body = c.req.valid("json");
|
|
5532
5545
|
try {
|
|
@@ -5559,7 +5572,7 @@ var app24 = new Hono24().get("/", async (c) => {
|
|
|
5559
5572
|
if (!ch) return c.json({ error: "Channel not found" }, 404);
|
|
5560
5573
|
const participants = await getParticipants(ch.id);
|
|
5561
5574
|
return c.json(participants);
|
|
5562
|
-
}).post("/:name/invite",
|
|
5575
|
+
}).post("/:name/invite", zValidator9("json", inviteSchema), async (c) => {
|
|
5563
5576
|
const name = c.req.param("name");
|
|
5564
5577
|
const inviter = c.get("user");
|
|
5565
5578
|
const { username } = c.req.valid("json");
|
|
@@ -5579,21 +5592,21 @@ var app24 = new Hono24().get("/", async (c) => {
|
|
|
5579
5592
|
]);
|
|
5580
5593
|
return c.json({ ok: true });
|
|
5581
5594
|
});
|
|
5582
|
-
var channels_default2 =
|
|
5595
|
+
var channels_default2 = app25;
|
|
5583
5596
|
|
|
5584
5597
|
// src/web/api/volute/chat.ts
|
|
5585
|
-
import { zValidator as
|
|
5586
|
-
import { Hono as
|
|
5598
|
+
import { zValidator as zValidator10 } from "@hono/zod-validator";
|
|
5599
|
+
import { Hono as Hono26 } from "hono";
|
|
5587
5600
|
import { streamSSE as streamSSE6 } from "hono/streaming";
|
|
5588
|
-
import { z as
|
|
5601
|
+
import { z as z10 } from "zod";
|
|
5589
5602
|
async function fanOutToMinds2(opts) {
|
|
5590
5603
|
const participants = await getParticipants(opts.conversationId);
|
|
5591
5604
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
5592
5605
|
const participantNames = participants.map((p) => p.username);
|
|
5593
5606
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
5594
5607
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
5595
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
5596
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
5608
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-4NDNAYAB.js");
|
|
5609
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
|
|
5597
5610
|
const manager = getMindManager2();
|
|
5598
5611
|
const sm = getSleepManagerIfReady();
|
|
5599
5612
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -5641,18 +5654,18 @@ async function fanOutToMinds2(opts) {
|
|
|
5641
5654
|
});
|
|
5642
5655
|
}
|
|
5643
5656
|
}
|
|
5644
|
-
var chatSchema =
|
|
5645
|
-
message:
|
|
5646
|
-
conversationId:
|
|
5647
|
-
sender:
|
|
5648
|
-
images:
|
|
5649
|
-
|
|
5650
|
-
media_type:
|
|
5651
|
-
data:
|
|
5657
|
+
var chatSchema = z10.object({
|
|
5658
|
+
message: z10.string().optional(),
|
|
5659
|
+
conversationId: z10.string().optional(),
|
|
5660
|
+
sender: z10.string().optional(),
|
|
5661
|
+
images: z10.array(
|
|
5662
|
+
z10.object({
|
|
5663
|
+
media_type: z10.string(),
|
|
5664
|
+
data: z10.string()
|
|
5652
5665
|
})
|
|
5653
5666
|
).optional()
|
|
5654
5667
|
});
|
|
5655
|
-
var
|
|
5668
|
+
var app26 = new Hono26().post("/:name/chat", zValidator10("json", chatSchema), async (c) => {
|
|
5656
5669
|
const name = c.req.param("name");
|
|
5657
5670
|
const [baseName] = name.split("@", 2);
|
|
5658
5671
|
const entry = findMind(baseName);
|
|
@@ -5710,11 +5723,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5710
5723
|
}
|
|
5711
5724
|
}
|
|
5712
5725
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
5726
|
+
const isDM = conv?.type === "dm";
|
|
5713
5727
|
await fanOutToMinds2({
|
|
5714
5728
|
conversationId,
|
|
5715
5729
|
contentBlocks,
|
|
5716
5730
|
senderName,
|
|
5717
5731
|
convTitle,
|
|
5732
|
+
isDM,
|
|
5733
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
5734
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
5718
5735
|
targetName: (username) => username === baseName ? name : username
|
|
5719
5736
|
});
|
|
5720
5737
|
return c.json({ ok: true, conversationId });
|
|
@@ -5735,23 +5752,23 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5735
5752
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5736
5753
|
});
|
|
5737
5754
|
}, 15e3);
|
|
5738
|
-
await new Promise((
|
|
5755
|
+
await new Promise((resolve20) => {
|
|
5739
5756
|
stream.onAbort(() => {
|
|
5740
5757
|
unsubscribe();
|
|
5741
5758
|
clearInterval(keepAlive);
|
|
5742
|
-
|
|
5759
|
+
resolve20();
|
|
5743
5760
|
});
|
|
5744
5761
|
});
|
|
5745
5762
|
});
|
|
5746
5763
|
});
|
|
5747
|
-
var unifiedChatSchema2 =
|
|
5748
|
-
message:
|
|
5749
|
-
conversationId:
|
|
5750
|
-
images:
|
|
5764
|
+
var unifiedChatSchema2 = z10.object({
|
|
5765
|
+
message: z10.string().optional(),
|
|
5766
|
+
conversationId: z10.string(),
|
|
5767
|
+
images: z10.array(z10.object({ media_type: z10.string(), data: z10.string() })).optional()
|
|
5751
5768
|
});
|
|
5752
|
-
var unifiedChatApp = new
|
|
5769
|
+
var unifiedChatApp = new Hono26().post(
|
|
5753
5770
|
"/chat",
|
|
5754
|
-
|
|
5771
|
+
zValidator10("json", unifiedChatSchema2),
|
|
5755
5772
|
async (c) => {
|
|
5756
5773
|
const user = c.get("user");
|
|
5757
5774
|
const body = c.req.valid("json");
|
|
@@ -5785,18 +5802,18 @@ var unifiedChatApp = new Hono25().post(
|
|
|
5785
5802
|
return c.json({ ok: true, conversationId: body.conversationId });
|
|
5786
5803
|
}
|
|
5787
5804
|
);
|
|
5788
|
-
var chat_default2 =
|
|
5805
|
+
var chat_default2 = app26;
|
|
5789
5806
|
|
|
5790
5807
|
// src/web/api/volute/conversations.ts
|
|
5791
|
-
import { zValidator as
|
|
5792
|
-
import { Hono as
|
|
5793
|
-
import { z as
|
|
5794
|
-
var createConvSchema =
|
|
5795
|
-
title:
|
|
5796
|
-
participantIds:
|
|
5797
|
-
participantNames:
|
|
5808
|
+
import { zValidator as zValidator11 } from "@hono/zod-validator";
|
|
5809
|
+
import { Hono as Hono27 } from "hono";
|
|
5810
|
+
import { z as z11 } from "zod";
|
|
5811
|
+
var createConvSchema = z11.object({
|
|
5812
|
+
title: z11.string().optional(),
|
|
5813
|
+
participantIds: z11.array(z11.number()).optional(),
|
|
5814
|
+
participantNames: z11.array(z11.string()).optional()
|
|
5798
5815
|
});
|
|
5799
|
-
var
|
|
5816
|
+
var app27 = new Hono27().get("/:name/conversations", async (c) => {
|
|
5800
5817
|
const name = c.req.param("name");
|
|
5801
5818
|
const user = c.get("user");
|
|
5802
5819
|
let lookupId = user.id;
|
|
@@ -5807,7 +5824,7 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
|
|
|
5807
5824
|
const all = await listConversationsForUser(lookupId);
|
|
5808
5825
|
const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
|
|
5809
5826
|
return c.json(convs);
|
|
5810
|
-
}).post("/:name/conversations",
|
|
5827
|
+
}).post("/:name/conversations", zValidator11("json", createConvSchema), async (c) => {
|
|
5811
5828
|
const name = c.req.param("name");
|
|
5812
5829
|
const user = c.get("user");
|
|
5813
5830
|
const body = c.req.valid("json");
|
|
@@ -5881,18 +5898,18 @@ var app26 = new Hono26().get("/:name/conversations", async (c) => {
|
|
|
5881
5898
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
5882
5899
|
return c.json({ ok: true });
|
|
5883
5900
|
});
|
|
5884
|
-
var conversations_default2 =
|
|
5901
|
+
var conversations_default2 = app27;
|
|
5885
5902
|
|
|
5886
5903
|
// src/web/api/volute/user-conversations.ts
|
|
5887
|
-
import { zValidator as
|
|
5888
|
-
import { Hono as
|
|
5904
|
+
import { zValidator as zValidator12 } from "@hono/zod-validator";
|
|
5905
|
+
import { Hono as Hono28 } from "hono";
|
|
5889
5906
|
import { streamSSE as streamSSE7 } from "hono/streaming";
|
|
5890
|
-
import { z as
|
|
5891
|
-
var createSchema3 =
|
|
5892
|
-
title:
|
|
5893
|
-
participantNames:
|
|
5907
|
+
import { z as z12 } from "zod";
|
|
5908
|
+
var createSchema3 = z12.object({
|
|
5909
|
+
title: z12.string().optional(),
|
|
5910
|
+
participantNames: z12.array(z12.string()).min(1)
|
|
5894
5911
|
});
|
|
5895
|
-
var
|
|
5912
|
+
var app28 = new Hono28().use("*", authMiddleware).get("/", async (c) => {
|
|
5896
5913
|
const user = c.get("user");
|
|
5897
5914
|
const convs = await listConversationsWithParticipants(user.id);
|
|
5898
5915
|
return c.json(convs);
|
|
@@ -5904,7 +5921,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5904
5921
|
}
|
|
5905
5922
|
const msgs = await getMessages(id);
|
|
5906
5923
|
return c.json(msgs);
|
|
5907
|
-
}).post("/",
|
|
5924
|
+
}).post("/", zValidator12("json", createSchema3), async (c) => {
|
|
5908
5925
|
const user = c.get("user");
|
|
5909
5926
|
const body = c.req.valid("json");
|
|
5910
5927
|
const participantIds = /* @__PURE__ */ new Set();
|
|
@@ -5951,11 +5968,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5951
5968
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5952
5969
|
});
|
|
5953
5970
|
}, 15e3);
|
|
5954
|
-
await new Promise((
|
|
5971
|
+
await new Promise((resolve20) => {
|
|
5955
5972
|
stream.onAbort(() => {
|
|
5956
5973
|
unsubscribe();
|
|
5957
5974
|
clearInterval(keepAlive);
|
|
5958
|
-
|
|
5975
|
+
resolve20();
|
|
5959
5976
|
});
|
|
5960
5977
|
});
|
|
5961
5978
|
});
|
|
@@ -5966,12 +5983,12 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5966
5983
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
5967
5984
|
return c.json({ ok: true });
|
|
5968
5985
|
});
|
|
5969
|
-
var user_conversations_default =
|
|
5986
|
+
var user_conversations_default = app28;
|
|
5970
5987
|
|
|
5971
5988
|
// src/web/app.ts
|
|
5972
5989
|
var httpLog = logger_default.child("http");
|
|
5973
|
-
var
|
|
5974
|
-
|
|
5990
|
+
var app29 = new Hono29();
|
|
5991
|
+
app29.onError((err, c) => {
|
|
5975
5992
|
if (err instanceof HTTPException) {
|
|
5976
5993
|
return err.getResponse();
|
|
5977
5994
|
}
|
|
@@ -5982,10 +5999,10 @@ app28.onError((err, c) => {
|
|
|
5982
5999
|
});
|
|
5983
6000
|
return c.json({ error: "Internal server error" }, 500);
|
|
5984
6001
|
});
|
|
5985
|
-
|
|
6002
|
+
app29.notFound((c) => {
|
|
5986
6003
|
return c.json({ error: "Not found" }, 404);
|
|
5987
6004
|
});
|
|
5988
|
-
|
|
6005
|
+
app29.use("*", async (c, next) => {
|
|
5989
6006
|
const start = Date.now();
|
|
5990
6007
|
await next();
|
|
5991
6008
|
const duration = Date.now() - start;
|
|
@@ -5996,7 +6013,7 @@ app28.use("*", async (c, next) => {
|
|
|
5996
6013
|
httpLog.debug("request", data);
|
|
5997
6014
|
}
|
|
5998
6015
|
});
|
|
5999
|
-
|
|
6016
|
+
app29.get("/api/health", (c) => {
|
|
6000
6017
|
let version = "unknown";
|
|
6001
6018
|
let cached = null;
|
|
6002
6019
|
try {
|
|
@@ -6011,38 +6028,39 @@ app28.get("/api/health", (c) => {
|
|
|
6011
6028
|
...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
|
|
6012
6029
|
});
|
|
6013
6030
|
});
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6031
|
+
app29.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
|
|
6032
|
+
app29.use("/api/*", csrf());
|
|
6033
|
+
app29.use("/api/activity/*", authMiddleware);
|
|
6034
|
+
app29.use("/api/minds/*", authMiddleware);
|
|
6035
|
+
app29.use("/api/conversations/*", authMiddleware);
|
|
6036
|
+
app29.use("/api/volute/*", authMiddleware);
|
|
6037
|
+
app29.use("/api/system/*", authMiddleware);
|
|
6038
|
+
app29.use("/api/env/*", authMiddleware);
|
|
6039
|
+
app29.use("/api/prompts/*", authMiddleware);
|
|
6040
|
+
app29.use("/api/skills/*", authMiddleware);
|
|
6041
|
+
app29.use("/api/v1/*", authMiddleware);
|
|
6042
|
+
app29.route("/pages", pages_default);
|
|
6043
|
+
app29.route("/public", public_files_default);
|
|
6044
|
+
var routes = app29.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default2).route("/api/minds", connectors_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
|
|
6045
|
+
app29.route("/api/v1/minds", minds_default);
|
|
6046
|
+
app29.route("/api/v1/minds", typing_default);
|
|
6047
|
+
app29.route("/api/v1/minds", variants_default);
|
|
6048
|
+
app29.route("/api/v1/minds", files_default);
|
|
6049
|
+
app29.route("/api/v1/minds", env_default);
|
|
6050
|
+
app29.route("/api/v1/minds", mind_skills_default);
|
|
6051
|
+
app29.route("/api/v1/minds", connectors_default);
|
|
6052
|
+
app29.route("/api/v1/minds", schedules_default);
|
|
6053
|
+
app29.route("/api/v1/minds", logs_default);
|
|
6054
|
+
app29.route("/api/v1/system", system_default);
|
|
6055
|
+
app29.route("/api/v1/system", update_default);
|
|
6056
|
+
app29.route("/api/v1/prompts", prompts_default);
|
|
6057
|
+
app29.route("/api/v1/skills", skills_default);
|
|
6058
|
+
app29.route("/api/v1/env", sharedEnvApp);
|
|
6059
|
+
app29.route("/api/v1/channels", channels_default2);
|
|
6060
|
+
var app_default = app29;
|
|
6043
6061
|
|
|
6044
6062
|
// src/web/server.ts
|
|
6045
|
-
var
|
|
6063
|
+
var MIME_TYPES3 = {
|
|
6046
6064
|
".html": "text/html",
|
|
6047
6065
|
".js": "application/javascript",
|
|
6048
6066
|
".css": "text/css",
|
|
@@ -6053,13 +6071,14 @@ var MIME_TYPES2 = {
|
|
|
6053
6071
|
};
|
|
6054
6072
|
async function startServer({
|
|
6055
6073
|
port,
|
|
6056
|
-
hostname = "127.0.0.1"
|
|
6074
|
+
hostname = "127.0.0.1",
|
|
6075
|
+
tls
|
|
6057
6076
|
}) {
|
|
6058
6077
|
let assetsDir = "";
|
|
6059
6078
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
6060
6079
|
for (let i = 0; i < 5; i++) {
|
|
6061
|
-
const candidate =
|
|
6062
|
-
if (
|
|
6080
|
+
const candidate = resolve18(searchDir, "dist", "web-assets");
|
|
6081
|
+
if (existsSync13(candidate)) {
|
|
6063
6082
|
assetsDir = candidate;
|
|
6064
6083
|
break;
|
|
6065
6084
|
}
|
|
@@ -6069,40 +6088,73 @@ async function startServer({
|
|
|
6069
6088
|
app_default.get("*", async (c) => {
|
|
6070
6089
|
const urlPath = new URL(c.req.url).pathname;
|
|
6071
6090
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
6072
|
-
const filePath =
|
|
6091
|
+
const filePath = resolve18(assetsDir, urlPath.slice(1));
|
|
6073
6092
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
6074
|
-
const s = await
|
|
6093
|
+
const s = await stat4(filePath).catch(() => null);
|
|
6075
6094
|
if (s?.isFile()) {
|
|
6076
|
-
const ext =
|
|
6077
|
-
const mime =
|
|
6078
|
-
const body = await
|
|
6095
|
+
const ext = extname5(filePath);
|
|
6096
|
+
const mime = MIME_TYPES3[ext] || "application/octet-stream";
|
|
6097
|
+
const body = await readFile4(filePath);
|
|
6079
6098
|
return c.body(body, 200, { "Content-Type": mime });
|
|
6080
6099
|
}
|
|
6081
|
-
const indexPath =
|
|
6082
|
-
const indexStat = await
|
|
6100
|
+
const indexPath = resolve18(assetsDir, "index.html");
|
|
6101
|
+
const indexStat = await stat4(indexPath).catch(() => null);
|
|
6083
6102
|
if (indexStat?.isFile()) {
|
|
6084
|
-
const body = await
|
|
6103
|
+
const body = await readFile4(indexPath, "utf-8");
|
|
6085
6104
|
return c.html(body);
|
|
6086
6105
|
}
|
|
6087
6106
|
return c.text("Not found", 404);
|
|
6088
6107
|
});
|
|
6089
6108
|
}
|
|
6109
|
+
if (tls) {
|
|
6110
|
+
const server2 = serve({
|
|
6111
|
+
fetch: app_default.fetch,
|
|
6112
|
+
port,
|
|
6113
|
+
hostname,
|
|
6114
|
+
createServer: createHttpsServer,
|
|
6115
|
+
serverOptions: { key: tls.key, cert: tls.cert }
|
|
6116
|
+
});
|
|
6117
|
+
await new Promise((resolve20, reject) => {
|
|
6118
|
+
server2.on("listening", () => {
|
|
6119
|
+
logger_default.info("Volute UI running (https)", { hostname, port });
|
|
6120
|
+
resolve20();
|
|
6121
|
+
});
|
|
6122
|
+
server2.on("error", (err) => {
|
|
6123
|
+
reject(err);
|
|
6124
|
+
});
|
|
6125
|
+
});
|
|
6126
|
+
const internalPort = port + 1;
|
|
6127
|
+
const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
|
|
6128
|
+
await new Promise((resolve20, reject) => {
|
|
6129
|
+
internalServer.on("listening", () => {
|
|
6130
|
+
logger_default.info("Volute API running (http, internal)", {
|
|
6131
|
+
hostname: "127.0.0.1",
|
|
6132
|
+
port: internalPort
|
|
6133
|
+
});
|
|
6134
|
+
resolve20();
|
|
6135
|
+
});
|
|
6136
|
+
internalServer.on("error", (err) => {
|
|
6137
|
+
reject(err);
|
|
6138
|
+
});
|
|
6139
|
+
});
|
|
6140
|
+
return { server: server2, internalPort };
|
|
6141
|
+
}
|
|
6090
6142
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
6091
|
-
await new Promise((
|
|
6143
|
+
await new Promise((resolve20, reject) => {
|
|
6092
6144
|
server.on("listening", () => {
|
|
6093
|
-
logger_default.info("Volute
|
|
6094
|
-
|
|
6145
|
+
logger_default.info("Volute API running (http)", { hostname, port });
|
|
6146
|
+
resolve20();
|
|
6095
6147
|
});
|
|
6096
6148
|
server.on("error", (err) => {
|
|
6097
6149
|
reject(err);
|
|
6098
6150
|
});
|
|
6099
6151
|
});
|
|
6100
|
-
return server;
|
|
6152
|
+
return { server };
|
|
6101
6153
|
}
|
|
6102
6154
|
|
|
6103
6155
|
// src/daemon.ts
|
|
6104
6156
|
if (!process.env.VOLUTE_HOME) {
|
|
6105
|
-
process.env.VOLUTE_HOME =
|
|
6157
|
+
process.env.VOLUTE_HOME = resolve19(homedir2(), ".volute");
|
|
6106
6158
|
}
|
|
6107
6159
|
if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
|
|
6108
6160
|
process.env.TZ = process.env.VOLUTE_TIMEZONE;
|
|
@@ -6112,7 +6164,7 @@ async function startDaemon(opts) {
|
|
|
6112
6164
|
const myPid = String(process.pid);
|
|
6113
6165
|
const home = voluteHome();
|
|
6114
6166
|
if (!opts.foreground) {
|
|
6115
|
-
const rotatingLog = new RotatingLog(
|
|
6167
|
+
const rotatingLog = new RotatingLog(resolve19(home, "daemon.log"));
|
|
6116
6168
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
6117
6169
|
`));
|
|
6118
6170
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -6122,9 +6174,9 @@ async function startDaemon(opts) {
|
|
|
6122
6174
|
console.warn = write;
|
|
6123
6175
|
console.info = write;
|
|
6124
6176
|
}
|
|
6125
|
-
const DAEMON_PID_PATH =
|
|
6126
|
-
const DAEMON_JSON_PATH =
|
|
6127
|
-
|
|
6177
|
+
const DAEMON_PID_PATH = resolve19(home, "daemon.pid");
|
|
6178
|
+
const DAEMON_JSON_PATH = resolve19(home, "daemon.json");
|
|
6179
|
+
mkdirSync9(home, { recursive: true });
|
|
6128
6180
|
migrateAgentsToMinds();
|
|
6129
6181
|
try {
|
|
6130
6182
|
await ensureSharedRepo();
|
|
@@ -6138,12 +6190,16 @@ async function startDaemon(opts) {
|
|
|
6138
6190
|
logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
|
|
6139
6191
|
}
|
|
6140
6192
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6193
|
+
let tls;
|
|
6194
|
+
if (opts.tailscale) {
|
|
6195
|
+
const { getTailscaleTls } = await import("./tailscale-AJ4VL5XK.js");
|
|
6196
|
+
const tlsConfig = await getTailscaleTls();
|
|
6197
|
+
tls = { key: tlsConfig.key, cert: tlsConfig.cert };
|
|
6198
|
+
logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
|
|
6199
|
+
}
|
|
6200
|
+
let result;
|
|
6145
6201
|
try {
|
|
6146
|
-
|
|
6202
|
+
result = await startServer({ port, hostname: "0.0.0.0", tls });
|
|
6147
6203
|
} catch (err) {
|
|
6148
6204
|
const e = err;
|
|
6149
6205
|
if (e.code === "EADDRINUSE") {
|
|
@@ -6152,11 +6208,17 @@ async function startDaemon(opts) {
|
|
|
6152
6208
|
}
|
|
6153
6209
|
throw err;
|
|
6154
6210
|
}
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6211
|
+
const { server, internalPort } = result;
|
|
6212
|
+
const daemonPort = internalPort ?? port;
|
|
6213
|
+
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
6214
|
+
process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
|
|
6215
|
+
process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
|
|
6216
|
+
writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
6217
|
+
const daemonConfig = { port, hostname, token };
|
|
6218
|
+
if (internalPort) daemonConfig.internalPort = internalPort;
|
|
6219
|
+
if (tls) daemonConfig.tls = true;
|
|
6220
|
+
writeFileSync9(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
|
|
6221
|
+
`, { mode: 420 });
|
|
6160
6222
|
const delivery = initDeliveryManager();
|
|
6161
6223
|
const manager = initMindManager();
|
|
6162
6224
|
manager.loadCrashAttempts();
|
|
@@ -6172,11 +6234,12 @@ async function startDaemon(opts) {
|
|
|
6172
6234
|
const unsubscribeWebhook = initWebhook();
|
|
6173
6235
|
const registry = readRegistry();
|
|
6174
6236
|
for (const entry of registry) {
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6237
|
+
for (const migrate of [migrateDotVoluteDir, migrateMindState, migratePagesDirToPublic]) {
|
|
6238
|
+
try {
|
|
6239
|
+
migrate(entry.name);
|
|
6240
|
+
} catch (err) {
|
|
6241
|
+
logger_default.warn(`failed to migrate state for ${entry.name}`, logger_default.errorData(err));
|
|
6242
|
+
}
|
|
6180
6243
|
}
|
|
6181
6244
|
}
|
|
6182
6245
|
const runningEntries = registry.filter((e) => e.running);
|
|
@@ -6188,8 +6251,8 @@ async function startDaemon(opts) {
|
|
|
6188
6251
|
if (sleepManager.isSleeping(entry.name)) {
|
|
6189
6252
|
try {
|
|
6190
6253
|
const dir = mindDir(entry.name);
|
|
6191
|
-
const
|
|
6192
|
-
await connectors.startConnectors(entry.name, dir, entry.port,
|
|
6254
|
+
const daemonPort2 = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
|
|
6255
|
+
await connectors.startConnectors(entry.name, dir, entry.port, daemonPort2);
|
|
6193
6256
|
scheduler.loadSchedules(entry.name);
|
|
6194
6257
|
} catch (err) {
|
|
6195
6258
|
logger_default.error(
|
|
@@ -6226,7 +6289,7 @@ async function startDaemon(opts) {
|
|
|
6226
6289
|
});
|
|
6227
6290
|
await Promise.all(workers);
|
|
6228
6291
|
}
|
|
6229
|
-
import("./cloud-sync-
|
|
6292
|
+
import("./cloud-sync-PPBBJDY6.js").then(
|
|
6230
6293
|
({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
|
|
6231
6294
|
logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
|
|
6232
6295
|
})
|
|
@@ -6234,7 +6297,7 @@ async function startDaemon(opts) {
|
|
|
6234
6297
|
logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
|
|
6235
6298
|
});
|
|
6236
6299
|
try {
|
|
6237
|
-
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-
|
|
6300
|
+
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-AZQMC32A.js");
|
|
6238
6301
|
backfillTemplateHashes();
|
|
6239
6302
|
notifyVersionUpdate().catch((err) => {
|
|
6240
6303
|
logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
|
|
@@ -6251,13 +6314,13 @@ async function startDaemon(opts) {
|
|
|
6251
6314
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
6252
6315
|
function cleanup() {
|
|
6253
6316
|
try {
|
|
6254
|
-
if (
|
|
6317
|
+
if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
6255
6318
|
unlinkSync(DAEMON_PID_PATH);
|
|
6256
6319
|
}
|
|
6257
6320
|
} catch {
|
|
6258
6321
|
}
|
|
6259
6322
|
try {
|
|
6260
|
-
const data = JSON.parse(
|
|
6323
|
+
const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
|
|
6261
6324
|
if (data.token === token) {
|
|
6262
6325
|
unlinkSync(DAEMON_JSON_PATH);
|
|
6263
6326
|
}
|
|
@@ -6271,9 +6334,9 @@ async function startDaemon(opts) {
|
|
|
6271
6334
|
logger_default.info("shutting down...");
|
|
6272
6335
|
const safe = (label, fn) => {
|
|
6273
6336
|
try {
|
|
6274
|
-
const
|
|
6275
|
-
if (
|
|
6276
|
-
return
|
|
6337
|
+
const result2 = fn();
|
|
6338
|
+
if (result2 instanceof Promise)
|
|
6339
|
+
return result2.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
|
|
6277
6340
|
} catch (err) {
|
|
6278
6341
|
logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
|
|
6279
6342
|
}
|
|
@@ -6305,9 +6368,10 @@ async function startDaemon(opts) {
|
|
|
6305
6368
|
process.on("exit", cleanup);
|
|
6306
6369
|
}
|
|
6307
6370
|
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
|
|
6308
|
-
let port =
|
|
6371
|
+
let port = 1618;
|
|
6309
6372
|
let hostname = "127.0.0.1";
|
|
6310
6373
|
let foreground = false;
|
|
6374
|
+
let tailscale = false;
|
|
6311
6375
|
for (let i = 2; i < process.argv.length; i++) {
|
|
6312
6376
|
if (process.argv[i] === "--port" && process.argv[i + 1]) {
|
|
6313
6377
|
port = parseInt(process.argv[i + 1], 10);
|
|
@@ -6317,9 +6381,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
6317
6381
|
i++;
|
|
6318
6382
|
} else if (process.argv[i] === "--foreground") {
|
|
6319
6383
|
foreground = true;
|
|
6384
|
+
} else if (process.argv[i] === "--tailscale") {
|
|
6385
|
+
tailscale = true;
|
|
6320
6386
|
}
|
|
6321
6387
|
}
|
|
6322
|
-
startDaemon({ port, hostname, foreground });
|
|
6388
|
+
startDaemon({ port, hostname, foreground, tailscale });
|
|
6323
6389
|
}
|
|
6324
6390
|
export {
|
|
6325
6391
|
startDaemon
|