volute 0.23.0 → 0.24.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 +306 -15
- 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-RK627D57.js → chunk-4TJ72QQ3.js} +2 -2
- package/dist/{chunk-A4S7H6G6.js → chunk-BFK6SOEJ.js} +1 -1
- package/dist/{chunk-HGCDWKSP.js → chunk-E7GOKNOT.js} +1 -1
- package/dist/{chunk-M5CNKH4J.js → chunk-NOBRGACV.js} +7 -7
- package/dist/{chunk-ISWZ6QUK.js → chunk-OOW675I3.js} +778 -108
- package/dist/{chunk-TFS25FIM.js → chunk-P3W36ZGD.js} +1 -1
- package/dist/{chunk-JG4CCJOA.js → chunk-TQDITGES.js} +33 -15
- package/dist/{chunk-KFI7TQJ6.js → chunk-TRQEV3CD.js} +9 -5
- package/dist/cli.js +18 -18
- package/dist/{cloud-sync-PI47U2LT.js → cloud-sync-DIU3OCPV.js} +6 -8
- 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-YMPEATQH.js} +5 -5
- package/dist/daemon.js +665 -813
- 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-FRDPQPJ2.js} +1 -1
- 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-S7BCNV6Y.js} +5 -5
- package/dist/{mind-BTXR5B3C.js → mind-KPLCRKQA.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-ZNRIYEK3.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-S5YF25XV.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-BQOFACEI.js} +1 -1
- package/dist/skills/volute-mind/SKILL.md +71 -1
- package/dist/{sleep-manager-2TMQ65E4.js → sleep-manager-XXSWQQLE.js} +5 -5
- package/dist/{sprout-UKCYBGHK.js → sprout-CGSW4CF5.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-OMHACRJL.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-SZ75QRGO.js} +5 -5
- package/dist/web-assets/assets/index-Bx9WDoaQ.js +69 -0
- package/dist/web-assets/assets/index-Clz8OhmJ.css +1 -0
- package/dist/web-assets/index.html +2 -2
- 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 +1 -1
- 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/{pages-YSTRWJR4.js → pages-TWR6U7DS.js} +0 -0
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,32 +17,68 @@ 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-OOW675I3.js";
|
|
50
82
|
import {
|
|
51
83
|
readSystemsConfig
|
|
52
84
|
} from "./chunk-HFCBO2GL.js";
|
|
@@ -54,11 +86,11 @@ import {
|
|
|
54
86
|
getActiveMinds,
|
|
55
87
|
onMindEvent,
|
|
56
88
|
stopAll
|
|
57
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-E7GOKNOT.js";
|
|
58
90
|
import {
|
|
59
91
|
broadcast,
|
|
60
92
|
subscribe
|
|
61
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-BFK6SOEJ.js";
|
|
62
94
|
import {
|
|
63
95
|
PROMPT_DEFAULTS,
|
|
64
96
|
PROMPT_KEYS,
|
|
@@ -69,13 +101,13 @@ import {
|
|
|
69
101
|
getPromptIfCustom,
|
|
70
102
|
initMindManager,
|
|
71
103
|
substitute
|
|
72
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-NOBRGACV.js";
|
|
73
105
|
import {
|
|
74
106
|
findOpenClawSession,
|
|
75
107
|
importOpenClawConnectors,
|
|
76
108
|
importPiSession,
|
|
77
109
|
parseNameFromIdentity
|
|
78
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-4TJ72QQ3.js";
|
|
79
111
|
import {
|
|
80
112
|
readVoluteConfig,
|
|
81
113
|
writeVoluteConfig
|
|
@@ -105,18 +137,15 @@ import {
|
|
|
105
137
|
syncBuiltinSkills,
|
|
106
138
|
uninstallSkill,
|
|
107
139
|
updateSkill
|
|
108
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-P3W36ZGD.js";
|
|
109
141
|
import {
|
|
110
142
|
activity,
|
|
111
|
-
conversationParticipants,
|
|
112
143
|
conversations,
|
|
113
144
|
getDb,
|
|
114
|
-
messages,
|
|
115
145
|
mindHistory,
|
|
116
146
|
sessions,
|
|
117
|
-
systemPrompts
|
|
118
|
-
|
|
119
|
-
} from "./chunk-SGPEZ32F.js";
|
|
147
|
+
systemPrompts
|
|
148
|
+
} from "./chunk-33XAVCS4.js";
|
|
120
149
|
import {
|
|
121
150
|
logBuffer,
|
|
122
151
|
logger_default
|
|
@@ -179,9 +208,9 @@ import {
|
|
|
179
208
|
|
|
180
209
|
// src/daemon.ts
|
|
181
210
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
182
|
-
import { mkdirSync as
|
|
211
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
183
212
|
import { homedir as homedir2 } from "os";
|
|
184
|
-
import { resolve as
|
|
213
|
+
import { resolve as resolve18 } from "path";
|
|
185
214
|
import { format } from "util";
|
|
186
215
|
|
|
187
216
|
// src/lib/migrate-agents-to-minds.ts
|
|
@@ -359,145 +388,9 @@ function migrateMindState(name) {
|
|
|
359
388
|
|
|
360
389
|
// src/web/middleware/auth.ts
|
|
361
390
|
import { timingSafeEqual } from "crypto";
|
|
362
|
-
import { eq
|
|
391
|
+
import { eq, lt } from "drizzle-orm";
|
|
363
392
|
import { getCookie } from "hono/cookie";
|
|
364
393
|
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
394
|
function isValidDaemonToken(token) {
|
|
502
395
|
const expected = process.env.VOLUTE_DAEMON_TOKEN;
|
|
503
396
|
if (!expected || token.length !== expected.length) return false;
|
|
@@ -506,6 +399,9 @@ function isValidDaemonToken(token) {
|
|
|
506
399
|
var SESSION_MAX_AGE = 864e5;
|
|
507
400
|
var SESSION_CACHE_TTL = 5 * 60 * 1e3;
|
|
508
401
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
402
|
+
function invalidateSessionCache(sessionId) {
|
|
403
|
+
sessionCache.delete(sessionId);
|
|
404
|
+
}
|
|
509
405
|
async function createSession(userId) {
|
|
510
406
|
const db = await getDb();
|
|
511
407
|
const sessionId = crypto.randomUUID();
|
|
@@ -515,14 +411,14 @@ async function createSession(userId) {
|
|
|
515
411
|
async function deleteSession(sessionId) {
|
|
516
412
|
sessionCache.delete(sessionId);
|
|
517
413
|
const db = await getDb();
|
|
518
|
-
await db.delete(sessions).where(
|
|
414
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
519
415
|
}
|
|
520
416
|
async function getSessionUserId(sessionId) {
|
|
521
417
|
const db = await getDb();
|
|
522
|
-
const row = await db.select().from(sessions).where(
|
|
418
|
+
const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
|
|
523
419
|
if (!row) return void 0;
|
|
524
420
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
525
|
-
await db.delete(sessions).where(
|
|
421
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
526
422
|
return void 0;
|
|
527
423
|
}
|
|
528
424
|
return row.userId;
|
|
@@ -544,7 +440,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
544
440
|
if (authHeader?.startsWith("Bearer ")) {
|
|
545
441
|
const token = authHeader.slice(7);
|
|
546
442
|
if (token && isValidDaemonToken(token)) {
|
|
547
|
-
c.set("user", {
|
|
443
|
+
c.set("user", {
|
|
444
|
+
id: 0,
|
|
445
|
+
username: "cli",
|
|
446
|
+
role: "admin",
|
|
447
|
+
user_type: "brain",
|
|
448
|
+
display_name: null,
|
|
449
|
+
description: null,
|
|
450
|
+
avatar: null
|
|
451
|
+
});
|
|
548
452
|
await next();
|
|
549
453
|
return;
|
|
550
454
|
}
|
|
@@ -575,9 +479,10 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
575
479
|
});
|
|
576
480
|
|
|
577
481
|
// src/web/server.ts
|
|
578
|
-
import { existsSync as
|
|
482
|
+
import { existsSync as existsSync13 } from "fs";
|
|
579
483
|
import { readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
580
|
-
import {
|
|
484
|
+
import { createServer as createHttpsServer } from "https";
|
|
485
|
+
import { dirname, extname as extname4, resolve as resolve17 } from "path";
|
|
581
486
|
import { serve } from "@hono/node-server";
|
|
582
487
|
|
|
583
488
|
// src/web/app.ts
|
|
@@ -587,294 +492,9 @@ import { csrf } from "hono/csrf";
|
|
|
587
492
|
import { HTTPException } from "hono/http-exception";
|
|
588
493
|
|
|
589
494
|
// src/web/api/activity.ts
|
|
590
|
-
import { desc
|
|
495
|
+
import { desc } from "drizzle-orm";
|
|
591
496
|
import { Hono } from "hono";
|
|
592
497
|
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
498
|
var app = new Hono().get("/events", async (c) => {
|
|
879
499
|
const user = c.get("user");
|
|
880
500
|
return streamSSE(c, async (stream) => {
|
|
@@ -883,7 +503,7 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
883
503
|
let recentActivity = [];
|
|
884
504
|
try {
|
|
885
505
|
const db = await getDb();
|
|
886
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
506
|
+
recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
|
|
887
507
|
recentActivity = recentActivity.map((row) => ({
|
|
888
508
|
...row,
|
|
889
509
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -933,8 +553,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
933
553
|
});
|
|
934
554
|
}, 15e3);
|
|
935
555
|
cleanups.push(() => clearInterval(keepAlive));
|
|
936
|
-
await new Promise((
|
|
937
|
-
stream.onAbort(() =>
|
|
556
|
+
await new Promise((resolve19) => {
|
|
557
|
+
stream.onAbort(() => resolve19());
|
|
938
558
|
});
|
|
939
559
|
} finally {
|
|
940
560
|
for (const cleanup of cleanups) {
|
|
@@ -949,6 +569,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
949
569
|
var activity_default = app;
|
|
950
570
|
|
|
951
571
|
// src/web/api/auth.ts
|
|
572
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
573
|
+
import { extname, resolve as resolve3 } from "path";
|
|
952
574
|
import { zValidator } from "@hono/zod-validator";
|
|
953
575
|
import { Hono as Hono2 } from "hono";
|
|
954
576
|
import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
|
|
@@ -961,12 +583,71 @@ var changePasswordSchema = z.object({
|
|
|
961
583
|
currentPassword: z.string().min(1),
|
|
962
584
|
newPassword: z.string().min(1)
|
|
963
585
|
});
|
|
586
|
+
var profileSchema = z.object({
|
|
587
|
+
display_name: z.string().max(100).nullable().optional(),
|
|
588
|
+
description: z.string().max(500).nullable().optional()
|
|
589
|
+
});
|
|
590
|
+
var AVATAR_MIME = {
|
|
591
|
+
".png": "image/png",
|
|
592
|
+
".jpg": "image/jpeg",
|
|
593
|
+
".jpeg": "image/jpeg",
|
|
594
|
+
".gif": "image/gif",
|
|
595
|
+
".webp": "image/webp"
|
|
596
|
+
};
|
|
597
|
+
var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
|
|
598
|
+
function avatarsDir() {
|
|
599
|
+
return resolve3(voluteHome(), "avatars");
|
|
600
|
+
}
|
|
964
601
|
var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
965
602
|
const user = c.get("user");
|
|
966
603
|
const { currentPassword, newPassword } = c.req.valid("json");
|
|
967
604
|
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
968
605
|
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
969
606
|
return c.json({ ok: true });
|
|
607
|
+
}).put("/profile", zValidator("json", profileSchema), async (c) => {
|
|
608
|
+
const user = c.get("user");
|
|
609
|
+
const body = c.req.valid("json");
|
|
610
|
+
await updateUserProfile(user.id, body);
|
|
611
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
612
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
613
|
+
return c.json({ ok: true });
|
|
614
|
+
}).post("/avatar", async (c) => {
|
|
615
|
+
const user = c.get("user");
|
|
616
|
+
const body = await c.req.parseBody();
|
|
617
|
+
const file = body.file;
|
|
618
|
+
if (!(file instanceof File)) {
|
|
619
|
+
return c.json({ error: "No file uploaded" }, 400);
|
|
620
|
+
}
|
|
621
|
+
if (file.size > MAX_AVATAR_SIZE) {
|
|
622
|
+
return c.json({ error: "File too large (max 2MB)" }, 400);
|
|
623
|
+
}
|
|
624
|
+
const ext = extname(file.name).toLowerCase();
|
|
625
|
+
if (!AVATAR_MIME[ext]) {
|
|
626
|
+
return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
|
|
627
|
+
}
|
|
628
|
+
const dir = avatarsDir();
|
|
629
|
+
mkdirSync2(dir, { recursive: true });
|
|
630
|
+
const filename = `avatar-${user.id}${ext}`;
|
|
631
|
+
const buffer2 = Buffer.from(await file.arrayBuffer());
|
|
632
|
+
writeFileSync2(resolve3(dir, filename), buffer2);
|
|
633
|
+
if (user.avatar && user.avatar !== filename) {
|
|
634
|
+
const oldPath = resolve3(dir, user.avatar);
|
|
635
|
+
rmSync(oldPath, { force: true });
|
|
636
|
+
}
|
|
637
|
+
await updateUserProfile(user.id, { avatar: filename });
|
|
638
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
639
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
640
|
+
return c.json({ ok: true, avatar: filename });
|
|
641
|
+
}).delete("/avatar", async (c) => {
|
|
642
|
+
const user = c.get("user");
|
|
643
|
+
if (user.avatar) {
|
|
644
|
+
const path = resolve3(avatarsDir(), user.avatar);
|
|
645
|
+
rmSync(path, { force: true });
|
|
646
|
+
}
|
|
647
|
+
await updateUserProfile(user.id, { avatar: null });
|
|
648
|
+
const sessionId = getCookie2(c, "volute_session");
|
|
649
|
+
if (sessionId) invalidateSessionCache(sessionId);
|
|
650
|
+
return c.json({ ok: true });
|
|
970
651
|
});
|
|
971
652
|
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
972
653
|
const user = c.get("user");
|
|
@@ -988,8 +669,55 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
|
988
669
|
const user = c.get("user");
|
|
989
670
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
990
671
|
const id = parseInt(c.req.param("id"), 10);
|
|
672
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
991
673
|
await approveUser(id);
|
|
992
674
|
return c.json({ ok: true });
|
|
675
|
+
}).post(
|
|
676
|
+
"/users/:id/role",
|
|
677
|
+
zValidator("json", z.object({ role: z.enum(["admin", "user"]) })),
|
|
678
|
+
async (c) => {
|
|
679
|
+
const user = c.get("user");
|
|
680
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
681
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
682
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
683
|
+
const { role } = c.req.valid("json");
|
|
684
|
+
if (role !== "admin") {
|
|
685
|
+
const adminCount = await countAdmins();
|
|
686
|
+
if (adminCount <= 1) {
|
|
687
|
+
const target = await getUser(id);
|
|
688
|
+
if (target?.role === "admin") {
|
|
689
|
+
return c.json({ error: "Cannot remove the last admin" }, 400);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
await setUserRole(id, role);
|
|
694
|
+
return c.json({ ok: true });
|
|
695
|
+
}
|
|
696
|
+
).put("/users/:id/profile", zValidator("json", profileSchema), async (c) => {
|
|
697
|
+
const user = c.get("user");
|
|
698
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
699
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
700
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
701
|
+
const body = c.req.valid("json");
|
|
702
|
+
await updateUserProfile(id, body);
|
|
703
|
+
return c.json({ ok: true });
|
|
704
|
+
}).delete("/users/:id", async (c) => {
|
|
705
|
+
const user = c.get("user");
|
|
706
|
+
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
707
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
708
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
709
|
+
if (id === user.id) return c.json({ error: "Cannot delete yourself" }, 400);
|
|
710
|
+
const target = await getUser(id);
|
|
711
|
+
if (!target) return c.json({ error: "User not found" }, 404);
|
|
712
|
+
if (target.role === "admin") {
|
|
713
|
+
const adminCount = await countAdmins();
|
|
714
|
+
if (adminCount <= 1) return c.json({ error: "Cannot delete the last admin" }, 400);
|
|
715
|
+
}
|
|
716
|
+
if (target.user_type === "mind") {
|
|
717
|
+
return c.json({ error: "Use the mind deletion API to delete minds" }, 400);
|
|
718
|
+
}
|
|
719
|
+
await deleteUser(id);
|
|
720
|
+
return c.json({ ok: true });
|
|
993
721
|
});
|
|
994
722
|
var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
|
|
995
723
|
const { username, password } = c.req.valid("json");
|
|
@@ -1026,7 +754,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1026
754
|
if (userId == null) return c.json({ error: "Not logged in" }, 401);
|
|
1027
755
|
const user = await getUser(userId);
|
|
1028
756
|
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
1029
|
-
return c.json({
|
|
757
|
+
return c.json({
|
|
758
|
+
id: user.id,
|
|
759
|
+
username: user.username,
|
|
760
|
+
role: user.role,
|
|
761
|
+
display_name: user.display_name,
|
|
762
|
+
description: user.description,
|
|
763
|
+
avatar: user.avatar
|
|
764
|
+
});
|
|
765
|
+
}).get("/avatars/:filename", async (c) => {
|
|
766
|
+
const filename = c.req.param("filename");
|
|
767
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
768
|
+
return c.json({ error: "Invalid filename" }, 400);
|
|
769
|
+
}
|
|
770
|
+
const dir = avatarsDir();
|
|
771
|
+
const filePath = resolve3(dir, filename);
|
|
772
|
+
if (!filePath.startsWith(`${dir}/`)) return c.json({ error: "Invalid path" }, 400);
|
|
773
|
+
if (!existsSync3(filePath)) return c.json({ error: "Not found" }, 404);
|
|
774
|
+
const ext = extname(filename).toLowerCase();
|
|
775
|
+
const mime = AVATAR_MIME[ext];
|
|
776
|
+
if (!mime) return c.json({ error: "Invalid file type" }, 400);
|
|
777
|
+
const data = readFileSync2(filePath);
|
|
778
|
+
return c.body(data, 200, {
|
|
779
|
+
"Content-Type": mime,
|
|
780
|
+
"Cache-Control": "public, max-age=3600",
|
|
781
|
+
"X-Content-Type-Options": "nosniff"
|
|
782
|
+
});
|
|
1030
783
|
}).route("/", admin).route("/", authenticated);
|
|
1031
784
|
var auth_default = app2;
|
|
1032
785
|
|
|
@@ -1067,8 +820,8 @@ async function read(env, channelSlug, limit) {
|
|
|
1067
820
|
if (!res.ok) {
|
|
1068
821
|
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
1069
822
|
}
|
|
1070
|
-
const
|
|
1071
|
-
return
|
|
823
|
+
const messages = await res.json();
|
|
824
|
+
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
1072
825
|
}
|
|
1073
826
|
async function send(env, channelSlug, message, images) {
|
|
1074
827
|
const token = requireToken(env);
|
|
@@ -1300,8 +1053,8 @@ async function listConversations2(env) {
|
|
|
1300
1053
|
const userMap = /* @__PURE__ */ new Map();
|
|
1301
1054
|
const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
|
|
1302
1055
|
if (imChannels.length > 0) {
|
|
1303
|
-
const
|
|
1304
|
-
for (const u of
|
|
1056
|
+
const users = await listUsers3(env);
|
|
1057
|
+
for (const u of users) {
|
|
1305
1058
|
userMap.set(u.id, u.username);
|
|
1306
1059
|
}
|
|
1307
1060
|
}
|
|
@@ -1496,16 +1249,16 @@ __export(volute_exports, {
|
|
|
1496
1249
|
read: () => read4,
|
|
1497
1250
|
send: () => send4
|
|
1498
1251
|
});
|
|
1499
|
-
import { existsSync as
|
|
1500
|
-
import { resolve as
|
|
1252
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1253
|
+
import { resolve as resolve4 } from "path";
|
|
1501
1254
|
function getDaemonConfig() {
|
|
1502
|
-
const configPath2 =
|
|
1503
|
-
if (!
|
|
1255
|
+
const configPath2 = resolve4(voluteHome(), "daemon.json");
|
|
1256
|
+
if (!existsSync4(configPath2)) {
|
|
1504
1257
|
throw new Error("Volute daemon is not running");
|
|
1505
1258
|
}
|
|
1506
1259
|
let config;
|
|
1507
1260
|
try {
|
|
1508
|
-
config = JSON.parse(
|
|
1261
|
+
config = JSON.parse(readFileSync3(configPath2, "utf-8"));
|
|
1509
1262
|
} catch (err) {
|
|
1510
1263
|
throw new Error(`Failed to parse ${configPath2}: ${err}`);
|
|
1511
1264
|
}
|
|
@@ -1531,8 +1284,8 @@ async function read4(env, channelSlug, limit) {
|
|
|
1531
1284
|
if (!res.ok) {
|
|
1532
1285
|
throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
|
|
1533
1286
|
}
|
|
1534
|
-
const
|
|
1535
|
-
return
|
|
1287
|
+
const messages = await res.json();
|
|
1288
|
+
return messages.slice(-limit).map((m) => {
|
|
1536
1289
|
const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
|
|
1537
1290
|
return `${m.sender_name ?? m.role}: ${text}`;
|
|
1538
1291
|
}).join("\n");
|
|
@@ -1759,8 +1512,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
|
|
|
1759
1512
|
return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
|
|
1760
1513
|
const env = buildEnv(name);
|
|
1761
1514
|
try {
|
|
1762
|
-
const
|
|
1763
|
-
return c.json(
|
|
1515
|
+
const users = await driver.listUsers(env);
|
|
1516
|
+
return c.json(users);
|
|
1764
1517
|
} catch (err) {
|
|
1765
1518
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1766
1519
|
}
|
|
@@ -1942,14 +1695,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
|
1942
1695
|
var env_default = app5;
|
|
1943
1696
|
|
|
1944
1697
|
// src/web/api/file-sharing.ts
|
|
1945
|
-
import { readFileSync as
|
|
1946
|
-
import { resolve as
|
|
1698
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
1699
|
+
import { resolve as resolve6 } from "path";
|
|
1947
1700
|
import { Hono as Hono6 } from "hono";
|
|
1948
1701
|
|
|
1949
1702
|
// src/lib/file-sharing.ts
|
|
1950
1703
|
import { randomBytes } from "crypto";
|
|
1951
|
-
import { existsSync as
|
|
1952
|
-
import { basename, join, normalize, resolve as
|
|
1704
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1705
|
+
import { basename, join, normalize, resolve as resolve5 } from "path";
|
|
1953
1706
|
function validateFilePath(filePath) {
|
|
1954
1707
|
if (!filePath) return "File path is required";
|
|
1955
1708
|
const normalized = normalize(filePath);
|
|
@@ -1962,13 +1715,13 @@ function validateFilePath(filePath) {
|
|
|
1962
1715
|
return null;
|
|
1963
1716
|
}
|
|
1964
1717
|
function configPath(dir) {
|
|
1965
|
-
return
|
|
1718
|
+
return resolve5(dir, "home", ".config", "file-sharing.json");
|
|
1966
1719
|
}
|
|
1967
1720
|
function readFileSharingConfig(dir) {
|
|
1968
1721
|
const p = configPath(dir);
|
|
1969
|
-
if (!
|
|
1722
|
+
if (!existsSync5(p)) return {};
|
|
1970
1723
|
try {
|
|
1971
|
-
return JSON.parse(
|
|
1724
|
+
return JSON.parse(readFileSync4(p, "utf-8"));
|
|
1972
1725
|
} catch (err) {
|
|
1973
1726
|
console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
|
|
1974
1727
|
return {};
|
|
@@ -1976,8 +1729,8 @@ function readFileSharingConfig(dir) {
|
|
|
1976
1729
|
}
|
|
1977
1730
|
function writeFileSharingConfig(dir, config) {
|
|
1978
1731
|
const p = configPath(dir);
|
|
1979
|
-
|
|
1980
|
-
|
|
1732
|
+
mkdirSync3(resolve5(p, ".."), { recursive: true });
|
|
1733
|
+
writeFileSync3(p, `${JSON.stringify(config, null, 2)}
|
|
1981
1734
|
`);
|
|
1982
1735
|
}
|
|
1983
1736
|
function isTrustedSender(dir, sender) {
|
|
@@ -2000,7 +1753,7 @@ function removeTrust(dir, sender) {
|
|
|
2000
1753
|
writeFileSharingConfig(dir, config);
|
|
2001
1754
|
}
|
|
2002
1755
|
function pendingDir(receiver) {
|
|
2003
|
-
return
|
|
1756
|
+
return resolve5(stateDir(receiver), "pending-files");
|
|
2004
1757
|
}
|
|
2005
1758
|
function validateId(id) {
|
|
2006
1759
|
if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
@@ -2019,8 +1772,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2019
1772
|
throw new Error("Invalid sender name");
|
|
2020
1773
|
}
|
|
2021
1774
|
const id = generateId(sender);
|
|
2022
|
-
const dir =
|
|
2023
|
-
|
|
1775
|
+
const dir = resolve5(pendingDir(receiver), id);
|
|
1776
|
+
mkdirSync3(dir, { recursive: true });
|
|
2024
1777
|
const metadata = {
|
|
2025
1778
|
id,
|
|
2026
1779
|
sender,
|
|
@@ -2029,22 +1782,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2029
1782
|
size: content.length,
|
|
2030
1783
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2031
1784
|
};
|
|
2032
|
-
|
|
1785
|
+
writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
|
|
2033
1786
|
`);
|
|
2034
|
-
|
|
1787
|
+
writeFileSync3(resolve5(dir, "data"), content);
|
|
2035
1788
|
return { id };
|
|
2036
1789
|
}
|
|
2037
1790
|
function listPending(receiver) {
|
|
2038
1791
|
const dir = pendingDir(receiver);
|
|
2039
|
-
if (!
|
|
1792
|
+
if (!existsSync5(dir)) return [];
|
|
2040
1793
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2041
1794
|
const result = [];
|
|
2042
1795
|
for (const entry of entries) {
|
|
2043
1796
|
if (!entry.isDirectory()) continue;
|
|
2044
|
-
const metaPath =
|
|
2045
|
-
if (!
|
|
1797
|
+
const metaPath = resolve5(dir, entry.name, "metadata.json");
|
|
1798
|
+
if (!existsSync5(metaPath)) continue;
|
|
2046
1799
|
try {
|
|
2047
|
-
result.push(JSON.parse(
|
|
1800
|
+
result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
|
|
2048
1801
|
} catch (err) {
|
|
2049
1802
|
console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
|
|
2050
1803
|
}
|
|
@@ -2053,10 +1806,10 @@ function listPending(receiver) {
|
|
|
2053
1806
|
}
|
|
2054
1807
|
function getPending(receiver, id) {
|
|
2055
1808
|
validateId(id);
|
|
2056
|
-
const metaPath =
|
|
2057
|
-
if (!
|
|
1809
|
+
const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
|
|
1810
|
+
if (!existsSync5(metaPath)) return null;
|
|
2058
1811
|
try {
|
|
2059
|
-
return JSON.parse(
|
|
1812
|
+
return JSON.parse(readFileSync4(metaPath, "utf-8"));
|
|
2060
1813
|
} catch (err) {
|
|
2061
1814
|
console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
|
|
2062
1815
|
return null;
|
|
@@ -2071,27 +1824,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
|
|
|
2071
1824
|
if (sender.includes("/") || sender.includes("\\")) {
|
|
2072
1825
|
throw new Error("Invalid sender name");
|
|
2073
1826
|
}
|
|
2074
|
-
const destDir =
|
|
2075
|
-
|
|
2076
|
-
const destPath =
|
|
2077
|
-
|
|
1827
|
+
const destDir = resolve5(receiverDir, "home", inbox, sender);
|
|
1828
|
+
mkdirSync3(destDir, { recursive: true });
|
|
1829
|
+
const destPath = resolve5(destDir, basename(filename));
|
|
1830
|
+
writeFileSync3(destPath, content);
|
|
2078
1831
|
return join(inbox, sender, basename(filename));
|
|
2079
1832
|
}
|
|
2080
1833
|
function acceptPending(receiver, id, receiverDir) {
|
|
2081
1834
|
const meta = getPending(receiver, id);
|
|
2082
1835
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2083
|
-
const dataPath =
|
|
2084
|
-
const content =
|
|
1836
|
+
const dataPath = resolve5(pendingDir(receiver), id, "data");
|
|
1837
|
+
const content = readFileSync4(dataPath);
|
|
2085
1838
|
const config = readFileSharingConfig(receiverDir);
|
|
2086
1839
|
const inboxPath = config.inboxPath ?? "inbox";
|
|
2087
1840
|
const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
|
|
2088
|
-
|
|
1841
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2089
1842
|
return { sender: meta.sender, filename: meta.filename, destPath };
|
|
2090
1843
|
}
|
|
2091
1844
|
function rejectPending(receiver, id) {
|
|
2092
1845
|
const meta = getPending(receiver, id);
|
|
2093
1846
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2094
|
-
|
|
1847
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2095
1848
|
return { sender: meta.sender, filename: meta.filename };
|
|
2096
1849
|
}
|
|
2097
1850
|
function formatFileSize(bytes) {
|
|
@@ -2132,7 +1885,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2132
1885
|
const pathErr = validateFilePath(body.filePath);
|
|
2133
1886
|
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2134
1887
|
const senderDir = mindDir(senderName);
|
|
2135
|
-
const filePath =
|
|
1888
|
+
const filePath = resolve6(senderDir, "home", body.filePath);
|
|
2136
1889
|
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
2137
1890
|
const stat4 = statSync(filePath, { throwIfNoEntry: false });
|
|
2138
1891
|
if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
@@ -2146,7 +1899,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2146
1899
|
}
|
|
2147
1900
|
let content;
|
|
2148
1901
|
try {
|
|
2149
|
-
content =
|
|
1902
|
+
content = readFileSync5(filePath);
|
|
2150
1903
|
} catch {
|
|
2151
1904
|
return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2152
1905
|
}
|
|
@@ -2248,19 +2001,19 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2248
2001
|
var file_sharing_default = app6;
|
|
2249
2002
|
|
|
2250
2003
|
// src/web/api/files.ts
|
|
2251
|
-
import { existsSync as
|
|
2004
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2252
2005
|
import { readdir, readFile, realpath, stat } from "fs/promises";
|
|
2253
|
-
import { extname, resolve as
|
|
2006
|
+
import { extname as extname2, resolve as resolve7 } from "path";
|
|
2254
2007
|
import { Hono as Hono7 } from "hono";
|
|
2255
2008
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2256
|
-
var
|
|
2009
|
+
var AVATAR_MIME2 = {
|
|
2257
2010
|
".png": "image/png",
|
|
2258
2011
|
".jpg": "image/jpeg",
|
|
2259
2012
|
".jpeg": "image/jpeg",
|
|
2260
2013
|
".gif": "image/gif",
|
|
2261
2014
|
".webp": "image/webp"
|
|
2262
2015
|
};
|
|
2263
|
-
var
|
|
2016
|
+
var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
|
|
2264
2017
|
var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
2265
2018
|
const name = c.req.param("name");
|
|
2266
2019
|
const entry = findMind(name);
|
|
@@ -2268,11 +2021,11 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2268
2021
|
const dir = mindDir(name);
|
|
2269
2022
|
const config = readVoluteConfig(dir);
|
|
2270
2023
|
if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
|
|
2271
|
-
const ext =
|
|
2272
|
-
const mime =
|
|
2024
|
+
const ext = extname2(config.avatar).toLowerCase();
|
|
2025
|
+
const mime = AVATAR_MIME2[ext];
|
|
2273
2026
|
if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
|
|
2274
|
-
const homeDir =
|
|
2275
|
-
const avatarPath =
|
|
2027
|
+
const homeDir = resolve7(dir, "home");
|
|
2028
|
+
const avatarPath = resolve7(homeDir, config.avatar);
|
|
2276
2029
|
if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
|
|
2277
2030
|
let realAvatarPath;
|
|
2278
2031
|
try {
|
|
@@ -2287,7 +2040,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2287
2040
|
}
|
|
2288
2041
|
try {
|
|
2289
2042
|
const fileStat = await stat(realAvatarPath);
|
|
2290
|
-
if (fileStat.size >
|
|
2043
|
+
if (fileStat.size > MAX_AVATAR_SIZE2) return c.json({ error: "Avatar file too large" }, 400);
|
|
2291
2044
|
const body = await readFile(realAvatarPath);
|
|
2292
2045
|
return c.body(body, 200, {
|
|
2293
2046
|
"Content-Type": mime,
|
|
@@ -2301,8 +2054,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2301
2054
|
const entry = findMind(name);
|
|
2302
2055
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2303
2056
|
const dir = mindDir(name);
|
|
2304
|
-
const homeDir =
|
|
2305
|
-
if (!
|
|
2057
|
+
const homeDir = resolve7(dir, "home");
|
|
2058
|
+
if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2306
2059
|
const allFiles = await readdir(homeDir);
|
|
2307
2060
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2308
2061
|
return c.json(files);
|
|
@@ -2315,8 +2068,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2315
2068
|
const entry = findMind(name);
|
|
2316
2069
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2317
2070
|
const dir = mindDir(name);
|
|
2318
|
-
const filePath =
|
|
2319
|
-
if (!
|
|
2071
|
+
const filePath = resolve7(dir, "home", filename);
|
|
2072
|
+
if (!existsSync6(filePath)) {
|
|
2320
2073
|
return c.json({ error: "File not found" }, 404);
|
|
2321
2074
|
}
|
|
2322
2075
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2329,19 +2082,19 @@ import { Hono as Hono8 } from "hono";
|
|
|
2329
2082
|
|
|
2330
2083
|
// src/lib/identity.ts
|
|
2331
2084
|
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
2332
|
-
import { existsSync as
|
|
2333
|
-
import { resolve as
|
|
2085
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2086
|
+
import { resolve as resolve8 } from "path";
|
|
2334
2087
|
function generateIdentity(mindDir2) {
|
|
2335
|
-
const identityDir =
|
|
2336
|
-
|
|
2088
|
+
const identityDir = resolve8(mindDir2, ".mind/identity");
|
|
2089
|
+
mkdirSync4(identityDir, { recursive: true });
|
|
2337
2090
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
2338
2091
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2339
2092
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2340
2093
|
});
|
|
2341
|
-
const privatePath =
|
|
2342
|
-
const publicPath =
|
|
2343
|
-
|
|
2344
|
-
|
|
2094
|
+
const privatePath = resolve8(identityDir, "private.pem");
|
|
2095
|
+
const publicPath = resolve8(identityDir, "public.pem");
|
|
2096
|
+
writeFileSync4(privatePath, privateKey, { mode: 384 });
|
|
2097
|
+
writeFileSync4(publicPath, publicKey, { mode: 420 });
|
|
2345
2098
|
const config = readVoluteConfig(mindDir2) ?? {};
|
|
2346
2099
|
config.identity = {
|
|
2347
2100
|
privateKey: ".mind/identity/private.pem",
|
|
@@ -2354,17 +2107,17 @@ function getPrivateKey(mindDir2) {
|
|
|
2354
2107
|
const config = readVoluteConfig(mindDir2);
|
|
2355
2108
|
const relPath = config?.identity?.privateKey;
|
|
2356
2109
|
if (!relPath) return null;
|
|
2357
|
-
const fullPath =
|
|
2358
|
-
if (!
|
|
2359
|
-
return
|
|
2110
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2111
|
+
if (!existsSync7(fullPath)) return null;
|
|
2112
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2360
2113
|
}
|
|
2361
2114
|
function getPublicKey(mindDir2) {
|
|
2362
2115
|
const config = readVoluteConfig(mindDir2);
|
|
2363
2116
|
const relPath = config?.identity?.publicKey;
|
|
2364
2117
|
if (!relPath) return null;
|
|
2365
|
-
const fullPath =
|
|
2366
|
-
if (!
|
|
2367
|
-
return
|
|
2118
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2119
|
+
if (!existsSync7(fullPath)) return null;
|
|
2120
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2368
2121
|
}
|
|
2369
2122
|
function getFingerprint(publicKeyPem) {
|
|
2370
2123
|
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
@@ -2417,16 +2170,16 @@ var keys_default = app8;
|
|
|
2417
2170
|
|
|
2418
2171
|
// src/web/api/logs.ts
|
|
2419
2172
|
import { spawn } from "child_process";
|
|
2420
|
-
import { existsSync as
|
|
2421
|
-
import { resolve as
|
|
2173
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2174
|
+
import { resolve as resolve9 } from "path";
|
|
2422
2175
|
import { Hono as Hono9 } from "hono";
|
|
2423
2176
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
2424
2177
|
var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
2425
2178
|
const name = c.req.param("name");
|
|
2426
2179
|
const entry = findMind(name);
|
|
2427
2180
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2428
|
-
const logFile =
|
|
2429
|
-
if (!
|
|
2181
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2182
|
+
if (!existsSync8(logFile)) {
|
|
2430
2183
|
return c.json({ error: "No log file found" }, 404);
|
|
2431
2184
|
}
|
|
2432
2185
|
return streamSSE2(c, async (stream) => {
|
|
@@ -2444,17 +2197,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2444
2197
|
stream.onAbort(() => {
|
|
2445
2198
|
tail.kill();
|
|
2446
2199
|
});
|
|
2447
|
-
await new Promise((
|
|
2448
|
-
tail.on("exit",
|
|
2449
|
-
stream.onAbort(
|
|
2200
|
+
await new Promise((resolve19) => {
|
|
2201
|
+
tail.on("exit", resolve19);
|
|
2202
|
+
stream.onAbort(resolve19);
|
|
2450
2203
|
});
|
|
2451
2204
|
});
|
|
2452
2205
|
}).get("/:name/logs/tail", async (c) => {
|
|
2453
2206
|
const name = c.req.param("name");
|
|
2454
2207
|
const entry = findMind(name);
|
|
2455
2208
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2456
|
-
const logFile =
|
|
2457
|
-
if (!
|
|
2209
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2210
|
+
if (!existsSync8(logFile)) {
|
|
2458
2211
|
return c.json({ error: "No log file found" }, 404);
|
|
2459
2212
|
}
|
|
2460
2213
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -2464,8 +2217,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2464
2217
|
tail.stdout.on("data", (data) => {
|
|
2465
2218
|
output += data.toString();
|
|
2466
2219
|
});
|
|
2467
|
-
await new Promise((
|
|
2468
|
-
tail.on("exit",
|
|
2220
|
+
await new Promise((resolve19) => {
|
|
2221
|
+
tail.on("exit", resolve19);
|
|
2469
2222
|
});
|
|
2470
2223
|
return c.text(output);
|
|
2471
2224
|
});
|
|
@@ -2555,33 +2308,33 @@ var mind_skills_default = app10;
|
|
|
2555
2308
|
// src/web/api/minds.ts
|
|
2556
2309
|
import {
|
|
2557
2310
|
cpSync,
|
|
2558
|
-
existsSync as
|
|
2559
|
-
mkdirSync as
|
|
2311
|
+
existsSync as existsSync10,
|
|
2312
|
+
mkdirSync as mkdirSync6,
|
|
2560
2313
|
readdirSync as readdirSync4,
|
|
2561
|
-
readFileSync as
|
|
2562
|
-
rmSync as
|
|
2563
|
-
writeFileSync as
|
|
2314
|
+
readFileSync as readFileSync9,
|
|
2315
|
+
rmSync as rmSync4,
|
|
2316
|
+
writeFileSync as writeFileSync7
|
|
2564
2317
|
} from "fs";
|
|
2565
|
-
import { resolve as
|
|
2318
|
+
import { resolve as resolve12 } from "path";
|
|
2566
2319
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2567
|
-
import { and
|
|
2320
|
+
import { and, desc as desc2, eq as eq2, sql } from "drizzle-orm";
|
|
2568
2321
|
import { Hono as Hono11 } from "hono";
|
|
2569
2322
|
import { z as z3 } from "zod";
|
|
2570
2323
|
|
|
2571
2324
|
// src/lib/consolidate.ts
|
|
2572
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2573
|
-
import { resolve as
|
|
2325
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2326
|
+
import { resolve as resolve10 } from "path";
|
|
2574
2327
|
async function consolidateMemory(mindDir2) {
|
|
2575
|
-
const soulPath =
|
|
2576
|
-
const memoryPath =
|
|
2577
|
-
const memoryDir =
|
|
2578
|
-
const soul =
|
|
2328
|
+
const soulPath = resolve10(mindDir2, "home/SOUL.md");
|
|
2329
|
+
const memoryPath = resolve10(mindDir2, "home/MEMORY.md");
|
|
2330
|
+
const memoryDir = resolve10(mindDir2, "home/memory");
|
|
2331
|
+
const soul = readFileSync7(soulPath, "utf-8");
|
|
2579
2332
|
const logs = [];
|
|
2580
2333
|
try {
|
|
2581
2334
|
const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
2582
2335
|
for (const filename of files) {
|
|
2583
2336
|
const date = filename.replace(".md", "");
|
|
2584
|
-
const content2 =
|
|
2337
|
+
const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
|
|
2585
2338
|
if (content2) {
|
|
2586
2339
|
logs.push(`### ${date}
|
|
2587
2340
|
|
|
@@ -2631,7 +2384,7 @@ ${content2}`);
|
|
|
2631
2384
|
const data = await res.json();
|
|
2632
2385
|
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
2633
2386
|
if (content) {
|
|
2634
|
-
|
|
2387
|
+
writeFileSync5(memoryPath, `${content}
|
|
2635
2388
|
`);
|
|
2636
2389
|
console.log("MEMORY.md created successfully.");
|
|
2637
2390
|
} else {
|
|
@@ -2640,28 +2393,28 @@ ${content2}`);
|
|
|
2640
2393
|
}
|
|
2641
2394
|
|
|
2642
2395
|
// src/lib/convert-session.ts
|
|
2643
|
-
import { randomUUID
|
|
2644
|
-
import { mkdirSync as
|
|
2396
|
+
import { randomUUID } from "crypto";
|
|
2397
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
2645
2398
|
import { homedir } from "os";
|
|
2646
|
-
import { resolve as
|
|
2399
|
+
import { resolve as resolve11 } from "path";
|
|
2647
2400
|
function convertSession(opts) {
|
|
2648
|
-
const lines =
|
|
2649
|
-
const sessionId =
|
|
2401
|
+
const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
|
|
2402
|
+
const sessionId = randomUUID();
|
|
2650
2403
|
const idMap = /* @__PURE__ */ new Map();
|
|
2651
|
-
const
|
|
2404
|
+
const messages = [];
|
|
2652
2405
|
for (const line of lines) {
|
|
2653
2406
|
const event = JSON.parse(line);
|
|
2654
2407
|
if (event.type === "message" && event.message) {
|
|
2655
|
-
|
|
2408
|
+
messages.push(event);
|
|
2656
2409
|
}
|
|
2657
2410
|
}
|
|
2658
2411
|
const sdkEvents = [];
|
|
2659
2412
|
let lastSdkUuid = null;
|
|
2660
|
-
for (let i = 0; i <
|
|
2661
|
-
const event =
|
|
2413
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2414
|
+
const event = messages[i];
|
|
2662
2415
|
const msg = event.message;
|
|
2663
2416
|
if (msg.role === "user") {
|
|
2664
|
-
const uuid =
|
|
2417
|
+
const uuid = randomUUID();
|
|
2665
2418
|
idMap.set(event.id, uuid);
|
|
2666
2419
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2667
2420
|
const sdkEvent = {
|
|
@@ -2685,7 +2438,7 @@ function convertSession(opts) {
|
|
|
2685
2438
|
} else if (msg.role === "assistant") {
|
|
2686
2439
|
const content = convertAssistantContent(msg.content);
|
|
2687
2440
|
if (content.length === 0) continue;
|
|
2688
|
-
const uuid =
|
|
2441
|
+
const uuid = randomUUID();
|
|
2689
2442
|
idMap.set(event.id, uuid);
|
|
2690
2443
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2691
2444
|
const stopReason = mapStopReason(msg.stopReason);
|
|
@@ -2700,12 +2453,12 @@ function convertSession(opts) {
|
|
|
2700
2453
|
isSidechain: false,
|
|
2701
2454
|
userType: "external",
|
|
2702
2455
|
type: "assistant",
|
|
2703
|
-
requestId: `req_imported_${
|
|
2456
|
+
requestId: `req_imported_${randomUUID()}`,
|
|
2704
2457
|
message: {
|
|
2705
2458
|
role: "assistant",
|
|
2706
2459
|
content,
|
|
2707
2460
|
type: "message",
|
|
2708
|
-
id: `msg_imported_${
|
|
2461
|
+
id: `msg_imported_${randomUUID()}`,
|
|
2709
2462
|
model: mapModel(msg.model),
|
|
2710
2463
|
stop_reason: stopReason,
|
|
2711
2464
|
stop_sequence: null,
|
|
@@ -2719,8 +2472,8 @@ function convertSession(opts) {
|
|
|
2719
2472
|
let lastToolResultId = event.id;
|
|
2720
2473
|
let lastTimestamp = event.timestamp;
|
|
2721
2474
|
let j = i;
|
|
2722
|
-
while (j <
|
|
2723
|
-
const tr =
|
|
2475
|
+
while (j < messages.length && messages[j].message.role === "toolResult") {
|
|
2476
|
+
const tr = messages[j];
|
|
2724
2477
|
const trMsg = tr.message;
|
|
2725
2478
|
lastToolResultId = tr.id;
|
|
2726
2479
|
lastTimestamp = tr.timestamp;
|
|
@@ -2733,7 +2486,7 @@ function convertSession(opts) {
|
|
|
2733
2486
|
j++;
|
|
2734
2487
|
}
|
|
2735
2488
|
i = j - 1;
|
|
2736
|
-
const uuid =
|
|
2489
|
+
const uuid = randomUUID();
|
|
2737
2490
|
idMap.set(lastToolResultId, uuid);
|
|
2738
2491
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
|
|
2739
2492
|
const sdkEvent = {
|
|
@@ -2759,10 +2512,10 @@ function convertSession(opts) {
|
|
|
2759
2512
|
}
|
|
2760
2513
|
}
|
|
2761
2514
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
2762
|
-
const sdkDir =
|
|
2763
|
-
|
|
2764
|
-
const sdkPath =
|
|
2765
|
-
|
|
2515
|
+
const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
|
|
2516
|
+
mkdirSync5(sdkDir, { recursive: true });
|
|
2517
|
+
const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
|
|
2518
|
+
writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
|
|
2766
2519
|
`);
|
|
2767
2520
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
2768
2521
|
return sessionId;
|
|
@@ -2814,7 +2567,7 @@ function convertAssistantContent(content) {
|
|
|
2814
2567
|
}
|
|
2815
2568
|
|
|
2816
2569
|
// src/lib/variant-cleanup.ts
|
|
2817
|
-
import { existsSync as
|
|
2570
|
+
import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
|
|
2818
2571
|
async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
|
|
2819
2572
|
if (opts?.stop) {
|
|
2820
2573
|
try {
|
|
@@ -2822,11 +2575,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
|
|
|
2822
2575
|
} catch {
|
|
2823
2576
|
}
|
|
2824
2577
|
}
|
|
2825
|
-
if (
|
|
2578
|
+
if (existsSync9(variantPath)) {
|
|
2826
2579
|
try {
|
|
2827
2580
|
await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
|
|
2828
2581
|
} catch {
|
|
2829
|
-
|
|
2582
|
+
rmSync3(variantPath, { recursive: true, force: true });
|
|
2830
2583
|
try {
|
|
2831
2584
|
await gitExec(["worktree", "prune"], { cwd: projectRoot });
|
|
2832
2585
|
} catch {
|
|
@@ -2853,7 +2606,7 @@ async function getMindStatus(name, port) {
|
|
|
2853
2606
|
const manager = getMindManager();
|
|
2854
2607
|
let status = "stopped";
|
|
2855
2608
|
try {
|
|
2856
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
2609
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
2857
2610
|
if (getSleepManagerIfReady()?.isSleeping(name)) {
|
|
2858
2611
|
status = "sleeping";
|
|
2859
2612
|
}
|
|
@@ -2911,7 +2664,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
|
|
|
2911
2664
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
2912
2665
|
}
|
|
2913
2666
|
async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
2914
|
-
const tempWorktree =
|
|
2667
|
+
const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
|
|
2915
2668
|
let branchExists = false;
|
|
2916
2669
|
try {
|
|
2917
2670
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -2922,8 +2675,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2922
2675
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2923
2676
|
} catch {
|
|
2924
2677
|
}
|
|
2925
|
-
if (
|
|
2926
|
-
|
|
2678
|
+
if (existsSync10(tempWorktree)) {
|
|
2679
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2927
2680
|
}
|
|
2928
2681
|
const templatesRoot = findTemplatesRoot();
|
|
2929
2682
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -2943,9 +2696,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2943
2696
|
});
|
|
2944
2697
|
}
|
|
2945
2698
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
2946
|
-
const initDir =
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
2699
|
+
const initDir = resolve12(tempWorktree, ".init");
|
|
2700
|
+
if (existsSync10(initDir)) {
|
|
2701
|
+
rmSync4(initDir, { recursive: true, force: true });
|
|
2949
2702
|
}
|
|
2950
2703
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
2951
2704
|
try {
|
|
@@ -2958,10 +2711,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2958
2711
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2959
2712
|
} catch {
|
|
2960
2713
|
}
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
2714
|
+
if (existsSync10(tempWorktree)) {
|
|
2715
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2963
2716
|
}
|
|
2964
|
-
|
|
2717
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
2965
2718
|
}
|
|
2966
2719
|
}
|
|
2967
2720
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -2984,14 +2737,14 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
2984
2737
|
async function npmInstallAsMind(cwd, mindName) {
|
|
2985
2738
|
if (isIsolationEnabled()) {
|
|
2986
2739
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
2987
|
-
await exec(cmd, args, { cwd, env: { ...process.env, HOME:
|
|
2740
|
+
await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve12(cwd, "home") } });
|
|
2988
2741
|
} else {
|
|
2989
2742
|
await exec("npm", ["install"], { cwd });
|
|
2990
2743
|
}
|
|
2991
2744
|
}
|
|
2992
2745
|
async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
2993
|
-
const extractedMindDir =
|
|
2994
|
-
if (!
|
|
2746
|
+
const extractedMindDir = resolve12(tempDir, "mind");
|
|
2747
|
+
if (!existsSync10(extractedMindDir)) {
|
|
2995
2748
|
return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
|
|
2996
2749
|
}
|
|
2997
2750
|
if (!manifest?.includes || !manifest.name || !manifest.template) {
|
|
@@ -3009,21 +2762,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3009
2762
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3010
2763
|
ensureVoluteHome();
|
|
3011
2764
|
const dest = mindDir(name);
|
|
3012
|
-
if (
|
|
2765
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3013
2766
|
try {
|
|
3014
2767
|
cpSync(extractedMindDir, dest, { recursive: true });
|
|
3015
2768
|
if (!manifest.includes.identity) {
|
|
3016
2769
|
generateIdentity(dest);
|
|
3017
2770
|
}
|
|
3018
2771
|
const state = stateDir(name);
|
|
3019
|
-
|
|
3020
|
-
const channelsJson =
|
|
3021
|
-
if (
|
|
3022
|
-
cpSync(channelsJson,
|
|
2772
|
+
mkdirSync6(state, { recursive: true });
|
|
2773
|
+
const channelsJson = resolve12(tempDir, "state/channels.json");
|
|
2774
|
+
if (existsSync10(channelsJson)) {
|
|
2775
|
+
cpSync(channelsJson, resolve12(state, "channels.json"));
|
|
3023
2776
|
}
|
|
3024
|
-
const envJson =
|
|
3025
|
-
if (
|
|
3026
|
-
cpSync(envJson,
|
|
2777
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2778
|
+
if (existsSync10(envJson)) {
|
|
2779
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3027
2780
|
}
|
|
3028
2781
|
const port = nextPort();
|
|
3029
2782
|
addMind(name, port, manifest.stage, manifest.template);
|
|
@@ -3032,36 +2785,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3032
2785
|
} catch (err) {
|
|
3033
2786
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3034
2787
|
}
|
|
3035
|
-
const homeDir =
|
|
2788
|
+
const homeDir = resolve12(dest, "home");
|
|
3036
2789
|
ensureVoluteGroup();
|
|
3037
2790
|
createMindUser(name, homeDir);
|
|
3038
2791
|
chownMindDir(dest, name);
|
|
3039
2792
|
await npmInstallAsMind(dest, name);
|
|
3040
2793
|
await importHistoryFromArchive(name, tempDir);
|
|
3041
2794
|
importSessionsFromArchive(dest, tempDir);
|
|
3042
|
-
if (!
|
|
2795
|
+
if (!existsSync10(resolve12(dest, ".git"))) {
|
|
3043
2796
|
try {
|
|
3044
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
2797
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3045
2798
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3046
2799
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3047
2800
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3048
2801
|
await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
|
|
3049
2802
|
} catch (err) {
|
|
3050
2803
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3051
|
-
|
|
2804
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3052
2805
|
}
|
|
3053
2806
|
}
|
|
3054
2807
|
chownMindDir(dest, name);
|
|
3055
|
-
|
|
2808
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3056
2809
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3057
2810
|
} catch (err) {
|
|
3058
|
-
if (
|
|
2811
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3059
2812
|
try {
|
|
3060
2813
|
removeMind(name);
|
|
3061
2814
|
} catch (cleanupErr) {
|
|
3062
2815
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3063
2816
|
}
|
|
3064
|
-
|
|
2817
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3065
2818
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3066
2819
|
}
|
|
3067
2820
|
}
|
|
@@ -3072,7 +2825,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3072
2825
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3073
2826
|
ensureVoluteHome();
|
|
3074
2827
|
const dest = mindDir(name);
|
|
3075
|
-
if (
|
|
2828
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3076
2829
|
const templatesRoot = findTemplatesRoot();
|
|
3077
2830
|
const { composedDir, manifest: templateManifest } = composeTemplate(
|
|
3078
2831
|
templatesRoot,
|
|
@@ -3081,40 +2834,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3081
2834
|
try {
|
|
3082
2835
|
copyTemplateToDir(composedDir, dest, name, templateManifest);
|
|
3083
2836
|
applyInitFiles(dest);
|
|
3084
|
-
const extractedHome =
|
|
3085
|
-
if (
|
|
3086
|
-
cpSync(extractedHome,
|
|
2837
|
+
const extractedHome = resolve12(extractedMindDir, "home");
|
|
2838
|
+
if (existsSync10(extractedHome)) {
|
|
2839
|
+
cpSync(extractedHome, resolve12(dest, "home"), { recursive: true });
|
|
3087
2840
|
}
|
|
3088
|
-
const extractedMindInternal =
|
|
3089
|
-
if (
|
|
3090
|
-
cpSync(extractedMindInternal,
|
|
2841
|
+
const extractedMindInternal = resolve12(extractedMindDir, ".mind");
|
|
2842
|
+
if (existsSync10(extractedMindInternal)) {
|
|
2843
|
+
cpSync(extractedMindInternal, resolve12(dest, ".mind"), { recursive: true });
|
|
3091
2844
|
}
|
|
3092
|
-
const identityDir =
|
|
2845
|
+
const identityDir = resolve12(dest, ".mind/identity");
|
|
3093
2846
|
let publicKeyPem;
|
|
3094
|
-
if (!manifest.includes.identity || !
|
|
2847
|
+
if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
|
|
3095
2848
|
({ publicKeyPem } = generateIdentity(dest));
|
|
3096
2849
|
} else {
|
|
3097
|
-
publicKeyPem =
|
|
2850
|
+
publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
|
|
3098
2851
|
}
|
|
3099
|
-
const promptsPath =
|
|
3100
|
-
if (!
|
|
2852
|
+
const promptsPath = resolve12(dest, "home/.config/prompts.json");
|
|
2853
|
+
if (!existsSync10(promptsPath)) {
|
|
3101
2854
|
const mindPrompts = await getMindPromptDefaults();
|
|
3102
|
-
|
|
2855
|
+
writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
|
|
3103
2856
|
`);
|
|
3104
2857
|
}
|
|
3105
2858
|
const state = stateDir(name);
|
|
3106
|
-
|
|
3107
|
-
const channelsJson =
|
|
3108
|
-
if (
|
|
3109
|
-
cpSync(channelsJson,
|
|
2859
|
+
mkdirSync6(state, { recursive: true });
|
|
2860
|
+
const channelsJson = resolve12(tempDir, "state/channels.json");
|
|
2861
|
+
if (existsSync10(channelsJson)) {
|
|
2862
|
+
cpSync(channelsJson, resolve12(state, "channels.json"));
|
|
3110
2863
|
}
|
|
3111
|
-
const envJson =
|
|
3112
|
-
if (
|
|
3113
|
-
cpSync(envJson,
|
|
2864
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2865
|
+
if (existsSync10(envJson)) {
|
|
2866
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3114
2867
|
}
|
|
3115
2868
|
const port = nextPort();
|
|
3116
2869
|
addMind(name, port, manifest.stage, manifest.template);
|
|
3117
|
-
const homeDir =
|
|
2870
|
+
const homeDir = resolve12(dest, "home");
|
|
3118
2871
|
ensureVoluteGroup();
|
|
3119
2872
|
createMindUser(name, homeDir);
|
|
3120
2873
|
chownMindDir(dest, name);
|
|
@@ -3127,7 +2880,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3127
2880
|
await initTemplateBranch(dest, composedDir, templateManifest, name, env);
|
|
3128
2881
|
} catch (err) {
|
|
3129
2882
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3130
|
-
|
|
2883
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3131
2884
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3132
2885
|
}
|
|
3133
2886
|
try {
|
|
@@ -3151,7 +2904,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3151
2904
|
publishPublicKey(name, publicKeyPem).catch(
|
|
3152
2905
|
(err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
|
|
3153
2906
|
);
|
|
3154
|
-
|
|
2907
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3155
2908
|
return c.json({
|
|
3156
2909
|
ok: true,
|
|
3157
2910
|
name,
|
|
@@ -3162,24 +2915,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3162
2915
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3163
2916
|
});
|
|
3164
2917
|
} catch (err) {
|
|
3165
|
-
if (
|
|
2918
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3166
2919
|
try {
|
|
3167
2920
|
removeMind(name);
|
|
3168
2921
|
} catch (cleanupErr) {
|
|
3169
2922
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3170
2923
|
}
|
|
3171
|
-
|
|
2924
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3172
2925
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3173
2926
|
} finally {
|
|
3174
|
-
|
|
2927
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3175
2928
|
}
|
|
3176
2929
|
}
|
|
3177
2930
|
async function importHistoryFromArchive(name, tempDir) {
|
|
3178
|
-
const historyJsonl =
|
|
3179
|
-
if (!
|
|
2931
|
+
const historyJsonl = resolve12(tempDir, "history.jsonl");
|
|
2932
|
+
if (!existsSync10(historyJsonl)) return;
|
|
3180
2933
|
try {
|
|
3181
2934
|
const db = await getDb();
|
|
3182
|
-
const lines =
|
|
2935
|
+
const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
|
|
3183
2936
|
let imported = 0;
|
|
3184
2937
|
let failed = 0;
|
|
3185
2938
|
for (const line of lines) {
|
|
@@ -3215,13 +2968,13 @@ async function importHistoryFromArchive(name, tempDir) {
|
|
|
3215
2968
|
}
|
|
3216
2969
|
}
|
|
3217
2970
|
function importSessionsFromArchive(dest, tempDir) {
|
|
3218
|
-
const sessionsDir =
|
|
3219
|
-
if (!
|
|
2971
|
+
const sessionsDir = resolve12(tempDir, "sessions");
|
|
2972
|
+
if (!existsSync10(sessionsDir)) return;
|
|
3220
2973
|
try {
|
|
3221
|
-
const destSessions =
|
|
3222
|
-
|
|
2974
|
+
const destSessions = resolve12(dest, ".mind/sessions");
|
|
2975
|
+
mkdirSync6(destSessions, { recursive: true });
|
|
3223
2976
|
for (const file of readdirSync4(sessionsDir)) {
|
|
3224
|
-
cpSync(
|
|
2977
|
+
cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
|
|
3225
2978
|
}
|
|
3226
2979
|
} catch (err) {
|
|
3227
2980
|
logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
|
|
@@ -3244,7 +2997,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3244
2997
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3245
2998
|
ensureVoluteHome();
|
|
3246
2999
|
const dest = mindDir(name);
|
|
3247
|
-
if (
|
|
3000
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3248
3001
|
const templatesRoot = findTemplatesRoot();
|
|
3249
3002
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3250
3003
|
try {
|
|
@@ -3258,15 +3011,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3258
3011
|
writeVoluteConfig(dest, seedConfig);
|
|
3259
3012
|
}
|
|
3260
3013
|
if (body.model) {
|
|
3261
|
-
const configPath2 =
|
|
3262
|
-
const existing =
|
|
3014
|
+
const configPath2 = resolve12(dest, "home/.config/config.json");
|
|
3015
|
+
const existing = existsSync10(configPath2) ? JSON.parse(readFileSync9(configPath2, "utf-8")) : {};
|
|
3263
3016
|
existing.model = body.model;
|
|
3264
|
-
|
|
3017
|
+
writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
|
|
3265
3018
|
`);
|
|
3266
3019
|
}
|
|
3267
3020
|
const mindPrompts = await getMindPromptDefaults();
|
|
3268
|
-
|
|
3269
|
-
|
|
3021
|
+
writeFileSync7(
|
|
3022
|
+
resolve12(dest, "home/.config/prompts.json"),
|
|
3270
3023
|
`${JSON.stringify(mindPrompts, null, 2)}
|
|
3271
3024
|
`
|
|
3272
3025
|
);
|
|
@@ -3277,7 +3030,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3277
3030
|
} catch (err) {
|
|
3278
3031
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3279
3032
|
}
|
|
3280
|
-
const homeDir =
|
|
3033
|
+
const homeDir = resolve12(dest, "home");
|
|
3281
3034
|
ensureVoluteGroup();
|
|
3282
3035
|
createMindUser(name, homeDir);
|
|
3283
3036
|
chownMindDir(dest, name);
|
|
@@ -3290,7 +3043,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3290
3043
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
3291
3044
|
} catch (err) {
|
|
3292
3045
|
logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
|
|
3293
|
-
|
|
3046
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3294
3047
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3295
3048
|
}
|
|
3296
3049
|
try {
|
|
@@ -3305,7 +3058,7 @@ The human who planted you described you as: "${body.description}"
|
|
|
3305
3058
|
` : "";
|
|
3306
3059
|
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
3307
3060
|
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
3308
|
-
|
|
3061
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
|
|
3309
3062
|
}
|
|
3310
3063
|
const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
|
|
3311
3064
|
const skillWarnings = [];
|
|
@@ -3320,11 +3073,11 @@ The human who planted you described you as: "${body.description}"
|
|
|
3320
3073
|
if (body.stage !== "seed") {
|
|
3321
3074
|
const customSoul = await getPromptIfCustom("default_soul");
|
|
3322
3075
|
if (customSoul) {
|
|
3323
|
-
|
|
3076
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3324
3077
|
}
|
|
3325
3078
|
const customMemory = await getPromptIfCustom("default_memory");
|
|
3326
3079
|
if (customMemory) {
|
|
3327
|
-
|
|
3080
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
|
|
3328
3081
|
}
|
|
3329
3082
|
}
|
|
3330
3083
|
publishPublicKey(name, publicKeyPem).catch(
|
|
@@ -3351,14 +3104,14 @@ The human who planted you described you as: "${body.description}"
|
|
|
3351
3104
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3352
3105
|
});
|
|
3353
3106
|
} catch (err) {
|
|
3354
|
-
if (
|
|
3107
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3355
3108
|
try {
|
|
3356
3109
|
removeMind(name);
|
|
3357
3110
|
} catch {
|
|
3358
3111
|
}
|
|
3359
3112
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
3360
3113
|
} finally {
|
|
3361
|
-
|
|
3114
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3362
3115
|
}
|
|
3363
3116
|
}).post("/import", requireAdmin, async (c) => {
|
|
3364
3117
|
let body;
|
|
@@ -3371,13 +3124,13 @@ The human who planted you described you as: "${body.description}"
|
|
|
3371
3124
|
return importFromArchive(c, body.archivePath, body.name, body.manifest);
|
|
3372
3125
|
}
|
|
3373
3126
|
const wsDir = body.workspacePath;
|
|
3374
|
-
if (!wsDir || !
|
|
3127
|
+
if (!wsDir || !existsSync10(resolve12(wsDir, "SOUL.md")) || !existsSync10(resolve12(wsDir, "IDENTITY.md"))) {
|
|
3375
3128
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
3376
3129
|
}
|
|
3377
|
-
const soul =
|
|
3378
|
-
const identity =
|
|
3379
|
-
const userPath =
|
|
3380
|
-
const user =
|
|
3130
|
+
const soul = readFileSync9(resolve12(wsDir, "SOUL.md"), "utf-8");
|
|
3131
|
+
const identity = readFileSync9(resolve12(wsDir, "IDENTITY.md"), "utf-8");
|
|
3132
|
+
const userPath = resolve12(wsDir, "USER.md");
|
|
3133
|
+
const user = existsSync10(userPath) ? readFileSync9(userPath, "utf-8") : "";
|
|
3381
3134
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
3382
3135
|
const template = body.template ?? "claude";
|
|
3383
3136
|
const nameErr = validateMindName(name);
|
|
@@ -3397,33 +3150,33 @@ ${user.trimEnd()}
|
|
|
3397
3150
|
` : "";
|
|
3398
3151
|
ensureVoluteHome();
|
|
3399
3152
|
const dest = mindDir(name);
|
|
3400
|
-
if (
|
|
3153
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3401
3154
|
const templatesRoot = findTemplatesRoot();
|
|
3402
3155
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3403
3156
|
try {
|
|
3404
3157
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
3405
3158
|
applyInitFiles(dest);
|
|
3406
3159
|
const { publicKeyPem: importPublicKey } = generateIdentity(dest);
|
|
3407
|
-
|
|
3408
|
-
const wsMemoryPath =
|
|
3409
|
-
const hasMemory =
|
|
3160
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
|
|
3161
|
+
const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
|
|
3162
|
+
const hasMemory = existsSync10(wsMemoryPath);
|
|
3410
3163
|
if (hasMemory) {
|
|
3411
|
-
const existingMemory =
|
|
3412
|
-
|
|
3413
|
-
|
|
3164
|
+
const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
|
|
3165
|
+
writeFileSync7(
|
|
3166
|
+
resolve12(dest, "home/MEMORY.md"),
|
|
3414
3167
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
3415
3168
|
);
|
|
3416
3169
|
} else if (user) {
|
|
3417
|
-
|
|
3170
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
3418
3171
|
`);
|
|
3419
3172
|
}
|
|
3420
|
-
const wsMemoryDir =
|
|
3173
|
+
const wsMemoryDir = resolve12(wsDir, "memory");
|
|
3421
3174
|
let dailyLogCount = 0;
|
|
3422
|
-
if (
|
|
3423
|
-
const destMemoryDir =
|
|
3175
|
+
if (existsSync10(wsMemoryDir)) {
|
|
3176
|
+
const destMemoryDir = resolve12(dest, "home/memory");
|
|
3424
3177
|
const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
3425
3178
|
for (const file of files) {
|
|
3426
|
-
cpSync(
|
|
3179
|
+
cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
|
|
3427
3180
|
}
|
|
3428
3181
|
dailyLogCount = files.length;
|
|
3429
3182
|
}
|
|
@@ -3434,7 +3187,7 @@ ${user.trimEnd()}
|
|
|
3434
3187
|
} catch (err) {
|
|
3435
3188
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3436
3189
|
}
|
|
3437
|
-
const homeDir =
|
|
3190
|
+
const homeDir = resolve12(dest, "home");
|
|
3438
3191
|
ensureVoluteGroup();
|
|
3439
3192
|
createMindUser(name, homeDir);
|
|
3440
3193
|
chownMindDir(dest, name);
|
|
@@ -3442,20 +3195,20 @@ ${user.trimEnd()}
|
|
|
3442
3195
|
if (!hasMemory && dailyLogCount > 0) {
|
|
3443
3196
|
await consolidateMemory(dest);
|
|
3444
3197
|
}
|
|
3445
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3198
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3446
3199
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3447
3200
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3448
3201
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3449
3202
|
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
|
|
3450
|
-
const sessionFile = body.sessionPath ?
|
|
3451
|
-
if (sessionFile &&
|
|
3203
|
+
const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
|
|
3204
|
+
if (sessionFile && existsSync10(sessionFile)) {
|
|
3452
3205
|
if (template === "pi") {
|
|
3453
3206
|
importPiSession(sessionFile, dest);
|
|
3454
3207
|
} else if (template === "claude") {
|
|
3455
3208
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
3456
|
-
const mindRuntimeDir =
|
|
3457
|
-
|
|
3458
|
-
|
|
3209
|
+
const mindRuntimeDir = resolve12(dest, ".mind");
|
|
3210
|
+
mkdirSync6(mindRuntimeDir, { recursive: true });
|
|
3211
|
+
writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
|
|
3459
3212
|
}
|
|
3460
3213
|
}
|
|
3461
3214
|
importOpenClawConnectors(name, dest);
|
|
@@ -3470,14 +3223,14 @@ ${user.trimEnd()}
|
|
|
3470
3223
|
);
|
|
3471
3224
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3472
3225
|
} catch (err) {
|
|
3473
|
-
if (
|
|
3226
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3474
3227
|
try {
|
|
3475
3228
|
removeMind(name);
|
|
3476
3229
|
} catch {
|
|
3477
3230
|
}
|
|
3478
3231
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3479
3232
|
} finally {
|
|
3480
|
-
|
|
3233
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3481
3234
|
}
|
|
3482
3235
|
}).get("/", async (c) => {
|
|
3483
3236
|
const entries = readRegistry();
|
|
@@ -3486,7 +3239,7 @@ ${user.trimEnd()}
|
|
|
3486
3239
|
const db = await getDb();
|
|
3487
3240
|
const lastActiveRows = await db.select({
|
|
3488
3241
|
mind: mindHistory.mind,
|
|
3489
|
-
lastActiveAt:
|
|
3242
|
+
lastActiveAt: sql`MAX(${mindHistory.created_at})`
|
|
3490
3243
|
}).from(mindHistory).groupBy(mindHistory.mind);
|
|
3491
3244
|
lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
|
|
3492
3245
|
} catch {
|
|
@@ -3494,7 +3247,7 @@ ${user.trimEnd()}
|
|
|
3494
3247
|
const minds = await Promise.all(
|
|
3495
3248
|
entries.map(async (entry) => {
|
|
3496
3249
|
const mindStatus = await getMindStatus(entry.name, entry.port);
|
|
3497
|
-
const hasPages =
|
|
3250
|
+
const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "pages"));
|
|
3498
3251
|
return {
|
|
3499
3252
|
...entry,
|
|
3500
3253
|
...mindStatus,
|
|
@@ -3512,7 +3265,7 @@ ${user.trimEnd()}
|
|
|
3512
3265
|
const name = c.req.param("name");
|
|
3513
3266
|
const entry = findMind(name);
|
|
3514
3267
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3515
|
-
if (!
|
|
3268
|
+
if (!existsSync10(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
|
|
3516
3269
|
const mindStatus = await getMindStatus(name, entry.port);
|
|
3517
3270
|
const variants = readVariants(name);
|
|
3518
3271
|
const manager = getMindManager();
|
|
@@ -3527,7 +3280,7 @@ ${user.trimEnd()}
|
|
|
3527
3280
|
return { name: v.name, port: v.port, status: variantStatus };
|
|
3528
3281
|
})
|
|
3529
3282
|
);
|
|
3530
|
-
const hasPages =
|
|
3283
|
+
const hasPages = existsSync10(resolve12(mindDir(name), "home", "pages"));
|
|
3531
3284
|
return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
|
|
3532
3285
|
}).post("/:name/start", requireAdmin, async (c) => {
|
|
3533
3286
|
const name = c.req.param("name");
|
|
@@ -3541,7 +3294,7 @@ ${user.trimEnd()}
|
|
|
3541
3294
|
targetPort = variant.port;
|
|
3542
3295
|
} else {
|
|
3543
3296
|
const dir = mindDir(baseName);
|
|
3544
|
-
if (!
|
|
3297
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3545
3298
|
}
|
|
3546
3299
|
if (getMindManager().isRunning(name)) {
|
|
3547
3300
|
return c.json({ error: "Mind already running" }, 409);
|
|
@@ -3564,7 +3317,7 @@ ${user.trimEnd()}
|
|
|
3564
3317
|
targetPort = variant.port;
|
|
3565
3318
|
} else {
|
|
3566
3319
|
const dir = mindDir(baseName);
|
|
3567
|
-
if (!
|
|
3320
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3568
3321
|
}
|
|
3569
3322
|
let context;
|
|
3570
3323
|
const contentType = c.req.header("content-type");
|
|
@@ -3591,7 +3344,7 @@ ${user.trimEnd()}
|
|
|
3591
3344
|
const variant = findVariant(baseName, mergeVariantName);
|
|
3592
3345
|
if (variant) {
|
|
3593
3346
|
const projectRoot = mindDir(baseName);
|
|
3594
|
-
if (
|
|
3347
|
+
if (existsSync10(variant.path)) {
|
|
3595
3348
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3596
3349
|
if (status) {
|
|
3597
3350
|
try {
|
|
@@ -3633,7 +3386,7 @@ ${user.trimEnd()}
|
|
|
3633
3386
|
if (context?.type === "sprouted" && !variantName) {
|
|
3634
3387
|
try {
|
|
3635
3388
|
const db = await getDb();
|
|
3636
|
-
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(
|
|
3389
|
+
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq2(conversations.mind_name, baseName)).all();
|
|
3637
3390
|
for (const conv of activeConvs) {
|
|
3638
3391
|
await addMessage(conv.id, "assistant", "system", [
|
|
3639
3392
|
{ type: "text", text: "[seed has sprouted]" }
|
|
@@ -3671,7 +3424,7 @@ ${user.trimEnd()}
|
|
|
3671
3424
|
const name = c.req.param("name");
|
|
3672
3425
|
const entry = findMind(name);
|
|
3673
3426
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3674
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3427
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3675
3428
|
const sm = getSleepManagerIfReady();
|
|
3676
3429
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3677
3430
|
return c.json(sm.getState(name));
|
|
@@ -3679,7 +3432,7 @@ ${user.trimEnd()}
|
|
|
3679
3432
|
const name = c.req.param("name");
|
|
3680
3433
|
const entry = findMind(name);
|
|
3681
3434
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3682
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3435
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3683
3436
|
const sm = getSleepManagerIfReady();
|
|
3684
3437
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3685
3438
|
if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
|
|
@@ -3699,7 +3452,7 @@ ${user.trimEnd()}
|
|
|
3699
3452
|
const name = c.req.param("name");
|
|
3700
3453
|
const entry = findMind(name);
|
|
3701
3454
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3702
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3455
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3703
3456
|
const sm = getSleepManagerIfReady();
|
|
3704
3457
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3705
3458
|
if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
|
|
@@ -3709,7 +3462,7 @@ ${user.trimEnd()}
|
|
|
3709
3462
|
const name = c.req.param("name");
|
|
3710
3463
|
const entry = findMind(name);
|
|
3711
3464
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3712
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3465
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3713
3466
|
const sm = getSleepManagerIfReady();
|
|
3714
3467
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3715
3468
|
const flushed = await sm.flushQueuedMessages(name);
|
|
@@ -3742,11 +3495,11 @@ ${user.trimEnd()}
|
|
|
3742
3495
|
removeMind(name);
|
|
3743
3496
|
await deleteMindUser2(name);
|
|
3744
3497
|
const state = stateDir(name);
|
|
3745
|
-
if (
|
|
3746
|
-
|
|
3498
|
+
if (existsSync10(state)) {
|
|
3499
|
+
rmSync4(state, { recursive: true, force: true });
|
|
3747
3500
|
}
|
|
3748
|
-
if (force &&
|
|
3749
|
-
|
|
3501
|
+
if (force && existsSync10(dir)) {
|
|
3502
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
3750
3503
|
deleteMindUser(name);
|
|
3751
3504
|
}
|
|
3752
3505
|
fireWebhook({
|
|
@@ -3760,7 +3513,7 @@ ${user.trimEnd()}
|
|
|
3760
3513
|
const entry = findMind(mindName);
|
|
3761
3514
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3762
3515
|
const dir = mindDir(mindName);
|
|
3763
|
-
if (!
|
|
3516
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3764
3517
|
let body = {};
|
|
3765
3518
|
try {
|
|
3766
3519
|
body = await c.req.json();
|
|
@@ -3769,15 +3522,15 @@ ${user.trimEnd()}
|
|
|
3769
3522
|
const template = body.template ?? entry.template ?? "claude";
|
|
3770
3523
|
const UPGRADE_VARIANT = "upgrade";
|
|
3771
3524
|
if (body.abort) {
|
|
3772
|
-
const worktreeDir2 =
|
|
3773
|
-
if (!
|
|
3525
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3526
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3774
3527
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3775
3528
|
}
|
|
3776
3529
|
try {
|
|
3777
3530
|
try {
|
|
3778
|
-
const gitDirContent =
|
|
3531
|
+
const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
|
|
3779
3532
|
const gitDir = gitDirContent.replace("gitdir: ", "");
|
|
3780
|
-
if (
|
|
3533
|
+
if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
|
|
3781
3534
|
await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
|
|
3782
3535
|
}
|
|
3783
3536
|
} catch {
|
|
@@ -3792,8 +3545,8 @@ ${user.trimEnd()}
|
|
|
3792
3545
|
}
|
|
3793
3546
|
}
|
|
3794
3547
|
if (body.continue) {
|
|
3795
|
-
const worktreeDir2 =
|
|
3796
|
-
if (!
|
|
3548
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3549
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3797
3550
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3798
3551
|
}
|
|
3799
3552
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
|
|
@@ -3837,23 +3590,23 @@ ${user.trimEnd()}
|
|
|
3837
3590
|
);
|
|
3838
3591
|
}
|
|
3839
3592
|
}
|
|
3840
|
-
const worktreeDir =
|
|
3841
|
-
if (
|
|
3593
|
+
const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3594
|
+
if (existsSync10(worktreeDir)) {
|
|
3842
3595
|
return c.json(
|
|
3843
3596
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
3844
3597
|
409
|
|
3845
3598
|
);
|
|
3846
3599
|
}
|
|
3847
|
-
if (!
|
|
3600
|
+
if (!existsSync10(resolve12(dir, ".git"))) {
|
|
3848
3601
|
try {
|
|
3849
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3602
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
|
|
3850
3603
|
await gitExec(["init"], { cwd: dir, mindName, env });
|
|
3851
3604
|
await configureGitIdentity(mindName, { cwd: dir, mindName, env });
|
|
3852
3605
|
await gitExec(["add", "-A"], { cwd: dir, mindName, env });
|
|
3853
3606
|
await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
|
|
3854
3607
|
chownMindDir(dir, mindName);
|
|
3855
3608
|
} catch (err) {
|
|
3856
|
-
|
|
3609
|
+
rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
|
|
3857
3610
|
return c.json(
|
|
3858
3611
|
{
|
|
3859
3612
|
error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3867,7 +3620,7 @@ ${user.trimEnd()}
|
|
|
3867
3620
|
await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
|
|
3868
3621
|
} catch {
|
|
3869
3622
|
}
|
|
3870
|
-
if (!
|
|
3623
|
+
if (!existsSync10(resolve12(dir, "home", "shared"))) {
|
|
3871
3624
|
try {
|
|
3872
3625
|
await addSharedWorktree(mindName, dir);
|
|
3873
3626
|
} catch (err) {
|
|
@@ -3878,9 +3631,9 @@ ${user.trimEnd()}
|
|
|
3878
3631
|
}
|
|
3879
3632
|
}
|
|
3880
3633
|
await updateTemplateBranch(dir, template, mindName);
|
|
3881
|
-
const parentDir =
|
|
3882
|
-
if (!
|
|
3883
|
-
|
|
3634
|
+
const parentDir = resolve12(dir, ".variants");
|
|
3635
|
+
if (!existsSync10(parentDir)) {
|
|
3636
|
+
mkdirSync6(parentDir, { recursive: true });
|
|
3884
3637
|
}
|
|
3885
3638
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
3886
3639
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -3927,7 +3680,7 @@ ${user.trimEnd()}
|
|
|
3927
3680
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
3928
3681
|
}
|
|
3929
3682
|
try {
|
|
3930
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3683
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3931
3684
|
const sm = getSleepManagerIfReady();
|
|
3932
3685
|
if (sm?.isSleeping(baseName)) {
|
|
3933
3686
|
const body2 = await c.req.text();
|
|
@@ -4025,7 +3778,7 @@ ${user.trimEnd()}
|
|
|
4025
3778
|
if (seedEntry?.stage === "seed") {
|
|
4026
3779
|
try {
|
|
4027
3780
|
const db = await getDb();
|
|
4028
|
-
const countResult = await db.select({ count:
|
|
3781
|
+
const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
|
|
4029
3782
|
const msgCount = countResult[0]?.count ?? 0;
|
|
4030
3783
|
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
4031
3784
|
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 +3823,13 @@ ${user.trimEnd()}
|
|
|
4070
3823
|
const entry = findMind(name);
|
|
4071
3824
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4072
3825
|
const dir = mindDir(name);
|
|
4073
|
-
if (!
|
|
3826
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4074
3827
|
let config = readVoluteConfig(dir);
|
|
4075
3828
|
if (!config && entry.template === "pi") {
|
|
4076
|
-
const piConfigPath =
|
|
4077
|
-
if (
|
|
3829
|
+
const piConfigPath = resolve12(dir, "home/.config/config.json");
|
|
3830
|
+
if (existsSync10(piConfigPath)) {
|
|
4078
3831
|
try {
|
|
4079
|
-
config = JSON.parse(
|
|
3832
|
+
config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
|
|
4080
3833
|
} catch {
|
|
4081
3834
|
}
|
|
4082
3835
|
}
|
|
@@ -4113,7 +3866,7 @@ ${user.trimEnd()}
|
|
|
4113
3866
|
const entry = findMind(name);
|
|
4114
3867
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4115
3868
|
const dir = mindDir(name);
|
|
4116
|
-
if (!
|
|
3869
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4117
3870
|
const body = c.req.valid("json");
|
|
4118
3871
|
const existing = readVoluteConfig(dir) ?? {};
|
|
4119
3872
|
if (body.model !== void 0) existing.model = body.model;
|
|
@@ -4294,22 +4047,22 @@ ${user.trimEnd()}
|
|
|
4294
4047
|
const db = await getDb();
|
|
4295
4048
|
const rows = await db.select({
|
|
4296
4049
|
session: mindHistory.session,
|
|
4297
|
-
started_at:
|
|
4298
|
-
event_count:
|
|
4299
|
-
message_count:
|
|
4300
|
-
tool_count:
|
|
4301
|
-
}).from(mindHistory).where(
|
|
4050
|
+
started_at: sql`MIN(${mindHistory.created_at})`,
|
|
4051
|
+
event_count: sql`COUNT(*)`,
|
|
4052
|
+
message_count: sql`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
|
|
4053
|
+
tool_count: sql`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
|
|
4054
|
+
}).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
4055
|
return c.json(rows);
|
|
4303
4056
|
}).get("/:name/history/channels", async (c) => {
|
|
4304
4057
|
const name = c.req.param("name");
|
|
4305
4058
|
const db = await getDb();
|
|
4306
|
-
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(
|
|
4059
|
+
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq2(mindHistory.mind, name));
|
|
4307
4060
|
return c.json(rows.map((r) => r.channel));
|
|
4308
4061
|
}).get("/:name/history/export", async (c) => {
|
|
4309
4062
|
const name = c.req.param("name");
|
|
4310
4063
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
4311
4064
|
const db = await getDb();
|
|
4312
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4065
|
+
const rows = await db.select().from(mindHistory).where(eq2(mindHistory.mind, name));
|
|
4313
4066
|
return c.json(rows);
|
|
4314
4067
|
}).get("/:name/history", async (c) => {
|
|
4315
4068
|
const name = c.req.param("name");
|
|
@@ -4319,24 +4072,24 @@ ${user.trimEnd()}
|
|
|
4319
4072
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
4320
4073
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
4321
4074
|
const db = await getDb();
|
|
4322
|
-
const conditions = [
|
|
4075
|
+
const conditions = [eq2(mindHistory.mind, name)];
|
|
4323
4076
|
if (channel) {
|
|
4324
|
-
conditions.push(
|
|
4077
|
+
conditions.push(eq2(mindHistory.channel, channel));
|
|
4325
4078
|
}
|
|
4326
4079
|
if (session) {
|
|
4327
|
-
conditions.push(
|
|
4080
|
+
conditions.push(eq2(mindHistory.session, session));
|
|
4328
4081
|
}
|
|
4329
4082
|
if (!full) {
|
|
4330
|
-
conditions.push(
|
|
4083
|
+
conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
4331
4084
|
}
|
|
4332
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4085
|
+
const rows = await db.select().from(mindHistory).where(and(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
|
|
4333
4086
|
return c.json(rows);
|
|
4334
4087
|
});
|
|
4335
4088
|
var minds_default = app11;
|
|
4336
4089
|
|
|
4337
4090
|
// src/web/api/pages.ts
|
|
4338
4091
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4339
|
-
import { extname as
|
|
4092
|
+
import { extname as extname3, resolve as resolve13 } from "path";
|
|
4340
4093
|
import { Hono as Hono12 } from "hono";
|
|
4341
4094
|
var MIME_TYPES = {
|
|
4342
4095
|
".html": "text/html",
|
|
@@ -4358,17 +4111,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4358
4111
|
const name = c.req.param("name");
|
|
4359
4112
|
let pagesRoot;
|
|
4360
4113
|
if (name === "_system") {
|
|
4361
|
-
pagesRoot =
|
|
4114
|
+
pagesRoot = resolve13(voluteHome(), "shared", "pages");
|
|
4362
4115
|
} else {
|
|
4363
4116
|
if (!findMind(name)) return c.text("Not found", 404);
|
|
4364
|
-
pagesRoot =
|
|
4117
|
+
pagesRoot = resolve13(mindDir(name), "home", "pages");
|
|
4365
4118
|
}
|
|
4366
4119
|
const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
|
|
4367
|
-
const requestedPath =
|
|
4120
|
+
const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
|
|
4368
4121
|
if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
|
|
4369
4122
|
let fileStat = await stat2(requestedPath).catch(() => null);
|
|
4370
4123
|
if (fileStat?.isDirectory()) {
|
|
4371
|
-
const indexPath =
|
|
4124
|
+
const indexPath = resolve13(requestedPath, "index.html");
|
|
4372
4125
|
fileStat = await stat2(indexPath).catch(() => null);
|
|
4373
4126
|
if (fileStat?.isFile()) {
|
|
4374
4127
|
const body = await readFile2(indexPath);
|
|
@@ -4377,7 +4130,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4377
4130
|
return c.text("Not found", 404);
|
|
4378
4131
|
}
|
|
4379
4132
|
if (fileStat?.isFile()) {
|
|
4380
|
-
const ext =
|
|
4133
|
+
const ext = extname3(requestedPath);
|
|
4381
4134
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
4382
4135
|
const body = await readFile2(requestedPath);
|
|
4383
4136
|
return c.body(body, 200, { "Content-Type": mime });
|
|
@@ -4388,7 +4141,7 @@ var pages_default = app12;
|
|
|
4388
4141
|
|
|
4389
4142
|
// src/web/api/prompts.ts
|
|
4390
4143
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
4391
|
-
import { eq as
|
|
4144
|
+
import { eq as eq3, sql as sql2 } from "drizzle-orm";
|
|
4392
4145
|
import { Hono as Hono13 } from "hono";
|
|
4393
4146
|
import { z as z4 } from "zod";
|
|
4394
4147
|
var app13 = new Hono13().get("/", async (c) => {
|
|
@@ -4421,9 +4174,9 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4421
4174
|
}
|
|
4422
4175
|
const { content } = c.req.valid("json");
|
|
4423
4176
|
const db = await getDb();
|
|
4424
|
-
await db.insert(systemPrompts).values({ key, content, updated_at:
|
|
4177
|
+
await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
|
|
4425
4178
|
target: systemPrompts.key,
|
|
4426
|
-
set: { content, updated_at:
|
|
4179
|
+
set: { content, updated_at: sql2`(datetime('now'))` }
|
|
4427
4180
|
});
|
|
4428
4181
|
return c.json({ ok: true });
|
|
4429
4182
|
}).delete("/:key", requireAdmin, async (c) => {
|
|
@@ -4432,7 +4185,7 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4432
4185
|
return c.json({ error: "Unknown prompt key" }, 404);
|
|
4433
4186
|
}
|
|
4434
4187
|
const db = await getDb();
|
|
4435
|
-
await db.delete(systemPrompts).where(
|
|
4188
|
+
await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
|
|
4436
4189
|
return c.json({ ok: true });
|
|
4437
4190
|
});
|
|
4438
4191
|
var prompts_default = app13;
|
|
@@ -4615,9 +4368,9 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
|
|
|
4615
4368
|
var shared_default = app15;
|
|
4616
4369
|
|
|
4617
4370
|
// src/web/api/skills.ts
|
|
4618
|
-
import { existsSync as
|
|
4371
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
|
|
4619
4372
|
import { tmpdir } from "os";
|
|
4620
|
-
import { join as join2, resolve as
|
|
4373
|
+
import { join as join2, resolve as resolve14 } from "path";
|
|
4621
4374
|
import AdmZip from "adm-zip";
|
|
4622
4375
|
import { Hono as Hono16 } from "hono";
|
|
4623
4376
|
var app16 = new Hono16().get("/", async (c) => {
|
|
@@ -4644,19 +4397,19 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4644
4397
|
try {
|
|
4645
4398
|
const zip = new AdmZip(buffer2);
|
|
4646
4399
|
for (const entry of zip.getEntries()) {
|
|
4647
|
-
const target =
|
|
4400
|
+
const target = resolve14(tmpDir, entry.entryName);
|
|
4648
4401
|
if (!target.startsWith(tmpDir)) {
|
|
4649
4402
|
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
4650
4403
|
}
|
|
4651
4404
|
}
|
|
4652
4405
|
zip.extractAllTo(tmpDir, true);
|
|
4653
4406
|
let skillDir = null;
|
|
4654
|
-
if (
|
|
4407
|
+
if (existsSync11(join2(tmpDir, "SKILL.md"))) {
|
|
4655
4408
|
skillDir = tmpDir;
|
|
4656
4409
|
} else {
|
|
4657
4410
|
const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4658
4411
|
for (const entry of entries) {
|
|
4659
|
-
if (
|
|
4412
|
+
if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
|
|
4660
4413
|
skillDir = join2(tmpDir, entry.name);
|
|
4661
4414
|
break;
|
|
4662
4415
|
}
|
|
@@ -4673,7 +4426,7 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4673
4426
|
}
|
|
4674
4427
|
throw e;
|
|
4675
4428
|
} finally {
|
|
4676
|
-
|
|
4429
|
+
rmSync5(tmpDir, { recursive: true, force: true });
|
|
4677
4430
|
}
|
|
4678
4431
|
}).delete("/:id", requireAdmin, async (c) => {
|
|
4679
4432
|
const id = c.req.param("id");
|
|
@@ -4707,10 +4460,10 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
|
|
|
4707
4460
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
4708
4461
|
});
|
|
4709
4462
|
});
|
|
4710
|
-
await new Promise((
|
|
4463
|
+
await new Promise((resolve19) => {
|
|
4711
4464
|
stream.onAbort(() => {
|
|
4712
4465
|
unsubscribe();
|
|
4713
|
-
|
|
4466
|
+
resolve19();
|
|
4714
4467
|
});
|
|
4715
4468
|
});
|
|
4716
4469
|
});
|
|
@@ -4785,8 +4538,8 @@ async function fanOutToMinds(opts) {
|
|
|
4785
4538
|
const participantNames = participants.map((p) => p.username);
|
|
4786
4539
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
4787
4540
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
4788
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
4789
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
4541
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
|
|
4542
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
4790
4543
|
const manager = getMindManager2();
|
|
4791
4544
|
const sm = getSleepManagerIfReady();
|
|
4792
4545
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -4900,11 +4653,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4900
4653
|
}
|
|
4901
4654
|
}
|
|
4902
4655
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
4656
|
+
const isDM = conv?.type === "dm";
|
|
4903
4657
|
await fanOutToMinds({
|
|
4904
4658
|
conversationId,
|
|
4905
4659
|
contentBlocks,
|
|
4906
4660
|
senderName,
|
|
4907
4661
|
convTitle,
|
|
4662
|
+
isDM,
|
|
4663
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
4664
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
4908
4665
|
targetName: (username) => username === baseName ? name : username
|
|
4909
4666
|
});
|
|
4910
4667
|
return c.json({ ok: true, conversationId });
|
|
@@ -4925,11 +4682,11 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4925
4682
|
if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
|
|
4926
4683
|
});
|
|
4927
4684
|
}, 15e3);
|
|
4928
|
-
await new Promise((
|
|
4685
|
+
await new Promise((resolve19) => {
|
|
4929
4686
|
stream.onAbort(() => {
|
|
4930
4687
|
unsubscribe();
|
|
4931
4688
|
clearInterval(keepAlive);
|
|
4932
|
-
|
|
4689
|
+
resolve19();
|
|
4933
4690
|
});
|
|
4934
4691
|
});
|
|
4935
4692
|
});
|
|
@@ -5036,6 +4793,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5036
4793
|
participantIds: [...participantIds]
|
|
5037
4794
|
});
|
|
5038
4795
|
return c.json(conv, 201);
|
|
4796
|
+
}).post("/:id/read", async (c) => {
|
|
4797
|
+
const id = c.req.param("id");
|
|
4798
|
+
const user = c.get("user");
|
|
4799
|
+
if (user.id === 0) return c.json({ ok: true });
|
|
4800
|
+
if (!await isParticipantOrOwner(id, user.id)) {
|
|
4801
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
4802
|
+
}
|
|
4803
|
+
await markConversationRead(user.id, id);
|
|
4804
|
+
return c.json({ ok: true });
|
|
5039
4805
|
}).delete("/:id", async (c) => {
|
|
5040
4806
|
const id = c.req.param("id");
|
|
5041
4807
|
const user = c.get("user");
|
|
@@ -5046,10 +4812,33 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5046
4812
|
var conversations_default = app21;
|
|
5047
4813
|
|
|
5048
4814
|
// src/web/api/v1/events.ts
|
|
5049
|
-
import { desc as
|
|
4815
|
+
import { desc as desc3 } from "drizzle-orm";
|
|
5050
4816
|
import { Hono as Hono22 } from "hono";
|
|
5051
4817
|
import { streamSSE as streamSSE5 } from "hono/streaming";
|
|
5052
4818
|
|
|
4819
|
+
// src/lib/events/brain-presence.ts
|
|
4820
|
+
var connections = /* @__PURE__ */ new Map();
|
|
4821
|
+
function addConnection(username) {
|
|
4822
|
+
const count = connections.get(username) ?? 0;
|
|
4823
|
+
connections.set(username, count + 1);
|
|
4824
|
+
if (count === 0) {
|
|
4825
|
+
broadcast({ type: "brain_online", mind: username, summary: `${username} connected` });
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
function removeConnection(username) {
|
|
4829
|
+
const count = connections.get(username);
|
|
4830
|
+
if (count == null) return;
|
|
4831
|
+
if (count <= 1) {
|
|
4832
|
+
connections.delete(username);
|
|
4833
|
+
broadcast({ type: "brain_offline", mind: username, summary: `${username} disconnected` });
|
|
4834
|
+
} else {
|
|
4835
|
+
connections.set(username, count - 1);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
function getOnlineBrains() {
|
|
4839
|
+
return [...connections.keys()];
|
|
4840
|
+
}
|
|
4841
|
+
|
|
5053
4842
|
// src/lib/events/event-sequencer.ts
|
|
5054
4843
|
var BUFFER_SIZE = 1e3;
|
|
5055
4844
|
var MAX_AGE_MS = 5 * 60 * 1e3;
|
|
@@ -5077,6 +4866,10 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5077
4866
|
const sinceId = since ? Number(since) : 0;
|
|
5078
4867
|
return streamSSE5(c, async (stream) => {
|
|
5079
4868
|
const cleanups = [];
|
|
4869
|
+
if (user.user_type === "brain") {
|
|
4870
|
+
addConnection(user.username);
|
|
4871
|
+
cleanups.push(() => removeConnection(user.username));
|
|
4872
|
+
}
|
|
5080
4873
|
try {
|
|
5081
4874
|
if (sinceId > 0) {
|
|
5082
4875
|
const missed = getEventsSince(sinceId);
|
|
@@ -5090,7 +4883,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5090
4883
|
let recentActivity = [];
|
|
5091
4884
|
try {
|
|
5092
4885
|
const db = await getDb();
|
|
5093
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
4886
|
+
recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
|
|
5094
4887
|
recentActivity = recentActivity.map((row) => ({
|
|
5095
4888
|
...row,
|
|
5096
4889
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -5101,6 +4894,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5101
4894
|
let conversations2 = [];
|
|
5102
4895
|
try {
|
|
5103
4896
|
conversations2 = await listConversationsWithParticipants(user.id);
|
|
4897
|
+
if (conversations2.length > 0) {
|
|
4898
|
+
const convIds = conversations2.map((c2) => c2.id);
|
|
4899
|
+
const unreads = await getUnreadCounts(user.id, convIds);
|
|
4900
|
+
for (const conv of conversations2) {
|
|
4901
|
+
conv.unreadCount = unreads[conv.id] ?? 0;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
5104
4904
|
} catch (err) {
|
|
5105
4905
|
logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
|
|
5106
4906
|
}
|
|
@@ -5112,7 +4912,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5112
4912
|
conversations: conversations2,
|
|
5113
4913
|
sites,
|
|
5114
4914
|
recentPages,
|
|
5115
|
-
activeMinds: getActiveMinds()
|
|
4915
|
+
activeMinds: getActiveMinds(),
|
|
4916
|
+
onlineBrains: getOnlineBrains()
|
|
5116
4917
|
};
|
|
5117
4918
|
const snapshotId = bufferEvent(snapshotData);
|
|
5118
4919
|
await stream.writeSSE({
|
|
@@ -5153,8 +4954,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5153
4954
|
});
|
|
5154
4955
|
}, 15e3);
|
|
5155
4956
|
cleanups.push(() => clearInterval(keepAlive));
|
|
5156
|
-
await new Promise((
|
|
5157
|
-
stream.onAbort(() =>
|
|
4957
|
+
await new Promise((resolve19) => {
|
|
4958
|
+
stream.onAbort(() => resolve19());
|
|
5158
4959
|
});
|
|
5159
4960
|
} finally {
|
|
5160
4961
|
for (const cleanup of cleanups) {
|
|
@@ -5169,16 +4970,16 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5169
4970
|
var events_default = app22;
|
|
5170
4971
|
|
|
5171
4972
|
// src/web/api/variants.ts
|
|
5172
|
-
import { existsSync as
|
|
5173
|
-
import { resolve as
|
|
4973
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
4974
|
+
import { resolve as resolve16 } from "path";
|
|
5174
4975
|
import { Hono as Hono23 } from "hono";
|
|
5175
4976
|
|
|
5176
4977
|
// src/lib/spawn-server.ts
|
|
5177
4978
|
import { spawn as spawn3 } from "child_process";
|
|
5178
|
-
import { closeSync, mkdirSync as
|
|
5179
|
-
import { resolve as
|
|
4979
|
+
import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
|
|
4980
|
+
import { resolve as resolve15 } from "path";
|
|
5180
4981
|
function tsxBin(cwd) {
|
|
5181
|
-
return
|
|
4982
|
+
return resolve15(cwd, "node_modules", ".bin", "tsx");
|
|
5182
4983
|
}
|
|
5183
4984
|
function spawnServer(cwd, port, options) {
|
|
5184
4985
|
if (options?.detached) {
|
|
@@ -5191,31 +4992,31 @@ function spawnAttached(cwd, port) {
|
|
|
5191
4992
|
cwd,
|
|
5192
4993
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5193
4994
|
});
|
|
5194
|
-
return new Promise((
|
|
5195
|
-
const timeout = setTimeout(() =>
|
|
4995
|
+
return new Promise((resolve19) => {
|
|
4996
|
+
const timeout = setTimeout(() => resolve19(null), 3e4);
|
|
5196
4997
|
function checkOutput(data) {
|
|
5197
4998
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
5198
4999
|
if (match) {
|
|
5199
5000
|
clearTimeout(timeout);
|
|
5200
|
-
|
|
5001
|
+
resolve19({ child, actualPort: parseInt(match[1], 10) });
|
|
5201
5002
|
}
|
|
5202
5003
|
}
|
|
5203
5004
|
child.stdout?.on("data", checkOutput);
|
|
5204
5005
|
child.stderr?.on("data", checkOutput);
|
|
5205
5006
|
child.on("error", () => {
|
|
5206
5007
|
clearTimeout(timeout);
|
|
5207
|
-
|
|
5008
|
+
resolve19(null);
|
|
5208
5009
|
});
|
|
5209
5010
|
child.on("exit", () => {
|
|
5210
5011
|
clearTimeout(timeout);
|
|
5211
|
-
|
|
5012
|
+
resolve19(null);
|
|
5212
5013
|
});
|
|
5213
5014
|
});
|
|
5214
5015
|
}
|
|
5215
5016
|
function spawnDetached(cwd, port, logDir) {
|
|
5216
|
-
const logsDir = logDir ??
|
|
5217
|
-
|
|
5218
|
-
const logPath =
|
|
5017
|
+
const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
|
|
5018
|
+
mkdirSync7(logsDir, { recursive: true });
|
|
5019
|
+
const logPath = resolve15(logsDir, "mind.log");
|
|
5219
5020
|
const logFd = openSync(logPath, "a");
|
|
5220
5021
|
const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
5221
5022
|
cwd,
|
|
@@ -5235,7 +5036,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
5235
5036
|
}
|
|
5236
5037
|
const interval = setInterval(() => {
|
|
5237
5038
|
try {
|
|
5238
|
-
const content =
|
|
5039
|
+
const content = readFileSync10(logPath, "utf-8");
|
|
5239
5040
|
const match = content.match(/listening on :(\d+)/);
|
|
5240
5041
|
if (match) {
|
|
5241
5042
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -5327,11 +5128,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5327
5128
|
const err = validateBranchName(variantName);
|
|
5328
5129
|
if (err) return c.json({ error: err }, 400);
|
|
5329
5130
|
const projectRoot = mindDir(mindName);
|
|
5330
|
-
const variantDir =
|
|
5331
|
-
if (
|
|
5131
|
+
const variantDir = resolve16(projectRoot, ".variants", variantName);
|
|
5132
|
+
if (existsSync12(variantDir)) {
|
|
5332
5133
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
5333
5134
|
}
|
|
5334
|
-
|
|
5135
|
+
mkdirSync8(resolve16(projectRoot, ".variants"), { recursive: true });
|
|
5335
5136
|
try {
|
|
5336
5137
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
5337
5138
|
} catch (e) {
|
|
@@ -5344,7 +5145,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5344
5145
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5345
5146
|
await exec(cmd, args, {
|
|
5346
5147
|
cwd: variantDir,
|
|
5347
|
-
env: { ...process.env, HOME:
|
|
5148
|
+
env: { ...process.env, HOME: resolve16(variantDir, "home") }
|
|
5348
5149
|
});
|
|
5349
5150
|
} else {
|
|
5350
5151
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -5354,7 +5155,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5354
5155
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
5355
5156
|
}
|
|
5356
5157
|
if (body.soul) {
|
|
5357
|
-
|
|
5158
|
+
writeFileSync8(resolve16(variantDir, "home/SOUL.md"), body.soul);
|
|
5358
5159
|
}
|
|
5359
5160
|
const variantPort = body.port ?? nextPort();
|
|
5360
5161
|
const variant = {
|
|
@@ -5392,7 +5193,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5392
5193
|
} catch {
|
|
5393
5194
|
}
|
|
5394
5195
|
const projectRoot = mindDir(mindName);
|
|
5395
|
-
if (
|
|
5196
|
+
if (existsSync12(variant.path)) {
|
|
5396
5197
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
5397
5198
|
if (status) {
|
|
5398
5199
|
try {
|
|
@@ -5465,7 +5266,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5465
5266
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5466
5267
|
await exec(cmd, args, {
|
|
5467
5268
|
cwd: projectRoot,
|
|
5468
|
-
env: { ...process.env, HOME:
|
|
5269
|
+
env: { ...process.env, HOME: resolve16(projectRoot, "home") }
|
|
5469
5270
|
});
|
|
5470
5271
|
} else {
|
|
5471
5272
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -5592,8 +5393,8 @@ async function fanOutToMinds2(opts) {
|
|
|
5592
5393
|
const participantNames = participants.map((p) => p.username);
|
|
5593
5394
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
5594
5395
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
5595
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
5596
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
5396
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-ZNRIYEK3.js");
|
|
5397
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
5597
5398
|
const manager = getMindManager2();
|
|
5598
5399
|
const sm = getSleepManagerIfReady();
|
|
5599
5400
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -5710,11 +5511,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5710
5511
|
}
|
|
5711
5512
|
}
|
|
5712
5513
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
5514
|
+
const isDM = conv?.type === "dm";
|
|
5713
5515
|
await fanOutToMinds2({
|
|
5714
5516
|
conversationId,
|
|
5715
5517
|
contentBlocks,
|
|
5716
5518
|
senderName,
|
|
5717
5519
|
convTitle,
|
|
5520
|
+
isDM,
|
|
5521
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
5522
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
5718
5523
|
targetName: (username) => username === baseName ? name : username
|
|
5719
5524
|
});
|
|
5720
5525
|
return c.json({ ok: true, conversationId });
|
|
@@ -5735,11 +5540,11 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5735
5540
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5736
5541
|
});
|
|
5737
5542
|
}, 15e3);
|
|
5738
|
-
await new Promise((
|
|
5543
|
+
await new Promise((resolve19) => {
|
|
5739
5544
|
stream.onAbort(() => {
|
|
5740
5545
|
unsubscribe();
|
|
5741
5546
|
clearInterval(keepAlive);
|
|
5742
|
-
|
|
5547
|
+
resolve19();
|
|
5743
5548
|
});
|
|
5744
5549
|
});
|
|
5745
5550
|
});
|
|
@@ -5951,11 +5756,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5951
5756
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5952
5757
|
});
|
|
5953
5758
|
}, 15e3);
|
|
5954
|
-
await new Promise((
|
|
5759
|
+
await new Promise((resolve19) => {
|
|
5955
5760
|
stream.onAbort(() => {
|
|
5956
5761
|
unsubscribe();
|
|
5957
5762
|
clearInterval(keepAlive);
|
|
5958
|
-
|
|
5763
|
+
resolve19();
|
|
5959
5764
|
});
|
|
5960
5765
|
});
|
|
5961
5766
|
});
|
|
@@ -6053,13 +5858,14 @@ var MIME_TYPES2 = {
|
|
|
6053
5858
|
};
|
|
6054
5859
|
async function startServer({
|
|
6055
5860
|
port,
|
|
6056
|
-
hostname = "127.0.0.1"
|
|
5861
|
+
hostname = "127.0.0.1",
|
|
5862
|
+
tls
|
|
6057
5863
|
}) {
|
|
6058
5864
|
let assetsDir = "";
|
|
6059
5865
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
6060
5866
|
for (let i = 0; i < 5; i++) {
|
|
6061
|
-
const candidate =
|
|
6062
|
-
if (
|
|
5867
|
+
const candidate = resolve17(searchDir, "dist", "web-assets");
|
|
5868
|
+
if (existsSync13(candidate)) {
|
|
6063
5869
|
assetsDir = candidate;
|
|
6064
5870
|
break;
|
|
6065
5871
|
}
|
|
@@ -6069,16 +5875,16 @@ async function startServer({
|
|
|
6069
5875
|
app_default.get("*", async (c) => {
|
|
6070
5876
|
const urlPath = new URL(c.req.url).pathname;
|
|
6071
5877
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
6072
|
-
const filePath =
|
|
5878
|
+
const filePath = resolve17(assetsDir, urlPath.slice(1));
|
|
6073
5879
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
6074
5880
|
const s = await stat3(filePath).catch(() => null);
|
|
6075
5881
|
if (s?.isFile()) {
|
|
6076
|
-
const ext =
|
|
5882
|
+
const ext = extname4(filePath);
|
|
6077
5883
|
const mime = MIME_TYPES2[ext] || "application/octet-stream";
|
|
6078
5884
|
const body = await readFile3(filePath);
|
|
6079
5885
|
return c.body(body, 200, { "Content-Type": mime });
|
|
6080
5886
|
}
|
|
6081
|
-
const indexPath =
|
|
5887
|
+
const indexPath = resolve17(assetsDir, "index.html");
|
|
6082
5888
|
const indexStat = await stat3(indexPath).catch(() => null);
|
|
6083
5889
|
if (indexStat?.isFile()) {
|
|
6084
5890
|
const body = await readFile3(indexPath, "utf-8");
|
|
@@ -6087,22 +5893,55 @@ async function startServer({
|
|
|
6087
5893
|
return c.text("Not found", 404);
|
|
6088
5894
|
});
|
|
6089
5895
|
}
|
|
5896
|
+
if (tls) {
|
|
5897
|
+
const server2 = serve({
|
|
5898
|
+
fetch: app_default.fetch,
|
|
5899
|
+
port,
|
|
5900
|
+
hostname,
|
|
5901
|
+
createServer: createHttpsServer,
|
|
5902
|
+
serverOptions: { key: tls.key, cert: tls.cert }
|
|
5903
|
+
});
|
|
5904
|
+
await new Promise((resolve19, reject) => {
|
|
5905
|
+
server2.on("listening", () => {
|
|
5906
|
+
logger_default.info("Volute UI running (https)", { hostname, port });
|
|
5907
|
+
resolve19();
|
|
5908
|
+
});
|
|
5909
|
+
server2.on("error", (err) => {
|
|
5910
|
+
reject(err);
|
|
5911
|
+
});
|
|
5912
|
+
});
|
|
5913
|
+
const internalPort = port + 1;
|
|
5914
|
+
const internalServer = serve({ fetch: app_default.fetch, port: internalPort, hostname: "127.0.0.1" });
|
|
5915
|
+
await new Promise((resolve19, reject) => {
|
|
5916
|
+
internalServer.on("listening", () => {
|
|
5917
|
+
logger_default.info("Volute API running (http, internal)", {
|
|
5918
|
+
hostname: "127.0.0.1",
|
|
5919
|
+
port: internalPort
|
|
5920
|
+
});
|
|
5921
|
+
resolve19();
|
|
5922
|
+
});
|
|
5923
|
+
internalServer.on("error", (err) => {
|
|
5924
|
+
reject(err);
|
|
5925
|
+
});
|
|
5926
|
+
});
|
|
5927
|
+
return { server: server2, internalPort };
|
|
5928
|
+
}
|
|
6090
5929
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
6091
|
-
await new Promise((
|
|
5930
|
+
await new Promise((resolve19, reject) => {
|
|
6092
5931
|
server.on("listening", () => {
|
|
6093
|
-
logger_default.info("Volute
|
|
6094
|
-
|
|
5932
|
+
logger_default.info("Volute API running (http)", { hostname, port });
|
|
5933
|
+
resolve19();
|
|
6095
5934
|
});
|
|
6096
5935
|
server.on("error", (err) => {
|
|
6097
5936
|
reject(err);
|
|
6098
5937
|
});
|
|
6099
5938
|
});
|
|
6100
|
-
return server;
|
|
5939
|
+
return { server };
|
|
6101
5940
|
}
|
|
6102
5941
|
|
|
6103
5942
|
// src/daemon.ts
|
|
6104
5943
|
if (!process.env.VOLUTE_HOME) {
|
|
6105
|
-
process.env.VOLUTE_HOME =
|
|
5944
|
+
process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
|
|
6106
5945
|
}
|
|
6107
5946
|
if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
|
|
6108
5947
|
process.env.TZ = process.env.VOLUTE_TIMEZONE;
|
|
@@ -6112,7 +5951,7 @@ async function startDaemon(opts) {
|
|
|
6112
5951
|
const myPid = String(process.pid);
|
|
6113
5952
|
const home = voluteHome();
|
|
6114
5953
|
if (!opts.foreground) {
|
|
6115
|
-
const rotatingLog = new RotatingLog(
|
|
5954
|
+
const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
|
|
6116
5955
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
6117
5956
|
`));
|
|
6118
5957
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -6122,9 +5961,9 @@ async function startDaemon(opts) {
|
|
|
6122
5961
|
console.warn = write;
|
|
6123
5962
|
console.info = write;
|
|
6124
5963
|
}
|
|
6125
|
-
const DAEMON_PID_PATH =
|
|
6126
|
-
const DAEMON_JSON_PATH =
|
|
6127
|
-
|
|
5964
|
+
const DAEMON_PID_PATH = resolve18(home, "daemon.pid");
|
|
5965
|
+
const DAEMON_JSON_PATH = resolve18(home, "daemon.json");
|
|
5966
|
+
mkdirSync9(home, { recursive: true });
|
|
6128
5967
|
migrateAgentsToMinds();
|
|
6129
5968
|
try {
|
|
6130
5969
|
await ensureSharedRepo();
|
|
@@ -6138,12 +5977,16 @@ async function startDaemon(opts) {
|
|
|
6138
5977
|
logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
|
|
6139
5978
|
}
|
|
6140
5979
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
5980
|
+
let tls;
|
|
5981
|
+
if (opts.tailscale) {
|
|
5982
|
+
const { getTailscaleTls } = await import("./tailscale-AJ4VL5XK.js");
|
|
5983
|
+
const tlsConfig = await getTailscaleTls();
|
|
5984
|
+
tls = { key: tlsConfig.key, cert: tlsConfig.cert };
|
|
5985
|
+
logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
|
|
5986
|
+
}
|
|
5987
|
+
let result;
|
|
6145
5988
|
try {
|
|
6146
|
-
|
|
5989
|
+
result = await startServer({ port, hostname: "0.0.0.0", tls });
|
|
6147
5990
|
} catch (err) {
|
|
6148
5991
|
const e = err;
|
|
6149
5992
|
if (e.code === "EADDRINUSE") {
|
|
@@ -6152,11 +5995,17 @@ async function startDaemon(opts) {
|
|
|
6152
5995
|
}
|
|
6153
5996
|
throw err;
|
|
6154
5997
|
}
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
5998
|
+
const { server, internalPort } = result;
|
|
5999
|
+
const daemonPort = internalPort ?? port;
|
|
6000
|
+
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
6001
|
+
process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
|
|
6002
|
+
process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
|
|
6003
|
+
writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
6004
|
+
const daemonConfig = { port, hostname, token };
|
|
6005
|
+
if (internalPort) daemonConfig.internalPort = internalPort;
|
|
6006
|
+
if (tls) daemonConfig.tls = true;
|
|
6007
|
+
writeFileSync9(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
|
|
6008
|
+
`, { mode: 420 });
|
|
6160
6009
|
const delivery = initDeliveryManager();
|
|
6161
6010
|
const manager = initMindManager();
|
|
6162
6011
|
manager.loadCrashAttempts();
|
|
@@ -6188,8 +6037,8 @@ async function startDaemon(opts) {
|
|
|
6188
6037
|
if (sleepManager.isSleeping(entry.name)) {
|
|
6189
6038
|
try {
|
|
6190
6039
|
const dir = mindDir(entry.name);
|
|
6191
|
-
const
|
|
6192
|
-
await connectors.startConnectors(entry.name, dir, entry.port,
|
|
6040
|
+
const daemonPort2 = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
|
|
6041
|
+
await connectors.startConnectors(entry.name, dir, entry.port, daemonPort2);
|
|
6193
6042
|
scheduler.loadSchedules(entry.name);
|
|
6194
6043
|
} catch (err) {
|
|
6195
6044
|
logger_default.error(
|
|
@@ -6226,7 +6075,7 @@ async function startDaemon(opts) {
|
|
|
6226
6075
|
});
|
|
6227
6076
|
await Promise.all(workers);
|
|
6228
6077
|
}
|
|
6229
|
-
import("./cloud-sync-
|
|
6078
|
+
import("./cloud-sync-DIU3OCPV.js").then(
|
|
6230
6079
|
({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
|
|
6231
6080
|
logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
|
|
6232
6081
|
})
|
|
@@ -6234,7 +6083,7 @@ async function startDaemon(opts) {
|
|
|
6234
6083
|
logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
|
|
6235
6084
|
});
|
|
6236
6085
|
try {
|
|
6237
|
-
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-
|
|
6086
|
+
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-SZ75QRGO.js");
|
|
6238
6087
|
backfillTemplateHashes();
|
|
6239
6088
|
notifyVersionUpdate().catch((err) => {
|
|
6240
6089
|
logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
|
|
@@ -6251,13 +6100,13 @@ async function startDaemon(opts) {
|
|
|
6251
6100
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
6252
6101
|
function cleanup() {
|
|
6253
6102
|
try {
|
|
6254
|
-
if (
|
|
6103
|
+
if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
6255
6104
|
unlinkSync(DAEMON_PID_PATH);
|
|
6256
6105
|
}
|
|
6257
6106
|
} catch {
|
|
6258
6107
|
}
|
|
6259
6108
|
try {
|
|
6260
|
-
const data = JSON.parse(
|
|
6109
|
+
const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
|
|
6261
6110
|
if (data.token === token) {
|
|
6262
6111
|
unlinkSync(DAEMON_JSON_PATH);
|
|
6263
6112
|
}
|
|
@@ -6271,9 +6120,9 @@ async function startDaemon(opts) {
|
|
|
6271
6120
|
logger_default.info("shutting down...");
|
|
6272
6121
|
const safe = (label, fn) => {
|
|
6273
6122
|
try {
|
|
6274
|
-
const
|
|
6275
|
-
if (
|
|
6276
|
-
return
|
|
6123
|
+
const result2 = fn();
|
|
6124
|
+
if (result2 instanceof Promise)
|
|
6125
|
+
return result2.catch((err) => logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err)));
|
|
6277
6126
|
} catch (err) {
|
|
6278
6127
|
logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
|
|
6279
6128
|
}
|
|
@@ -6305,9 +6154,10 @@ async function startDaemon(opts) {
|
|
|
6305
6154
|
process.on("exit", cleanup);
|
|
6306
6155
|
}
|
|
6307
6156
|
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
|
|
6308
|
-
let port =
|
|
6157
|
+
let port = 1618;
|
|
6309
6158
|
let hostname = "127.0.0.1";
|
|
6310
6159
|
let foreground = false;
|
|
6160
|
+
let tailscale = false;
|
|
6311
6161
|
for (let i = 2; i < process.argv.length; i++) {
|
|
6312
6162
|
if (process.argv[i] === "--port" && process.argv[i + 1]) {
|
|
6313
6163
|
port = parseInt(process.argv[i + 1], 10);
|
|
@@ -6317,9 +6167,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
6317
6167
|
i++;
|
|
6318
6168
|
} else if (process.argv[i] === "--foreground") {
|
|
6319
6169
|
foreground = true;
|
|
6170
|
+
} else if (process.argv[i] === "--tailscale") {
|
|
6171
|
+
tailscale = true;
|
|
6320
6172
|
}
|
|
6321
6173
|
}
|
|
6322
|
-
startDaemon({ port, hostname, foreground });
|
|
6174
|
+
startDaemon({ port, hostname, foreground, tailscale });
|
|
6323
6175
|
}
|
|
6324
6176
|
export {
|
|
6325
6177
|
startDaemon
|