volute 0.22.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-VNVCRVYI.js → chunk-NOBRGACV.js} +7 -7
- package/dist/{chunk-OSFGKF2T.js → chunk-OOW675I3.js} +839 -129
- package/dist/{chunk-TFS25FIM.js → chunk-P3W36ZGD.js} +1 -1
- package/dist/{chunk-JNFRY2WU.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-C6WRYRVR.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-TPQ2XBRZ.js → daemon-restart-YMPEATQH.js} +5 -5
- package/dist/daemon.js +697 -865
- 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-WUS4K4ZC.js → message-delivery-S7BCNV6Y.js} +9 -7
- 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-P5OBDUKI.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-A7PEYJI2.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-3RWUX2ZR.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-JKGC7PPF.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-5FGUAVSF.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/file-handler.ts +6 -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/templates/claude/src/lib/stream-consumer.ts +10 -1
- package/templates/pi/src/lib/content.ts +18 -3
- package/templates/pi/src/lib/event-handler.ts +9 -1
- package/dist/chunk-G5KRTU2F.js +0 -76
- package/dist/web-assets/assets/index-DWBxl4LO.js +0 -69
- package/dist/web-assets/assets/index-ZqMd1mx1.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,29 +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,
|
|
70
|
+
publish2,
|
|
41
71
|
publishTypingForChannels,
|
|
72
|
+
recordInbound,
|
|
73
|
+
setUserRole,
|
|
42
74
|
startMindFull,
|
|
43
75
|
stopAllWatchers,
|
|
44
76
|
stopMindFull,
|
|
45
|
-
subscribe as subscribe2
|
|
46
|
-
|
|
77
|
+
subscribe as subscribe2,
|
|
78
|
+
subscribe2 as subscribe3,
|
|
79
|
+
updateUserProfile,
|
|
80
|
+
verifyUser
|
|
81
|
+
} from "./chunk-OOW675I3.js";
|
|
47
82
|
import {
|
|
48
83
|
readSystemsConfig
|
|
49
84
|
} from "./chunk-HFCBO2GL.js";
|
|
@@ -51,11 +86,11 @@ import {
|
|
|
51
86
|
getActiveMinds,
|
|
52
87
|
onMindEvent,
|
|
53
88
|
stopAll
|
|
54
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-E7GOKNOT.js";
|
|
55
90
|
import {
|
|
56
91
|
broadcast,
|
|
57
92
|
subscribe
|
|
58
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-BFK6SOEJ.js";
|
|
59
94
|
import {
|
|
60
95
|
PROMPT_DEFAULTS,
|
|
61
96
|
PROMPT_KEYS,
|
|
@@ -66,13 +101,13 @@ import {
|
|
|
66
101
|
getPromptIfCustom,
|
|
67
102
|
initMindManager,
|
|
68
103
|
substitute
|
|
69
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-NOBRGACV.js";
|
|
70
105
|
import {
|
|
71
106
|
findOpenClawSession,
|
|
72
107
|
importOpenClawConnectors,
|
|
73
108
|
importPiSession,
|
|
74
109
|
parseNameFromIdentity
|
|
75
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-4TJ72QQ3.js";
|
|
76
111
|
import {
|
|
77
112
|
readVoluteConfig,
|
|
78
113
|
writeVoluteConfig
|
|
@@ -102,18 +137,15 @@ import {
|
|
|
102
137
|
syncBuiltinSkills,
|
|
103
138
|
uninstallSkill,
|
|
104
139
|
updateSkill
|
|
105
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-P3W36ZGD.js";
|
|
106
141
|
import {
|
|
107
142
|
activity,
|
|
108
|
-
conversationParticipants,
|
|
109
143
|
conversations,
|
|
110
144
|
getDb,
|
|
111
|
-
messages,
|
|
112
145
|
mindHistory,
|
|
113
146
|
sessions,
|
|
114
|
-
systemPrompts
|
|
115
|
-
|
|
116
|
-
} from "./chunk-SGPEZ32F.js";
|
|
147
|
+
systemPrompts
|
|
148
|
+
} from "./chunk-33XAVCS4.js";
|
|
117
149
|
import {
|
|
118
150
|
logBuffer,
|
|
119
151
|
logger_default
|
|
@@ -176,9 +208,9 @@ import {
|
|
|
176
208
|
|
|
177
209
|
// src/daemon.ts
|
|
178
210
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
179
|
-
import { mkdirSync as
|
|
211
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
180
212
|
import { homedir as homedir2 } from "os";
|
|
181
|
-
import { resolve as
|
|
213
|
+
import { resolve as resolve18 } from "path";
|
|
182
214
|
import { format } from "util";
|
|
183
215
|
|
|
184
216
|
// src/lib/migrate-agents-to-minds.ts
|
|
@@ -356,145 +388,9 @@ function migrateMindState(name) {
|
|
|
356
388
|
|
|
357
389
|
// src/web/middleware/auth.ts
|
|
358
390
|
import { timingSafeEqual } from "crypto";
|
|
359
|
-
import { eq
|
|
391
|
+
import { eq, lt } from "drizzle-orm";
|
|
360
392
|
import { getCookie } from "hono/cookie";
|
|
361
393
|
import { createMiddleware } from "hono/factory";
|
|
362
|
-
|
|
363
|
-
// src/lib/auth.ts
|
|
364
|
-
import { compareSync, hashSync } from "bcryptjs";
|
|
365
|
-
import { and, count, eq } from "drizzle-orm";
|
|
366
|
-
async function createUser(username, password) {
|
|
367
|
-
const db = await getDb();
|
|
368
|
-
const hash = hashSync(password, 10);
|
|
369
|
-
const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
|
|
370
|
-
const role = value === 0 ? "admin" : "pending";
|
|
371
|
-
const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
|
|
372
|
-
id: users.id,
|
|
373
|
-
username: users.username,
|
|
374
|
-
role: users.role,
|
|
375
|
-
user_type: users.user_type,
|
|
376
|
-
created_at: users.created_at
|
|
377
|
-
});
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
async function verifyUser(username, password) {
|
|
381
|
-
const db = await getDb();
|
|
382
|
-
const row = await db.select().from(users).where(eq(users.username, username)).get();
|
|
383
|
-
if (!row) return null;
|
|
384
|
-
if (row.user_type === "mind") return null;
|
|
385
|
-
if (!compareSync(password, row.password_hash)) return null;
|
|
386
|
-
const { password_hash: _, ...user } = row;
|
|
387
|
-
return user;
|
|
388
|
-
}
|
|
389
|
-
async function getUser(id) {
|
|
390
|
-
const db = await getDb();
|
|
391
|
-
const row = await db.select({
|
|
392
|
-
id: users.id,
|
|
393
|
-
username: users.username,
|
|
394
|
-
role: users.role,
|
|
395
|
-
user_type: users.user_type,
|
|
396
|
-
created_at: users.created_at
|
|
397
|
-
}).from(users).where(eq(users.id, id)).get();
|
|
398
|
-
return row ?? null;
|
|
399
|
-
}
|
|
400
|
-
async function getUserByUsername(username) {
|
|
401
|
-
const db = await getDb();
|
|
402
|
-
const row = await db.select({
|
|
403
|
-
id: users.id,
|
|
404
|
-
username: users.username,
|
|
405
|
-
role: users.role,
|
|
406
|
-
user_type: users.user_type,
|
|
407
|
-
created_at: users.created_at
|
|
408
|
-
}).from(users).where(eq(users.username, username)).get();
|
|
409
|
-
return row ?? null;
|
|
410
|
-
}
|
|
411
|
-
async function listUsers() {
|
|
412
|
-
const db = await getDb();
|
|
413
|
-
return db.select({
|
|
414
|
-
id: users.id,
|
|
415
|
-
username: users.username,
|
|
416
|
-
role: users.role,
|
|
417
|
-
user_type: users.user_type,
|
|
418
|
-
created_at: users.created_at
|
|
419
|
-
}).from(users).orderBy(users.created_at).all();
|
|
420
|
-
}
|
|
421
|
-
async function listPendingUsers() {
|
|
422
|
-
const db = await getDb();
|
|
423
|
-
return db.select({
|
|
424
|
-
id: users.id,
|
|
425
|
-
username: users.username,
|
|
426
|
-
role: users.role,
|
|
427
|
-
user_type: users.user_type,
|
|
428
|
-
created_at: users.created_at
|
|
429
|
-
}).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
|
|
430
|
-
}
|
|
431
|
-
async function listUsersByType(userType) {
|
|
432
|
-
const db = await getDb();
|
|
433
|
-
return db.select({
|
|
434
|
-
id: users.id,
|
|
435
|
-
username: users.username,
|
|
436
|
-
role: users.role,
|
|
437
|
-
user_type: users.user_type,
|
|
438
|
-
created_at: users.created_at
|
|
439
|
-
}).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
|
|
440
|
-
}
|
|
441
|
-
async function getOrCreateMindUser(mindName) {
|
|
442
|
-
const db = await getDb();
|
|
443
|
-
const existing = await db.select({
|
|
444
|
-
id: users.id,
|
|
445
|
-
username: users.username,
|
|
446
|
-
role: users.role,
|
|
447
|
-
user_type: users.user_type,
|
|
448
|
-
created_at: users.created_at
|
|
449
|
-
}).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
|
|
450
|
-
if (existing) return existing;
|
|
451
|
-
try {
|
|
452
|
-
const [result] = await db.insert(users).values({
|
|
453
|
-
username: mindName,
|
|
454
|
-
password_hash: "!mind",
|
|
455
|
-
role: "mind",
|
|
456
|
-
user_type: "mind"
|
|
457
|
-
}).returning({
|
|
458
|
-
id: users.id,
|
|
459
|
-
username: users.username,
|
|
460
|
-
role: users.role,
|
|
461
|
-
user_type: users.user_type,
|
|
462
|
-
created_at: users.created_at
|
|
463
|
-
});
|
|
464
|
-
return result;
|
|
465
|
-
} catch (err) {
|
|
466
|
-
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
467
|
-
const retried = await db.select({
|
|
468
|
-
id: users.id,
|
|
469
|
-
username: users.username,
|
|
470
|
-
role: users.role,
|
|
471
|
-
user_type: users.user_type,
|
|
472
|
-
created_at: users.created_at
|
|
473
|
-
}).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
|
|
474
|
-
if (retried) return retried;
|
|
475
|
-
}
|
|
476
|
-
throw err;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
async function deleteMindUser2(mindName) {
|
|
480
|
-
const db = await getDb();
|
|
481
|
-
await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
|
|
482
|
-
}
|
|
483
|
-
async function changePassword(userId, currentPassword, newPassword) {
|
|
484
|
-
const db = await getDb();
|
|
485
|
-
const row = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
486
|
-
if (!row) return false;
|
|
487
|
-
if (!compareSync(currentPassword, row.password_hash)) return false;
|
|
488
|
-
const hash = hashSync(newPassword, 10);
|
|
489
|
-
await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
|
|
490
|
-
return true;
|
|
491
|
-
}
|
|
492
|
-
async function approveUser(id) {
|
|
493
|
-
const db = await getDb();
|
|
494
|
-
await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// src/web/middleware/auth.ts
|
|
498
394
|
function isValidDaemonToken(token) {
|
|
499
395
|
const expected = process.env.VOLUTE_DAEMON_TOKEN;
|
|
500
396
|
if (!expected || token.length !== expected.length) return false;
|
|
@@ -503,6 +399,9 @@ function isValidDaemonToken(token) {
|
|
|
503
399
|
var SESSION_MAX_AGE = 864e5;
|
|
504
400
|
var SESSION_CACHE_TTL = 5 * 60 * 1e3;
|
|
505
401
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
402
|
+
function invalidateSessionCache(sessionId) {
|
|
403
|
+
sessionCache.delete(sessionId);
|
|
404
|
+
}
|
|
506
405
|
async function createSession(userId) {
|
|
507
406
|
const db = await getDb();
|
|
508
407
|
const sessionId = crypto.randomUUID();
|
|
@@ -512,14 +411,14 @@ async function createSession(userId) {
|
|
|
512
411
|
async function deleteSession(sessionId) {
|
|
513
412
|
sessionCache.delete(sessionId);
|
|
514
413
|
const db = await getDb();
|
|
515
|
-
await db.delete(sessions).where(
|
|
414
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
516
415
|
}
|
|
517
416
|
async function getSessionUserId(sessionId) {
|
|
518
417
|
const db = await getDb();
|
|
519
|
-
const row = await db.select().from(sessions).where(
|
|
418
|
+
const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
|
|
520
419
|
if (!row) return void 0;
|
|
521
420
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
522
|
-
await db.delete(sessions).where(
|
|
421
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
523
422
|
return void 0;
|
|
524
423
|
}
|
|
525
424
|
return row.userId;
|
|
@@ -541,7 +440,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
541
440
|
if (authHeader?.startsWith("Bearer ")) {
|
|
542
441
|
const token = authHeader.slice(7);
|
|
543
442
|
if (token && isValidDaemonToken(token)) {
|
|
544
|
-
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
|
+
});
|
|
545
452
|
await next();
|
|
546
453
|
return;
|
|
547
454
|
}
|
|
@@ -572,9 +479,10 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
572
479
|
});
|
|
573
480
|
|
|
574
481
|
// src/web/server.ts
|
|
575
|
-
import { existsSync as
|
|
482
|
+
import { existsSync as existsSync13 } from "fs";
|
|
576
483
|
import { readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
577
|
-
import {
|
|
484
|
+
import { createServer as createHttpsServer } from "https";
|
|
485
|
+
import { dirname, extname as extname4, resolve as resolve17 } from "path";
|
|
578
486
|
import { serve } from "@hono/node-server";
|
|
579
487
|
|
|
580
488
|
// src/web/app.ts
|
|
@@ -584,294 +492,9 @@ import { csrf } from "hono/csrf";
|
|
|
584
492
|
import { HTTPException } from "hono/http-exception";
|
|
585
493
|
|
|
586
494
|
// src/web/api/activity.ts
|
|
587
|
-
import { desc
|
|
495
|
+
import { desc } from "drizzle-orm";
|
|
588
496
|
import { Hono } from "hono";
|
|
589
497
|
import { streamSSE } from "hono/streaming";
|
|
590
|
-
|
|
591
|
-
// src/lib/events/conversations.ts
|
|
592
|
-
import { randomUUID } from "crypto";
|
|
593
|
-
import { and as and2, desc, eq as eq3, inArray, isNull, lt as lt2, sql } from "drizzle-orm";
|
|
594
|
-
async function createConversation(mindName, channel, opts) {
|
|
595
|
-
const db = await getDb();
|
|
596
|
-
const id = randomUUID();
|
|
597
|
-
const type = opts?.type ?? "dm";
|
|
598
|
-
const name = opts?.name ?? null;
|
|
599
|
-
await db.transaction(async (tx) => {
|
|
600
|
-
await tx.insert(conversations).values({
|
|
601
|
-
id,
|
|
602
|
-
mind_name: mindName,
|
|
603
|
-
channel,
|
|
604
|
-
type,
|
|
605
|
-
name,
|
|
606
|
-
user_id: opts?.userId ?? null,
|
|
607
|
-
title: opts?.title ?? null
|
|
608
|
-
});
|
|
609
|
-
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
610
|
-
await tx.insert(conversationParticipants).values(
|
|
611
|
-
opts.participantIds.map((uid, i) => ({
|
|
612
|
-
conversation_id: id,
|
|
613
|
-
user_id: uid,
|
|
614
|
-
role: i === 0 ? "owner" : "member"
|
|
615
|
-
}))
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
fireWebhook({
|
|
620
|
-
event: "conversation_created",
|
|
621
|
-
mind: mindName ?? "",
|
|
622
|
-
data: { id, mindName, channel, type, name, title: opts?.title ?? null }
|
|
623
|
-
});
|
|
624
|
-
return {
|
|
625
|
-
id,
|
|
626
|
-
mind_name: mindName,
|
|
627
|
-
channel,
|
|
628
|
-
type,
|
|
629
|
-
name,
|
|
630
|
-
user_id: opts?.userId ?? null,
|
|
631
|
-
title: opts?.title ?? null,
|
|
632
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
633
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
async function getConversation(id) {
|
|
637
|
-
const db = await getDb();
|
|
638
|
-
const row = await db.select().from(conversations).where(eq3(conversations.id, id)).get();
|
|
639
|
-
return row ?? null;
|
|
640
|
-
}
|
|
641
|
-
async function addParticipant(conversationId, userId, role = "member") {
|
|
642
|
-
const db = await getDb();
|
|
643
|
-
await db.insert(conversationParticipants).values({
|
|
644
|
-
conversation_id: conversationId,
|
|
645
|
-
user_id: userId,
|
|
646
|
-
role
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
async function removeParticipant(conversationId, userId) {
|
|
650
|
-
const db = await getDb();
|
|
651
|
-
await db.delete(conversationParticipants).where(
|
|
652
|
-
and2(
|
|
653
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
654
|
-
eq3(conversationParticipants.user_id, userId)
|
|
655
|
-
)
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
async function getParticipants(conversationId) {
|
|
659
|
-
const db = await getDb();
|
|
660
|
-
const rows = await db.select({
|
|
661
|
-
userId: conversationParticipants.user_id,
|
|
662
|
-
username: users.username,
|
|
663
|
-
userType: users.user_type,
|
|
664
|
-
role: conversationParticipants.role
|
|
665
|
-
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(eq3(conversationParticipants.conversation_id, conversationId)).all();
|
|
666
|
-
return rows;
|
|
667
|
-
}
|
|
668
|
-
async function isParticipant(conversationId, userId) {
|
|
669
|
-
const db = await getDb();
|
|
670
|
-
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
671
|
-
and2(
|
|
672
|
-
eq3(conversationParticipants.conversation_id, conversationId),
|
|
673
|
-
eq3(conversationParticipants.user_id, userId)
|
|
674
|
-
)
|
|
675
|
-
).get();
|
|
676
|
-
return row != null;
|
|
677
|
-
}
|
|
678
|
-
async function listConversationsForUser(userId) {
|
|
679
|
-
const db = await getDb();
|
|
680
|
-
const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq3(conversationParticipants.user_id, userId)).all();
|
|
681
|
-
if (participantRows.length === 0) return [];
|
|
682
|
-
const convIds = participantRows.map((r) => r.conversation_id);
|
|
683
|
-
return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
684
|
-
}
|
|
685
|
-
async function isParticipantOrOwner(conversationId, userId) {
|
|
686
|
-
if (await isParticipant(conversationId, userId)) return true;
|
|
687
|
-
const db = await getDb();
|
|
688
|
-
const row = await db.select().from(conversations).where(and2(eq3(conversations.id, conversationId), eq3(conversations.user_id, userId))).get();
|
|
689
|
-
return row != null;
|
|
690
|
-
}
|
|
691
|
-
async function deleteConversationForUser(id, userId) {
|
|
692
|
-
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
693
|
-
await deleteConversation(id);
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
async function addMessage(conversationId, role, senderName, content) {
|
|
697
|
-
const db = await getDb();
|
|
698
|
-
const serialized = JSON.stringify(content);
|
|
699
|
-
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 });
|
|
700
|
-
await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq3(conversations.id, conversationId));
|
|
701
|
-
if (role === "user") {
|
|
702
|
-
const firstText = content.find((b) => b.type === "text");
|
|
703
|
-
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
704
|
-
if (title) {
|
|
705
|
-
await db.update(conversations).set({ title }).where(and2(eq3(conversations.id, conversationId), isNull(conversations.title)));
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
const msg = {
|
|
709
|
-
id: result.id,
|
|
710
|
-
conversation_id: conversationId,
|
|
711
|
-
role,
|
|
712
|
-
sender_name: senderName,
|
|
713
|
-
content,
|
|
714
|
-
created_at: result.created_at
|
|
715
|
-
};
|
|
716
|
-
publish(conversationId, {
|
|
717
|
-
type: "message",
|
|
718
|
-
id: msg.id,
|
|
719
|
-
role: msg.role,
|
|
720
|
-
senderName: msg.sender_name,
|
|
721
|
-
content: msg.content,
|
|
722
|
-
createdAt: msg.created_at
|
|
723
|
-
});
|
|
724
|
-
const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq3(conversations.id, conversationId)).get();
|
|
725
|
-
fireWebhook({
|
|
726
|
-
event: "message_created",
|
|
727
|
-
mind: conv?.mind_name ?? "",
|
|
728
|
-
data: {
|
|
729
|
-
conversationId,
|
|
730
|
-
messageId: result.id,
|
|
731
|
-
role,
|
|
732
|
-
senderName,
|
|
733
|
-
content: content.filter((b) => b.type !== "image"),
|
|
734
|
-
createdAt: result.created_at
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
return msg;
|
|
738
|
-
}
|
|
739
|
-
async function getMessages(conversationId) {
|
|
740
|
-
const db = await getDb();
|
|
741
|
-
const rows = await db.select().from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
742
|
-
return rows.map(parseMessageRow);
|
|
743
|
-
}
|
|
744
|
-
async function getMessagesPaginated(conversationId, opts) {
|
|
745
|
-
const db = await getDb();
|
|
746
|
-
const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
|
|
747
|
-
const conditions = [eq3(messages.conversation_id, conversationId)];
|
|
748
|
-
if (opts?.before != null) {
|
|
749
|
-
conditions.push(lt2(messages.id, opts.before));
|
|
750
|
-
}
|
|
751
|
-
const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
|
|
752
|
-
const hasMore = rows.length > limit;
|
|
753
|
-
const page = rows.slice(0, limit).reverse();
|
|
754
|
-
return {
|
|
755
|
-
messages: page.map(parseMessageRow),
|
|
756
|
-
hasMore
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function parseMessageRow(row) {
|
|
760
|
-
let content;
|
|
761
|
-
try {
|
|
762
|
-
const parsed = JSON.parse(row.content);
|
|
763
|
-
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
764
|
-
} catch {
|
|
765
|
-
content = [{ type: "text", text: row.content }];
|
|
766
|
-
}
|
|
767
|
-
return { ...row, role: row.role, content };
|
|
768
|
-
}
|
|
769
|
-
async function listConversationsWithParticipants(userId) {
|
|
770
|
-
const convs = await listConversationsForUser(userId);
|
|
771
|
-
if (convs.length === 0) return [];
|
|
772
|
-
const db = await getDb();
|
|
773
|
-
const convIds = convs.map((c) => c.id);
|
|
774
|
-
const rows = await db.select({
|
|
775
|
-
conversationId: conversationParticipants.conversation_id,
|
|
776
|
-
userId: users.id,
|
|
777
|
-
username: users.username,
|
|
778
|
-
userType: users.user_type,
|
|
779
|
-
role: conversationParticipants.role
|
|
780
|
-
}).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
781
|
-
const byConv = /* @__PURE__ */ new Map();
|
|
782
|
-
for (const r of rows) {
|
|
783
|
-
let arr = byConv.get(r.conversationId);
|
|
784
|
-
if (!arr) {
|
|
785
|
-
arr = [];
|
|
786
|
-
byConv.set(r.conversationId, arr);
|
|
787
|
-
}
|
|
788
|
-
arr.push({
|
|
789
|
-
userId: r.userId,
|
|
790
|
-
username: r.username,
|
|
791
|
-
userType: r.userType,
|
|
792
|
-
role: r.role
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
const lastMsgIds = await db.select({
|
|
796
|
-
conversationId: messages.conversation_id,
|
|
797
|
-
maxId: sql`MAX(${messages.id})`
|
|
798
|
-
}).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
|
|
799
|
-
const byLastMsg = /* @__PURE__ */ new Map();
|
|
800
|
-
if (lastMsgIds.length > 0) {
|
|
801
|
-
const msgRows = await db.select().from(messages).where(
|
|
802
|
-
inArray(
|
|
803
|
-
messages.id,
|
|
804
|
-
lastMsgIds.map((r) => r.maxId)
|
|
805
|
-
)
|
|
806
|
-
);
|
|
807
|
-
for (const m of msgRows) {
|
|
808
|
-
let text = "";
|
|
809
|
-
try {
|
|
810
|
-
const parsed = JSON.parse(m.content);
|
|
811
|
-
const blocks = Array.isArray(parsed) ? parsed : [];
|
|
812
|
-
const textBlock = blocks.find((b) => b.type === "text");
|
|
813
|
-
if (textBlock && "text" in textBlock) text = textBlock.text;
|
|
814
|
-
} catch {
|
|
815
|
-
text = m.content;
|
|
816
|
-
}
|
|
817
|
-
byLastMsg.set(m.conversation_id, {
|
|
818
|
-
role: m.role,
|
|
819
|
-
senderName: m.sender_name,
|
|
820
|
-
text,
|
|
821
|
-
createdAt: m.created_at
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return convs.map((c) => ({
|
|
826
|
-
...c,
|
|
827
|
-
participants: byConv.get(c.id) ?? [],
|
|
828
|
-
lastMessage: byLastMsg.get(c.id)
|
|
829
|
-
}));
|
|
830
|
-
}
|
|
831
|
-
async function findDMConversation(mindName, participantIds) {
|
|
832
|
-
const db = await getDb();
|
|
833
|
-
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq3(conversations.mind_name, mindName), eq3(conversations.type, "dm"))).all();
|
|
834
|
-
for (const conv of mindConvs) {
|
|
835
|
-
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq3(conversationParticipants.conversation_id, conv.id)).all();
|
|
836
|
-
if (rows.length !== 2) continue;
|
|
837
|
-
const ids = new Set(rows.map((r) => r.user_id));
|
|
838
|
-
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
839
|
-
return conv.id;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return null;
|
|
843
|
-
}
|
|
844
|
-
async function deleteConversation(id) {
|
|
845
|
-
const db = await getDb();
|
|
846
|
-
await db.delete(conversations).where(eq3(conversations.id, id));
|
|
847
|
-
}
|
|
848
|
-
async function createChannel(name, creatorId) {
|
|
849
|
-
const participantIds = creatorId ? [creatorId] : [];
|
|
850
|
-
return createConversation(null, "volute", {
|
|
851
|
-
type: "channel",
|
|
852
|
-
name,
|
|
853
|
-
title: name,
|
|
854
|
-
participantIds
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
async function getChannelByName(name) {
|
|
858
|
-
const db = await getDb();
|
|
859
|
-
const row = await db.select().from(conversations).where(and2(eq3(conversations.name, name), eq3(conversations.type, "channel"))).get();
|
|
860
|
-
return row ?? null;
|
|
861
|
-
}
|
|
862
|
-
async function listChannels() {
|
|
863
|
-
const db = await getDb();
|
|
864
|
-
return await db.select().from(conversations).where(eq3(conversations.type, "channel")).orderBy(conversations.name).all();
|
|
865
|
-
}
|
|
866
|
-
async function joinChannel(conversationId, userId) {
|
|
867
|
-
if (await isParticipant(conversationId, userId)) return;
|
|
868
|
-
await addParticipant(conversationId, userId);
|
|
869
|
-
}
|
|
870
|
-
async function leaveChannel(conversationId, userId) {
|
|
871
|
-
await removeParticipant(conversationId, userId);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// src/web/api/activity.ts
|
|
875
498
|
var app = new Hono().get("/events", async (c) => {
|
|
876
499
|
const user = c.get("user");
|
|
877
500
|
return streamSSE(c, async (stream) => {
|
|
@@ -880,7 +503,7 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
880
503
|
let recentActivity = [];
|
|
881
504
|
try {
|
|
882
505
|
const db = await getDb();
|
|
883
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
506
|
+
recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
|
|
884
507
|
recentActivity = recentActivity.map((row) => ({
|
|
885
508
|
...row,
|
|
886
509
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -915,7 +538,7 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
915
538
|
});
|
|
916
539
|
cleanups.push(unsubActivity);
|
|
917
540
|
for (const conv of conversations2) {
|
|
918
|
-
const unsubConv =
|
|
541
|
+
const unsubConv = subscribe3(conv.id, (event) => {
|
|
919
542
|
stream.writeSSE({
|
|
920
543
|
data: JSON.stringify({ event: "conversation", conversationId: conv.id, ...event })
|
|
921
544
|
}).catch((err) => {
|
|
@@ -930,8 +553,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
930
553
|
});
|
|
931
554
|
}, 15e3);
|
|
932
555
|
cleanups.push(() => clearInterval(keepAlive));
|
|
933
|
-
await new Promise((
|
|
934
|
-
stream.onAbort(() =>
|
|
556
|
+
await new Promise((resolve19) => {
|
|
557
|
+
stream.onAbort(() => resolve19());
|
|
935
558
|
});
|
|
936
559
|
} finally {
|
|
937
560
|
for (const cleanup of cleanups) {
|
|
@@ -946,6 +569,8 @@ var app = new Hono().get("/events", async (c) => {
|
|
|
946
569
|
var activity_default = app;
|
|
947
570
|
|
|
948
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";
|
|
949
574
|
import { zValidator } from "@hono/zod-validator";
|
|
950
575
|
import { Hono as Hono2 } from "hono";
|
|
951
576
|
import { deleteCookie, getCookie as getCookie2, setCookie } from "hono/cookie";
|
|
@@ -958,12 +583,71 @@ var changePasswordSchema = z.object({
|
|
|
958
583
|
currentPassword: z.string().min(1),
|
|
959
584
|
newPassword: z.string().min(1)
|
|
960
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
|
+
}
|
|
961
601
|
var authenticated = new Hono2().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
962
602
|
const user = c.get("user");
|
|
963
603
|
const { currentPassword, newPassword } = c.req.valid("json");
|
|
964
604
|
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
965
605
|
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
966
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 });
|
|
967
651
|
});
|
|
968
652
|
var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
969
653
|
const user = c.get("user");
|
|
@@ -985,8 +669,55 @@ var admin = new Hono2().use(authMiddleware).get("/users", async (c) => {
|
|
|
985
669
|
const user = c.get("user");
|
|
986
670
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
987
671
|
const id = parseInt(c.req.param("id"), 10);
|
|
672
|
+
if (Number.isNaN(id)) return c.json({ error: "Invalid user ID" }, 400);
|
|
988
673
|
await approveUser(id);
|
|
989
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 });
|
|
990
721
|
});
|
|
991
722
|
var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema), async (c) => {
|
|
992
723
|
const { username, password } = c.req.valid("json");
|
|
@@ -1023,7 +754,32 @@ var app2 = new Hono2().post("/register", zValidator("json", credentialsSchema),
|
|
|
1023
754
|
if (userId == null) return c.json({ error: "Not logged in" }, 401);
|
|
1024
755
|
const user = await getUser(userId);
|
|
1025
756
|
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
1026
|
-
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
|
+
});
|
|
1027
783
|
}).route("/", admin).route("/", authenticated);
|
|
1028
784
|
var auth_default = app2;
|
|
1029
785
|
|
|
@@ -1064,8 +820,8 @@ async function read(env, channelSlug, limit) {
|
|
|
1064
820
|
if (!res.ok) {
|
|
1065
821
|
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
1066
822
|
}
|
|
1067
|
-
const
|
|
1068
|
-
return
|
|
823
|
+
const messages = await res.json();
|
|
824
|
+
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
1069
825
|
}
|
|
1070
826
|
async function send(env, channelSlug, message, images) {
|
|
1071
827
|
const token = requireToken(env);
|
|
@@ -1297,8 +1053,8 @@ async function listConversations2(env) {
|
|
|
1297
1053
|
const userMap = /* @__PURE__ */ new Map();
|
|
1298
1054
|
const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
|
|
1299
1055
|
if (imChannels.length > 0) {
|
|
1300
|
-
const
|
|
1301
|
-
for (const u of
|
|
1056
|
+
const users = await listUsers3(env);
|
|
1057
|
+
for (const u of users) {
|
|
1302
1058
|
userMap.set(u.id, u.username);
|
|
1303
1059
|
}
|
|
1304
1060
|
}
|
|
@@ -1493,16 +1249,16 @@ __export(volute_exports, {
|
|
|
1493
1249
|
read: () => read4,
|
|
1494
1250
|
send: () => send4
|
|
1495
1251
|
});
|
|
1496
|
-
import { existsSync as
|
|
1497
|
-
import { resolve as
|
|
1252
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1253
|
+
import { resolve as resolve4 } from "path";
|
|
1498
1254
|
function getDaemonConfig() {
|
|
1499
|
-
const configPath2 =
|
|
1500
|
-
if (!
|
|
1255
|
+
const configPath2 = resolve4(voluteHome(), "daemon.json");
|
|
1256
|
+
if (!existsSync4(configPath2)) {
|
|
1501
1257
|
throw new Error("Volute daemon is not running");
|
|
1502
1258
|
}
|
|
1503
1259
|
let config;
|
|
1504
1260
|
try {
|
|
1505
|
-
config = JSON.parse(
|
|
1261
|
+
config = JSON.parse(readFileSync3(configPath2, "utf-8"));
|
|
1506
1262
|
} catch (err) {
|
|
1507
1263
|
throw new Error(`Failed to parse ${configPath2}: ${err}`);
|
|
1508
1264
|
}
|
|
@@ -1528,8 +1284,8 @@ async function read4(env, channelSlug, limit) {
|
|
|
1528
1284
|
if (!res.ok) {
|
|
1529
1285
|
throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
|
|
1530
1286
|
}
|
|
1531
|
-
const
|
|
1532
|
-
return
|
|
1287
|
+
const messages = await res.json();
|
|
1288
|
+
return messages.slice(-limit).map((m) => {
|
|
1533
1289
|
const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
|
|
1534
1290
|
return `${m.sender_name ?? m.role}: ${text}`;
|
|
1535
1291
|
}).join("\n");
|
|
@@ -1756,8 +1512,8 @@ var app3 = new Hono3().post("/:name/channels/send", requireAdmin, async (c) => {
|
|
|
1756
1512
|
return c.json({ error: `Platform ${platform} does not support listing users` }, 400);
|
|
1757
1513
|
const env = buildEnv(name);
|
|
1758
1514
|
try {
|
|
1759
|
-
const
|
|
1760
|
-
return c.json(
|
|
1515
|
+
const users = await driver.listUsers(env);
|
|
1516
|
+
return c.json(users);
|
|
1761
1517
|
} catch (err) {
|
|
1762
1518
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1763
1519
|
}
|
|
@@ -1939,14 +1695,14 @@ var sharedEnvApp = new Hono5().get("/", (c) => {
|
|
|
1939
1695
|
var env_default = app5;
|
|
1940
1696
|
|
|
1941
1697
|
// src/web/api/file-sharing.ts
|
|
1942
|
-
import { readFileSync as
|
|
1943
|
-
import { resolve as
|
|
1698
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
1699
|
+
import { resolve as resolve6 } from "path";
|
|
1944
1700
|
import { Hono as Hono6 } from "hono";
|
|
1945
1701
|
|
|
1946
1702
|
// src/lib/file-sharing.ts
|
|
1947
1703
|
import { randomBytes } from "crypto";
|
|
1948
|
-
import { existsSync as
|
|
1949
|
-
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";
|
|
1950
1706
|
function validateFilePath(filePath) {
|
|
1951
1707
|
if (!filePath) return "File path is required";
|
|
1952
1708
|
const normalized = normalize(filePath);
|
|
@@ -1959,13 +1715,13 @@ function validateFilePath(filePath) {
|
|
|
1959
1715
|
return null;
|
|
1960
1716
|
}
|
|
1961
1717
|
function configPath(dir) {
|
|
1962
|
-
return
|
|
1718
|
+
return resolve5(dir, "home", ".config", "file-sharing.json");
|
|
1963
1719
|
}
|
|
1964
1720
|
function readFileSharingConfig(dir) {
|
|
1965
1721
|
const p = configPath(dir);
|
|
1966
|
-
if (!
|
|
1722
|
+
if (!existsSync5(p)) return {};
|
|
1967
1723
|
try {
|
|
1968
|
-
return JSON.parse(
|
|
1724
|
+
return JSON.parse(readFileSync4(p, "utf-8"));
|
|
1969
1725
|
} catch (err) {
|
|
1970
1726
|
console.warn(`[file-sharing] failed to parse config at ${p}:`, err);
|
|
1971
1727
|
return {};
|
|
@@ -1973,8 +1729,8 @@ function readFileSharingConfig(dir) {
|
|
|
1973
1729
|
}
|
|
1974
1730
|
function writeFileSharingConfig(dir, config) {
|
|
1975
1731
|
const p = configPath(dir);
|
|
1976
|
-
|
|
1977
|
-
|
|
1732
|
+
mkdirSync3(resolve5(p, ".."), { recursive: true });
|
|
1733
|
+
writeFileSync3(p, `${JSON.stringify(config, null, 2)}
|
|
1978
1734
|
`);
|
|
1979
1735
|
}
|
|
1980
1736
|
function isTrustedSender(dir, sender) {
|
|
@@ -1997,7 +1753,7 @@ function removeTrust(dir, sender) {
|
|
|
1997
1753
|
writeFileSharingConfig(dir, config);
|
|
1998
1754
|
}
|
|
1999
1755
|
function pendingDir(receiver) {
|
|
2000
|
-
return
|
|
1756
|
+
return resolve5(stateDir(receiver), "pending-files");
|
|
2001
1757
|
}
|
|
2002
1758
|
function validateId(id) {
|
|
2003
1759
|
if (!id || id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
@@ -2016,8 +1772,8 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2016
1772
|
throw new Error("Invalid sender name");
|
|
2017
1773
|
}
|
|
2018
1774
|
const id = generateId(sender);
|
|
2019
|
-
const dir =
|
|
2020
|
-
|
|
1775
|
+
const dir = resolve5(pendingDir(receiver), id);
|
|
1776
|
+
mkdirSync3(dir, { recursive: true });
|
|
2021
1777
|
const metadata = {
|
|
2022
1778
|
id,
|
|
2023
1779
|
sender,
|
|
@@ -2026,22 +1782,22 @@ function stageFile(receiver, sender, filename, content, originalPath) {
|
|
|
2026
1782
|
size: content.length,
|
|
2027
1783
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2028
1784
|
};
|
|
2029
|
-
|
|
1785
|
+
writeFileSync3(resolve5(dir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}
|
|
2030
1786
|
`);
|
|
2031
|
-
|
|
1787
|
+
writeFileSync3(resolve5(dir, "data"), content);
|
|
2032
1788
|
return { id };
|
|
2033
1789
|
}
|
|
2034
1790
|
function listPending(receiver) {
|
|
2035
1791
|
const dir = pendingDir(receiver);
|
|
2036
|
-
if (!
|
|
1792
|
+
if (!existsSync5(dir)) return [];
|
|
2037
1793
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2038
1794
|
const result = [];
|
|
2039
1795
|
for (const entry of entries) {
|
|
2040
1796
|
if (!entry.isDirectory()) continue;
|
|
2041
|
-
const metaPath =
|
|
2042
|
-
if (!
|
|
1797
|
+
const metaPath = resolve5(dir, entry.name, "metadata.json");
|
|
1798
|
+
if (!existsSync5(metaPath)) continue;
|
|
2043
1799
|
try {
|
|
2044
|
-
result.push(JSON.parse(
|
|
1800
|
+
result.push(JSON.parse(readFileSync4(metaPath, "utf-8")));
|
|
2045
1801
|
} catch (err) {
|
|
2046
1802
|
console.warn(`[file-sharing] skipping malformed pending entry ${entry.name}:`, err);
|
|
2047
1803
|
}
|
|
@@ -2050,10 +1806,10 @@ function listPending(receiver) {
|
|
|
2050
1806
|
}
|
|
2051
1807
|
function getPending(receiver, id) {
|
|
2052
1808
|
validateId(id);
|
|
2053
|
-
const metaPath =
|
|
2054
|
-
if (!
|
|
1809
|
+
const metaPath = resolve5(pendingDir(receiver), id, "metadata.json");
|
|
1810
|
+
if (!existsSync5(metaPath)) return null;
|
|
2055
1811
|
try {
|
|
2056
|
-
return JSON.parse(
|
|
1812
|
+
return JSON.parse(readFileSync4(metaPath, "utf-8"));
|
|
2057
1813
|
} catch (err) {
|
|
2058
1814
|
console.warn(`[file-sharing] failed to read pending metadata for ${id}:`, err);
|
|
2059
1815
|
return null;
|
|
@@ -2068,27 +1824,27 @@ function deliverFile(receiverDir, sender, filename, content, inboxPath) {
|
|
|
2068
1824
|
if (sender.includes("/") || sender.includes("\\")) {
|
|
2069
1825
|
throw new Error("Invalid sender name");
|
|
2070
1826
|
}
|
|
2071
|
-
const destDir =
|
|
2072
|
-
|
|
2073
|
-
const destPath =
|
|
2074
|
-
|
|
1827
|
+
const destDir = resolve5(receiverDir, "home", inbox, sender);
|
|
1828
|
+
mkdirSync3(destDir, { recursive: true });
|
|
1829
|
+
const destPath = resolve5(destDir, basename(filename));
|
|
1830
|
+
writeFileSync3(destPath, content);
|
|
2075
1831
|
return join(inbox, sender, basename(filename));
|
|
2076
1832
|
}
|
|
2077
1833
|
function acceptPending(receiver, id, receiverDir) {
|
|
2078
1834
|
const meta = getPending(receiver, id);
|
|
2079
1835
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2080
|
-
const dataPath =
|
|
2081
|
-
const content =
|
|
1836
|
+
const dataPath = resolve5(pendingDir(receiver), id, "data");
|
|
1837
|
+
const content = readFileSync4(dataPath);
|
|
2082
1838
|
const config = readFileSharingConfig(receiverDir);
|
|
2083
1839
|
const inboxPath = config.inboxPath ?? "inbox";
|
|
2084
1840
|
const destPath = deliverFile(receiverDir, meta.sender, meta.filename, content, inboxPath);
|
|
2085
|
-
|
|
1841
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2086
1842
|
return { sender: meta.sender, filename: meta.filename, destPath };
|
|
2087
1843
|
}
|
|
2088
1844
|
function rejectPending(receiver, id) {
|
|
2089
1845
|
const meta = getPending(receiver, id);
|
|
2090
1846
|
if (!meta) throw new Error(`Pending file not found: ${id}`);
|
|
2091
|
-
|
|
1847
|
+
rmSync2(resolve5(pendingDir(receiver), id), { recursive: true });
|
|
2092
1848
|
return { sender: meta.sender, filename: meta.filename };
|
|
2093
1849
|
}
|
|
2094
1850
|
function formatFileSize(bytes) {
|
|
@@ -2129,7 +1885,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2129
1885
|
const pathErr = validateFilePath(body.filePath);
|
|
2130
1886
|
if (pathErr) return c.json({ error: pathErr }, 400);
|
|
2131
1887
|
const senderDir = mindDir(senderName);
|
|
2132
|
-
const filePath =
|
|
1888
|
+
const filePath = resolve6(senderDir, "home", body.filePath);
|
|
2133
1889
|
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
2134
1890
|
const stat4 = statSync(filePath, { throwIfNoEntry: false });
|
|
2135
1891
|
if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
@@ -2143,7 +1899,7 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2143
1899
|
}
|
|
2144
1900
|
let content;
|
|
2145
1901
|
try {
|
|
2146
|
-
content =
|
|
1902
|
+
content = readFileSync5(filePath);
|
|
2147
1903
|
} catch {
|
|
2148
1904
|
return c.json({ error: `File not found: ${body.filePath}` }, 404);
|
|
2149
1905
|
}
|
|
@@ -2245,19 +2001,19 @@ var app6 = new Hono6().post("/:name/files/send", async (c) => {
|
|
|
2245
2001
|
var file_sharing_default = app6;
|
|
2246
2002
|
|
|
2247
2003
|
// src/web/api/files.ts
|
|
2248
|
-
import { existsSync as
|
|
2004
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2249
2005
|
import { readdir, readFile, realpath, stat } from "fs/promises";
|
|
2250
|
-
import { extname, resolve as
|
|
2006
|
+
import { extname as extname2, resolve as resolve7 } from "path";
|
|
2251
2007
|
import { Hono as Hono7 } from "hono";
|
|
2252
2008
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
2253
|
-
var
|
|
2009
|
+
var AVATAR_MIME2 = {
|
|
2254
2010
|
".png": "image/png",
|
|
2255
2011
|
".jpg": "image/jpeg",
|
|
2256
2012
|
".jpeg": "image/jpeg",
|
|
2257
2013
|
".gif": "image/gif",
|
|
2258
2014
|
".webp": "image/webp"
|
|
2259
2015
|
};
|
|
2260
|
-
var
|
|
2016
|
+
var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
|
|
2261
2017
|
var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
2262
2018
|
const name = c.req.param("name");
|
|
2263
2019
|
const entry = findMind(name);
|
|
@@ -2265,11 +2021,11 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2265
2021
|
const dir = mindDir(name);
|
|
2266
2022
|
const config = readVoluteConfig(dir);
|
|
2267
2023
|
if (!config?.avatar) return c.json({ error: "No avatar configured" }, 404);
|
|
2268
|
-
const ext =
|
|
2269
|
-
const mime =
|
|
2024
|
+
const ext = extname2(config.avatar).toLowerCase();
|
|
2025
|
+
const mime = AVATAR_MIME2[ext];
|
|
2270
2026
|
if (!mime) return c.json({ error: "Invalid avatar extension" }, 400);
|
|
2271
|
-
const homeDir =
|
|
2272
|
-
const avatarPath =
|
|
2027
|
+
const homeDir = resolve7(dir, "home");
|
|
2028
|
+
const avatarPath = resolve7(homeDir, config.avatar);
|
|
2273
2029
|
if (!avatarPath.startsWith(`${homeDir}/`)) return c.json({ error: "Invalid avatar path" }, 400);
|
|
2274
2030
|
let realAvatarPath;
|
|
2275
2031
|
try {
|
|
@@ -2284,7 +2040,7 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2284
2040
|
}
|
|
2285
2041
|
try {
|
|
2286
2042
|
const fileStat = await stat(realAvatarPath);
|
|
2287
|
-
if (fileStat.size >
|
|
2043
|
+
if (fileStat.size > MAX_AVATAR_SIZE2) return c.json({ error: "Avatar file too large" }, 400);
|
|
2288
2044
|
const body = await readFile(realAvatarPath);
|
|
2289
2045
|
return c.body(body, 200, {
|
|
2290
2046
|
"Content-Type": mime,
|
|
@@ -2298,8 +2054,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2298
2054
|
const entry = findMind(name);
|
|
2299
2055
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2300
2056
|
const dir = mindDir(name);
|
|
2301
|
-
const homeDir =
|
|
2302
|
-
if (!
|
|
2057
|
+
const homeDir = resolve7(dir, "home");
|
|
2058
|
+
if (!existsSync6(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
2303
2059
|
const allFiles = await readdir(homeDir);
|
|
2304
2060
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
2305
2061
|
return c.json(files);
|
|
@@ -2312,8 +2068,8 @@ var app7 = new Hono7().get("/:name/avatar", async (c) => {
|
|
|
2312
2068
|
const entry = findMind(name);
|
|
2313
2069
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2314
2070
|
const dir = mindDir(name);
|
|
2315
|
-
const filePath =
|
|
2316
|
-
if (!
|
|
2071
|
+
const filePath = resolve7(dir, "home", filename);
|
|
2072
|
+
if (!existsSync6(filePath)) {
|
|
2317
2073
|
return c.json({ error: "File not found" }, 404);
|
|
2318
2074
|
}
|
|
2319
2075
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2326,19 +2082,19 @@ import { Hono as Hono8 } from "hono";
|
|
|
2326
2082
|
|
|
2327
2083
|
// src/lib/identity.ts
|
|
2328
2084
|
import { createHash, generateKeyPairSync, sign, verify } from "crypto";
|
|
2329
|
-
import { existsSync as
|
|
2330
|
-
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";
|
|
2331
2087
|
function generateIdentity(mindDir2) {
|
|
2332
|
-
const identityDir =
|
|
2333
|
-
|
|
2088
|
+
const identityDir = resolve8(mindDir2, ".mind/identity");
|
|
2089
|
+
mkdirSync4(identityDir, { recursive: true });
|
|
2334
2090
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
2335
2091
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2336
2092
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2337
2093
|
});
|
|
2338
|
-
const privatePath =
|
|
2339
|
-
const publicPath =
|
|
2340
|
-
|
|
2341
|
-
|
|
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 });
|
|
2342
2098
|
const config = readVoluteConfig(mindDir2) ?? {};
|
|
2343
2099
|
config.identity = {
|
|
2344
2100
|
privateKey: ".mind/identity/private.pem",
|
|
@@ -2351,17 +2107,17 @@ function getPrivateKey(mindDir2) {
|
|
|
2351
2107
|
const config = readVoluteConfig(mindDir2);
|
|
2352
2108
|
const relPath = config?.identity?.privateKey;
|
|
2353
2109
|
if (!relPath) return null;
|
|
2354
|
-
const fullPath =
|
|
2355
|
-
if (!
|
|
2356
|
-
return
|
|
2110
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2111
|
+
if (!existsSync7(fullPath)) return null;
|
|
2112
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2357
2113
|
}
|
|
2358
2114
|
function getPublicKey(mindDir2) {
|
|
2359
2115
|
const config = readVoluteConfig(mindDir2);
|
|
2360
2116
|
const relPath = config?.identity?.publicKey;
|
|
2361
2117
|
if (!relPath) return null;
|
|
2362
|
-
const fullPath =
|
|
2363
|
-
if (!
|
|
2364
|
-
return
|
|
2118
|
+
const fullPath = resolve8(mindDir2, relPath);
|
|
2119
|
+
if (!existsSync7(fullPath)) return null;
|
|
2120
|
+
return readFileSync6(fullPath, "utf-8");
|
|
2365
2121
|
}
|
|
2366
2122
|
function getFingerprint(publicKeyPem) {
|
|
2367
2123
|
return createHash("sha256").update(publicKeyPem).digest("hex");
|
|
@@ -2414,16 +2170,16 @@ var keys_default = app8;
|
|
|
2414
2170
|
|
|
2415
2171
|
// src/web/api/logs.ts
|
|
2416
2172
|
import { spawn } from "child_process";
|
|
2417
|
-
import { existsSync as
|
|
2418
|
-
import { resolve as
|
|
2173
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2174
|
+
import { resolve as resolve9 } from "path";
|
|
2419
2175
|
import { Hono as Hono9 } from "hono";
|
|
2420
2176
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
2421
2177
|
var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
2422
2178
|
const name = c.req.param("name");
|
|
2423
2179
|
const entry = findMind(name);
|
|
2424
2180
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2425
|
-
const logFile =
|
|
2426
|
-
if (!
|
|
2181
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2182
|
+
if (!existsSync8(logFile)) {
|
|
2427
2183
|
return c.json({ error: "No log file found" }, 404);
|
|
2428
2184
|
}
|
|
2429
2185
|
return streamSSE2(c, async (stream) => {
|
|
@@ -2441,17 +2197,17 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2441
2197
|
stream.onAbort(() => {
|
|
2442
2198
|
tail.kill();
|
|
2443
2199
|
});
|
|
2444
|
-
await new Promise((
|
|
2445
|
-
tail.on("exit",
|
|
2446
|
-
stream.onAbort(
|
|
2200
|
+
await new Promise((resolve19) => {
|
|
2201
|
+
tail.on("exit", resolve19);
|
|
2202
|
+
stream.onAbort(resolve19);
|
|
2447
2203
|
});
|
|
2448
2204
|
});
|
|
2449
2205
|
}).get("/:name/logs/tail", async (c) => {
|
|
2450
2206
|
const name = c.req.param("name");
|
|
2451
2207
|
const entry = findMind(name);
|
|
2452
2208
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2453
|
-
const logFile =
|
|
2454
|
-
if (!
|
|
2209
|
+
const logFile = resolve9(stateDir(name), "logs", "mind.log");
|
|
2210
|
+
if (!existsSync8(logFile)) {
|
|
2455
2211
|
return c.json({ error: "No log file found" }, 404);
|
|
2456
2212
|
}
|
|
2457
2213
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -2461,8 +2217,8 @@ var app9 = new Hono9().get("/:name/logs", async (c) => {
|
|
|
2461
2217
|
tail.stdout.on("data", (data) => {
|
|
2462
2218
|
output += data.toString();
|
|
2463
2219
|
});
|
|
2464
|
-
await new Promise((
|
|
2465
|
-
tail.on("exit",
|
|
2220
|
+
await new Promise((resolve19) => {
|
|
2221
|
+
tail.on("exit", resolve19);
|
|
2466
2222
|
});
|
|
2467
2223
|
return c.text(output);
|
|
2468
2224
|
});
|
|
@@ -2552,33 +2308,33 @@ var mind_skills_default = app10;
|
|
|
2552
2308
|
// src/web/api/minds.ts
|
|
2553
2309
|
import {
|
|
2554
2310
|
cpSync,
|
|
2555
|
-
existsSync as
|
|
2556
|
-
mkdirSync as
|
|
2311
|
+
existsSync as existsSync10,
|
|
2312
|
+
mkdirSync as mkdirSync6,
|
|
2557
2313
|
readdirSync as readdirSync4,
|
|
2558
|
-
readFileSync as
|
|
2559
|
-
rmSync as
|
|
2560
|
-
writeFileSync as
|
|
2314
|
+
readFileSync as readFileSync9,
|
|
2315
|
+
rmSync as rmSync4,
|
|
2316
|
+
writeFileSync as writeFileSync7
|
|
2561
2317
|
} from "fs";
|
|
2562
|
-
import { resolve as
|
|
2318
|
+
import { resolve as resolve12 } from "path";
|
|
2563
2319
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2564
|
-
import { and
|
|
2320
|
+
import { and, desc as desc2, eq as eq2, sql } from "drizzle-orm";
|
|
2565
2321
|
import { Hono as Hono11 } from "hono";
|
|
2566
2322
|
import { z as z3 } from "zod";
|
|
2567
2323
|
|
|
2568
2324
|
// src/lib/consolidate.ts
|
|
2569
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2570
|
-
import { resolve as
|
|
2325
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2326
|
+
import { resolve as resolve10 } from "path";
|
|
2571
2327
|
async function consolidateMemory(mindDir2) {
|
|
2572
|
-
const soulPath =
|
|
2573
|
-
const memoryPath =
|
|
2574
|
-
const memoryDir =
|
|
2575
|
-
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");
|
|
2576
2332
|
const logs = [];
|
|
2577
2333
|
try {
|
|
2578
2334
|
const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
2579
2335
|
for (const filename of files) {
|
|
2580
2336
|
const date = filename.replace(".md", "");
|
|
2581
|
-
const content2 =
|
|
2337
|
+
const content2 = readFileSync7(resolve10(memoryDir, filename), "utf-8").trim();
|
|
2582
2338
|
if (content2) {
|
|
2583
2339
|
logs.push(`### ${date}
|
|
2584
2340
|
|
|
@@ -2628,7 +2384,7 @@ ${content2}`);
|
|
|
2628
2384
|
const data = await res.json();
|
|
2629
2385
|
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
2630
2386
|
if (content) {
|
|
2631
|
-
|
|
2387
|
+
writeFileSync5(memoryPath, `${content}
|
|
2632
2388
|
`);
|
|
2633
2389
|
console.log("MEMORY.md created successfully.");
|
|
2634
2390
|
} else {
|
|
@@ -2637,28 +2393,28 @@ ${content2}`);
|
|
|
2637
2393
|
}
|
|
2638
2394
|
|
|
2639
2395
|
// src/lib/convert-session.ts
|
|
2640
|
-
import { randomUUID
|
|
2641
|
-
import { mkdirSync as
|
|
2396
|
+
import { randomUUID } from "crypto";
|
|
2397
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
2642
2398
|
import { homedir } from "os";
|
|
2643
|
-
import { resolve as
|
|
2399
|
+
import { resolve as resolve11 } from "path";
|
|
2644
2400
|
function convertSession(opts) {
|
|
2645
|
-
const lines =
|
|
2646
|
-
const sessionId =
|
|
2401
|
+
const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
|
|
2402
|
+
const sessionId = randomUUID();
|
|
2647
2403
|
const idMap = /* @__PURE__ */ new Map();
|
|
2648
|
-
const
|
|
2404
|
+
const messages = [];
|
|
2649
2405
|
for (const line of lines) {
|
|
2650
2406
|
const event = JSON.parse(line);
|
|
2651
2407
|
if (event.type === "message" && event.message) {
|
|
2652
|
-
|
|
2408
|
+
messages.push(event);
|
|
2653
2409
|
}
|
|
2654
2410
|
}
|
|
2655
2411
|
const sdkEvents = [];
|
|
2656
2412
|
let lastSdkUuid = null;
|
|
2657
|
-
for (let i = 0; i <
|
|
2658
|
-
const event =
|
|
2413
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2414
|
+
const event = messages[i];
|
|
2659
2415
|
const msg = event.message;
|
|
2660
2416
|
if (msg.role === "user") {
|
|
2661
|
-
const uuid =
|
|
2417
|
+
const uuid = randomUUID();
|
|
2662
2418
|
idMap.set(event.id, uuid);
|
|
2663
2419
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2664
2420
|
const sdkEvent = {
|
|
@@ -2682,7 +2438,7 @@ function convertSession(opts) {
|
|
|
2682
2438
|
} else if (msg.role === "assistant") {
|
|
2683
2439
|
const content = convertAssistantContent(msg.content);
|
|
2684
2440
|
if (content.length === 0) continue;
|
|
2685
|
-
const uuid =
|
|
2441
|
+
const uuid = randomUUID();
|
|
2686
2442
|
idMap.set(event.id, uuid);
|
|
2687
2443
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
2688
2444
|
const stopReason = mapStopReason(msg.stopReason);
|
|
@@ -2697,12 +2453,12 @@ function convertSession(opts) {
|
|
|
2697
2453
|
isSidechain: false,
|
|
2698
2454
|
userType: "external",
|
|
2699
2455
|
type: "assistant",
|
|
2700
|
-
requestId: `req_imported_${
|
|
2456
|
+
requestId: `req_imported_${randomUUID()}`,
|
|
2701
2457
|
message: {
|
|
2702
2458
|
role: "assistant",
|
|
2703
2459
|
content,
|
|
2704
2460
|
type: "message",
|
|
2705
|
-
id: `msg_imported_${
|
|
2461
|
+
id: `msg_imported_${randomUUID()}`,
|
|
2706
2462
|
model: mapModel(msg.model),
|
|
2707
2463
|
stop_reason: stopReason,
|
|
2708
2464
|
stop_sequence: null,
|
|
@@ -2716,8 +2472,8 @@ function convertSession(opts) {
|
|
|
2716
2472
|
let lastToolResultId = event.id;
|
|
2717
2473
|
let lastTimestamp = event.timestamp;
|
|
2718
2474
|
let j = i;
|
|
2719
|
-
while (j <
|
|
2720
|
-
const tr =
|
|
2475
|
+
while (j < messages.length && messages[j].message.role === "toolResult") {
|
|
2476
|
+
const tr = messages[j];
|
|
2721
2477
|
const trMsg = tr.message;
|
|
2722
2478
|
lastToolResultId = tr.id;
|
|
2723
2479
|
lastTimestamp = tr.timestamp;
|
|
@@ -2730,7 +2486,7 @@ function convertSession(opts) {
|
|
|
2730
2486
|
j++;
|
|
2731
2487
|
}
|
|
2732
2488
|
i = j - 1;
|
|
2733
|
-
const uuid =
|
|
2489
|
+
const uuid = randomUUID();
|
|
2734
2490
|
idMap.set(lastToolResultId, uuid);
|
|
2735
2491
|
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
|
|
2736
2492
|
const sdkEvent = {
|
|
@@ -2756,10 +2512,10 @@ function convertSession(opts) {
|
|
|
2756
2512
|
}
|
|
2757
2513
|
}
|
|
2758
2514
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
2759
|
-
const sdkDir =
|
|
2760
|
-
|
|
2761
|
-
const sdkPath =
|
|
2762
|
-
|
|
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")}
|
|
2763
2519
|
`);
|
|
2764
2520
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
2765
2521
|
return sessionId;
|
|
@@ -2810,36 +2566,8 @@ function convertAssistantContent(content) {
|
|
|
2810
2566
|
return result;
|
|
2811
2567
|
}
|
|
2812
2568
|
|
|
2813
|
-
// src/lib/events/mind-events.ts
|
|
2814
|
-
var subscribers = /* @__PURE__ */ new Map();
|
|
2815
|
-
function subscribe3(mind, callback) {
|
|
2816
|
-
let set = subscribers.get(mind);
|
|
2817
|
-
if (!set) {
|
|
2818
|
-
set = /* @__PURE__ */ new Set();
|
|
2819
|
-
subscribers.set(mind, set);
|
|
2820
|
-
}
|
|
2821
|
-
set.add(callback);
|
|
2822
|
-
return () => {
|
|
2823
|
-
set.delete(callback);
|
|
2824
|
-
if (set.size === 0) subscribers.delete(mind);
|
|
2825
|
-
};
|
|
2826
|
-
}
|
|
2827
|
-
function publish2(mind, event) {
|
|
2828
|
-
const set = subscribers.get(mind);
|
|
2829
|
-
if (!set) return;
|
|
2830
|
-
for (const cb of set) {
|
|
2831
|
-
try {
|
|
2832
|
-
cb(event);
|
|
2833
|
-
} catch (err) {
|
|
2834
|
-
console.error("[mind-events] subscriber threw:", err);
|
|
2835
|
-
set.delete(cb);
|
|
2836
|
-
if (set.size === 0) subscribers.delete(mind);
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
2569
|
// src/lib/variant-cleanup.ts
|
|
2842
|
-
import { existsSync as
|
|
2570
|
+
import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
|
|
2843
2571
|
async function cleanupVariant(mindName, variantName, projectRoot, variantPath, opts) {
|
|
2844
2572
|
if (opts?.stop) {
|
|
2845
2573
|
try {
|
|
@@ -2847,11 +2575,11 @@ async function cleanupVariant(mindName, variantName, projectRoot, variantPath, o
|
|
|
2847
2575
|
} catch {
|
|
2848
2576
|
}
|
|
2849
2577
|
}
|
|
2850
|
-
if (
|
|
2578
|
+
if (existsSync9(variantPath)) {
|
|
2851
2579
|
try {
|
|
2852
2580
|
await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
|
|
2853
2581
|
} catch {
|
|
2854
|
-
|
|
2582
|
+
rmSync3(variantPath, { recursive: true, force: true });
|
|
2855
2583
|
try {
|
|
2856
2584
|
await gitExec(["worktree", "prune"], { cwd: projectRoot });
|
|
2857
2585
|
} catch {
|
|
@@ -2878,7 +2606,7 @@ async function getMindStatus(name, port) {
|
|
|
2878
2606
|
const manager = getMindManager();
|
|
2879
2607
|
let status = "stopped";
|
|
2880
2608
|
try {
|
|
2881
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
2609
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
2882
2610
|
if (getSleepManagerIfReady()?.isSleeping(name)) {
|
|
2883
2611
|
status = "sleeping";
|
|
2884
2612
|
}
|
|
@@ -2936,7 +2664,7 @@ async function initTemplateBranch(projectRoot, composedDir, manifest, mindName,
|
|
|
2936
2664
|
await gitExec(["commit", "-m", "initial commit"], opts);
|
|
2937
2665
|
}
|
|
2938
2666
|
async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
2939
|
-
const tempWorktree =
|
|
2667
|
+
const tempWorktree = resolve12(projectRoot, ".variants", "_template_update");
|
|
2940
2668
|
let branchExists = false;
|
|
2941
2669
|
try {
|
|
2942
2670
|
await gitExec(["rev-parse", "--verify", TEMPLATE_BRANCH], { cwd: projectRoot });
|
|
@@ -2947,8 +2675,8 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2947
2675
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2948
2676
|
} catch {
|
|
2949
2677
|
}
|
|
2950
|
-
if (
|
|
2951
|
-
|
|
2678
|
+
if (existsSync10(tempWorktree)) {
|
|
2679
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2952
2680
|
}
|
|
2953
2681
|
const templatesRoot = findTemplatesRoot();
|
|
2954
2682
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -2968,9 +2696,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2968
2696
|
});
|
|
2969
2697
|
}
|
|
2970
2698
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
2971
|
-
const initDir =
|
|
2972
|
-
if (
|
|
2973
|
-
|
|
2699
|
+
const initDir = resolve12(tempWorktree, ".init");
|
|
2700
|
+
if (existsSync10(initDir)) {
|
|
2701
|
+
rmSync4(initDir, { recursive: true, force: true });
|
|
2974
2702
|
}
|
|
2975
2703
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
2976
2704
|
try {
|
|
@@ -2983,10 +2711,10 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2983
2711
|
await gitExec(["worktree", "remove", "--force", tempWorktree], { cwd: projectRoot });
|
|
2984
2712
|
} catch {
|
|
2985
2713
|
}
|
|
2986
|
-
if (
|
|
2987
|
-
|
|
2714
|
+
if (existsSync10(tempWorktree)) {
|
|
2715
|
+
rmSync4(tempWorktree, { recursive: true, force: true });
|
|
2988
2716
|
}
|
|
2989
|
-
|
|
2717
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
2990
2718
|
}
|
|
2991
2719
|
}
|
|
2992
2720
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -3009,14 +2737,14 @@ async function mergeTemplateBranch(worktreeDir) {
|
|
|
3009
2737
|
async function npmInstallAsMind(cwd, mindName) {
|
|
3010
2738
|
if (isIsolationEnabled()) {
|
|
3011
2739
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
3012
|
-
await exec(cmd, args, { cwd, env: { ...process.env, HOME:
|
|
2740
|
+
await exec(cmd, args, { cwd, env: { ...process.env, HOME: resolve12(cwd, "home") } });
|
|
3013
2741
|
} else {
|
|
3014
2742
|
await exec("npm", ["install"], { cwd });
|
|
3015
2743
|
}
|
|
3016
2744
|
}
|
|
3017
2745
|
async function importFromArchive(c, tempDir, nameOverride, manifest) {
|
|
3018
|
-
const extractedMindDir =
|
|
3019
|
-
if (!
|
|
2746
|
+
const extractedMindDir = resolve12(tempDir, "mind");
|
|
2747
|
+
if (!existsSync10(extractedMindDir)) {
|
|
3020
2748
|
return c.json({ error: "Invalid archive: missing mind/ directory" }, 400);
|
|
3021
2749
|
}
|
|
3022
2750
|
if (!manifest?.includes || !manifest.name || !manifest.template) {
|
|
@@ -3034,21 +2762,21 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3034
2762
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3035
2763
|
ensureVoluteHome();
|
|
3036
2764
|
const dest = mindDir(name);
|
|
3037
|
-
if (
|
|
2765
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3038
2766
|
try {
|
|
3039
2767
|
cpSync(extractedMindDir, dest, { recursive: true });
|
|
3040
2768
|
if (!manifest.includes.identity) {
|
|
3041
2769
|
generateIdentity(dest);
|
|
3042
2770
|
}
|
|
3043
2771
|
const state = stateDir(name);
|
|
3044
|
-
|
|
3045
|
-
const channelsJson =
|
|
3046
|
-
if (
|
|
3047
|
-
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"));
|
|
3048
2776
|
}
|
|
3049
|
-
const envJson =
|
|
3050
|
-
if (
|
|
3051
|
-
cpSync(envJson,
|
|
2777
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2778
|
+
if (existsSync10(envJson)) {
|
|
2779
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3052
2780
|
}
|
|
3053
2781
|
const port = nextPort();
|
|
3054
2782
|
addMind(name, port, manifest.stage, manifest.template);
|
|
@@ -3057,36 +2785,36 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
|
|
|
3057
2785
|
} catch (err) {
|
|
3058
2786
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3059
2787
|
}
|
|
3060
|
-
const homeDir =
|
|
2788
|
+
const homeDir = resolve12(dest, "home");
|
|
3061
2789
|
ensureVoluteGroup();
|
|
3062
2790
|
createMindUser(name, homeDir);
|
|
3063
2791
|
chownMindDir(dest, name);
|
|
3064
2792
|
await npmInstallAsMind(dest, name);
|
|
3065
2793
|
await importHistoryFromArchive(name, tempDir);
|
|
3066
2794
|
importSessionsFromArchive(dest, tempDir);
|
|
3067
|
-
if (!
|
|
2795
|
+
if (!existsSync10(resolve12(dest, ".git"))) {
|
|
3068
2796
|
try {
|
|
3069
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
2797
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3070
2798
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3071
2799
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3072
2800
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3073
2801
|
await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
|
|
3074
2802
|
} catch (err) {
|
|
3075
2803
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3076
|
-
|
|
2804
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3077
2805
|
}
|
|
3078
2806
|
}
|
|
3079
2807
|
chownMindDir(dest, name);
|
|
3080
|
-
|
|
2808
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3081
2809
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3082
2810
|
} catch (err) {
|
|
3083
|
-
if (
|
|
2811
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3084
2812
|
try {
|
|
3085
2813
|
removeMind(name);
|
|
3086
2814
|
} catch (cleanupErr) {
|
|
3087
2815
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3088
2816
|
}
|
|
3089
|
-
|
|
2817
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3090
2818
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3091
2819
|
}
|
|
3092
2820
|
}
|
|
@@ -3097,7 +2825,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3097
2825
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3098
2826
|
ensureVoluteHome();
|
|
3099
2827
|
const dest = mindDir(name);
|
|
3100
|
-
if (
|
|
2828
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3101
2829
|
const templatesRoot = findTemplatesRoot();
|
|
3102
2830
|
const { composedDir, manifest: templateManifest } = composeTemplate(
|
|
3103
2831
|
templatesRoot,
|
|
@@ -3106,40 +2834,40 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3106
2834
|
try {
|
|
3107
2835
|
copyTemplateToDir(composedDir, dest, name, templateManifest);
|
|
3108
2836
|
applyInitFiles(dest);
|
|
3109
|
-
const extractedHome =
|
|
3110
|
-
if (
|
|
3111
|
-
cpSync(extractedHome,
|
|
2837
|
+
const extractedHome = resolve12(extractedMindDir, "home");
|
|
2838
|
+
if (existsSync10(extractedHome)) {
|
|
2839
|
+
cpSync(extractedHome, resolve12(dest, "home"), { recursive: true });
|
|
3112
2840
|
}
|
|
3113
|
-
const extractedMindInternal =
|
|
3114
|
-
if (
|
|
3115
|
-
cpSync(extractedMindInternal,
|
|
2841
|
+
const extractedMindInternal = resolve12(extractedMindDir, ".mind");
|
|
2842
|
+
if (existsSync10(extractedMindInternal)) {
|
|
2843
|
+
cpSync(extractedMindInternal, resolve12(dest, ".mind"), { recursive: true });
|
|
3116
2844
|
}
|
|
3117
|
-
const identityDir =
|
|
2845
|
+
const identityDir = resolve12(dest, ".mind/identity");
|
|
3118
2846
|
let publicKeyPem;
|
|
3119
|
-
if (!manifest.includes.identity || !
|
|
2847
|
+
if (!manifest.includes.identity || !existsSync10(resolve12(identityDir, "private.pem"))) {
|
|
3120
2848
|
({ publicKeyPem } = generateIdentity(dest));
|
|
3121
2849
|
} else {
|
|
3122
|
-
publicKeyPem =
|
|
2850
|
+
publicKeyPem = readFileSync9(resolve12(identityDir, "public.pem"), "utf-8");
|
|
3123
2851
|
}
|
|
3124
|
-
const promptsPath =
|
|
3125
|
-
if (!
|
|
2852
|
+
const promptsPath = resolve12(dest, "home/.config/prompts.json");
|
|
2853
|
+
if (!existsSync10(promptsPath)) {
|
|
3126
2854
|
const mindPrompts = await getMindPromptDefaults();
|
|
3127
|
-
|
|
2855
|
+
writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
|
|
3128
2856
|
`);
|
|
3129
2857
|
}
|
|
3130
2858
|
const state = stateDir(name);
|
|
3131
|
-
|
|
3132
|
-
const channelsJson =
|
|
3133
|
-
if (
|
|
3134
|
-
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"));
|
|
3135
2863
|
}
|
|
3136
|
-
const envJson =
|
|
3137
|
-
if (
|
|
3138
|
-
cpSync(envJson,
|
|
2864
|
+
const envJson = resolve12(tempDir, "state/env.json");
|
|
2865
|
+
if (existsSync10(envJson)) {
|
|
2866
|
+
cpSync(envJson, resolve12(state, "env.json"));
|
|
3139
2867
|
}
|
|
3140
2868
|
const port = nextPort();
|
|
3141
2869
|
addMind(name, port, manifest.stage, manifest.template);
|
|
3142
|
-
const homeDir =
|
|
2870
|
+
const homeDir = resolve12(dest, "home");
|
|
3143
2871
|
ensureVoluteGroup();
|
|
3144
2872
|
createMindUser(name, homeDir);
|
|
3145
2873
|
chownMindDir(dest, name);
|
|
@@ -3152,7 +2880,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3152
2880
|
await initTemplateBranch(dest, composedDir, templateManifest, name, env);
|
|
3153
2881
|
} catch (err) {
|
|
3154
2882
|
logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
|
|
3155
|
-
|
|
2883
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3156
2884
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3157
2885
|
}
|
|
3158
2886
|
try {
|
|
@@ -3176,7 +2904,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3176
2904
|
publishPublicKey(name, publicKeyPem).catch(
|
|
3177
2905
|
(err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
|
|
3178
2906
|
);
|
|
3179
|
-
|
|
2907
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3180
2908
|
return c.json({
|
|
3181
2909
|
ok: true,
|
|
3182
2910
|
name,
|
|
@@ -3187,24 +2915,24 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
|
|
|
3187
2915
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3188
2916
|
});
|
|
3189
2917
|
} catch (err) {
|
|
3190
|
-
if (
|
|
2918
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3191
2919
|
try {
|
|
3192
2920
|
removeMind(name);
|
|
3193
2921
|
} catch (cleanupErr) {
|
|
3194
2922
|
logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
|
|
3195
2923
|
}
|
|
3196
|
-
|
|
2924
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3197
2925
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3198
2926
|
} finally {
|
|
3199
|
-
|
|
2927
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3200
2928
|
}
|
|
3201
2929
|
}
|
|
3202
2930
|
async function importHistoryFromArchive(name, tempDir) {
|
|
3203
|
-
const historyJsonl =
|
|
3204
|
-
if (!
|
|
2931
|
+
const historyJsonl = resolve12(tempDir, "history.jsonl");
|
|
2932
|
+
if (!existsSync10(historyJsonl)) return;
|
|
3205
2933
|
try {
|
|
3206
2934
|
const db = await getDb();
|
|
3207
|
-
const lines =
|
|
2935
|
+
const lines = readFileSync9(historyJsonl, "utf-8").trim().split("\n");
|
|
3208
2936
|
let imported = 0;
|
|
3209
2937
|
let failed = 0;
|
|
3210
2938
|
for (const line of lines) {
|
|
@@ -3240,13 +2968,13 @@ async function importHistoryFromArchive(name, tempDir) {
|
|
|
3240
2968
|
}
|
|
3241
2969
|
}
|
|
3242
2970
|
function importSessionsFromArchive(dest, tempDir) {
|
|
3243
|
-
const sessionsDir =
|
|
3244
|
-
if (!
|
|
2971
|
+
const sessionsDir = resolve12(tempDir, "sessions");
|
|
2972
|
+
if (!existsSync10(sessionsDir)) return;
|
|
3245
2973
|
try {
|
|
3246
|
-
const destSessions =
|
|
3247
|
-
|
|
2974
|
+
const destSessions = resolve12(dest, ".mind/sessions");
|
|
2975
|
+
mkdirSync6(destSessions, { recursive: true });
|
|
3248
2976
|
for (const file of readdirSync4(sessionsDir)) {
|
|
3249
|
-
cpSync(
|
|
2977
|
+
cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
|
|
3250
2978
|
}
|
|
3251
2979
|
} catch (err) {
|
|
3252
2980
|
logger_default.error("Failed to import sessions from archive", logger_default.errorData(err));
|
|
@@ -3269,7 +2997,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3269
2997
|
if (findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
|
|
3270
2998
|
ensureVoluteHome();
|
|
3271
2999
|
const dest = mindDir(name);
|
|
3272
|
-
if (
|
|
3000
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3273
3001
|
const templatesRoot = findTemplatesRoot();
|
|
3274
3002
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3275
3003
|
try {
|
|
@@ -3283,15 +3011,15 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3283
3011
|
writeVoluteConfig(dest, seedConfig);
|
|
3284
3012
|
}
|
|
3285
3013
|
if (body.model) {
|
|
3286
|
-
const configPath2 =
|
|
3287
|
-
const existing =
|
|
3014
|
+
const configPath2 = resolve12(dest, "home/.config/config.json");
|
|
3015
|
+
const existing = existsSync10(configPath2) ? JSON.parse(readFileSync9(configPath2, "utf-8")) : {};
|
|
3288
3016
|
existing.model = body.model;
|
|
3289
|
-
|
|
3017
|
+
writeFileSync7(configPath2, `${JSON.stringify(existing, null, 2)}
|
|
3290
3018
|
`);
|
|
3291
3019
|
}
|
|
3292
3020
|
const mindPrompts = await getMindPromptDefaults();
|
|
3293
|
-
|
|
3294
|
-
|
|
3021
|
+
writeFileSync7(
|
|
3022
|
+
resolve12(dest, "home/.config/prompts.json"),
|
|
3295
3023
|
`${JSON.stringify(mindPrompts, null, 2)}
|
|
3296
3024
|
`
|
|
3297
3025
|
);
|
|
@@ -3302,7 +3030,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3302
3030
|
} catch (err) {
|
|
3303
3031
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3304
3032
|
}
|
|
3305
|
-
const homeDir =
|
|
3033
|
+
const homeDir = resolve12(dest, "home");
|
|
3306
3034
|
ensureVoluteGroup();
|
|
3307
3035
|
createMindUser(name, homeDir);
|
|
3308
3036
|
chownMindDir(dest, name);
|
|
@@ -3315,7 +3043,7 @@ var app11 = new Hono11().post("/", requireAdmin, zValidator3("json", createMindS
|
|
|
3315
3043
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
3316
3044
|
} catch (err) {
|
|
3317
3045
|
logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
|
|
3318
|
-
|
|
3046
|
+
rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
|
|
3319
3047
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
3320
3048
|
}
|
|
3321
3049
|
try {
|
|
@@ -3330,7 +3058,7 @@ The human who planted you described you as: "${body.description}"
|
|
|
3330
3058
|
` : "";
|
|
3331
3059
|
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
3332
3060
|
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
3333
|
-
|
|
3061
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
|
|
3334
3062
|
}
|
|
3335
3063
|
const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : STANDARD_SKILLS);
|
|
3336
3064
|
const skillWarnings = [];
|
|
@@ -3345,11 +3073,11 @@ The human who planted you described you as: "${body.description}"
|
|
|
3345
3073
|
if (body.stage !== "seed") {
|
|
3346
3074
|
const customSoul = await getPromptIfCustom("default_soul");
|
|
3347
3075
|
if (customSoul) {
|
|
3348
|
-
|
|
3076
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3349
3077
|
}
|
|
3350
3078
|
const customMemory = await getPromptIfCustom("default_memory");
|
|
3351
3079
|
if (customMemory) {
|
|
3352
|
-
|
|
3080
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
|
|
3353
3081
|
}
|
|
3354
3082
|
}
|
|
3355
3083
|
publishPublicKey(name, publicKeyPem).catch(
|
|
@@ -3376,14 +3104,14 @@ The human who planted you described you as: "${body.description}"
|
|
|
3376
3104
|
...skillWarnings.length > 0 && { skillWarnings }
|
|
3377
3105
|
});
|
|
3378
3106
|
} catch (err) {
|
|
3379
|
-
if (
|
|
3107
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3380
3108
|
try {
|
|
3381
3109
|
removeMind(name);
|
|
3382
3110
|
} catch {
|
|
3383
3111
|
}
|
|
3384
3112
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
3385
3113
|
} finally {
|
|
3386
|
-
|
|
3114
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3387
3115
|
}
|
|
3388
3116
|
}).post("/import", requireAdmin, async (c) => {
|
|
3389
3117
|
let body;
|
|
@@ -3396,13 +3124,13 @@ The human who planted you described you as: "${body.description}"
|
|
|
3396
3124
|
return importFromArchive(c, body.archivePath, body.name, body.manifest);
|
|
3397
3125
|
}
|
|
3398
3126
|
const wsDir = body.workspacePath;
|
|
3399
|
-
if (!wsDir || !
|
|
3127
|
+
if (!wsDir || !existsSync10(resolve12(wsDir, "SOUL.md")) || !existsSync10(resolve12(wsDir, "IDENTITY.md"))) {
|
|
3400
3128
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
3401
3129
|
}
|
|
3402
|
-
const soul =
|
|
3403
|
-
const identity =
|
|
3404
|
-
const userPath =
|
|
3405
|
-
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") : "";
|
|
3406
3134
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
3407
3135
|
const template = body.template ?? "claude";
|
|
3408
3136
|
const nameErr = validateMindName(name);
|
|
@@ -3422,33 +3150,33 @@ ${user.trimEnd()}
|
|
|
3422
3150
|
` : "";
|
|
3423
3151
|
ensureVoluteHome();
|
|
3424
3152
|
const dest = mindDir(name);
|
|
3425
|
-
if (
|
|
3153
|
+
if (existsSync10(dest)) return c.json({ error: "Mind directory already exists" }, 409);
|
|
3426
3154
|
const templatesRoot = findTemplatesRoot();
|
|
3427
3155
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
3428
3156
|
try {
|
|
3429
3157
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
3430
3158
|
applyInitFiles(dest);
|
|
3431
3159
|
const { publicKeyPem: importPublicKey } = generateIdentity(dest);
|
|
3432
|
-
|
|
3433
|
-
const wsMemoryPath =
|
|
3434
|
-
const hasMemory =
|
|
3160
|
+
writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
|
|
3161
|
+
const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
|
|
3162
|
+
const hasMemory = existsSync10(wsMemoryPath);
|
|
3435
3163
|
if (hasMemory) {
|
|
3436
|
-
const existingMemory =
|
|
3437
|
-
|
|
3438
|
-
|
|
3164
|
+
const existingMemory = readFileSync9(wsMemoryPath, "utf-8");
|
|
3165
|
+
writeFileSync7(
|
|
3166
|
+
resolve12(dest, "home/MEMORY.md"),
|
|
3439
3167
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
3440
3168
|
);
|
|
3441
3169
|
} else if (user) {
|
|
3442
|
-
|
|
3170
|
+
writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
3443
3171
|
`);
|
|
3444
3172
|
}
|
|
3445
|
-
const wsMemoryDir =
|
|
3173
|
+
const wsMemoryDir = resolve12(wsDir, "memory");
|
|
3446
3174
|
let dailyLogCount = 0;
|
|
3447
|
-
if (
|
|
3448
|
-
const destMemoryDir =
|
|
3175
|
+
if (existsSync10(wsMemoryDir)) {
|
|
3176
|
+
const destMemoryDir = resolve12(dest, "home/memory");
|
|
3449
3177
|
const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
3450
3178
|
for (const file of files) {
|
|
3451
|
-
cpSync(
|
|
3179
|
+
cpSync(resolve12(wsMemoryDir, file), resolve12(destMemoryDir, file));
|
|
3452
3180
|
}
|
|
3453
3181
|
dailyLogCount = files.length;
|
|
3454
3182
|
}
|
|
@@ -3459,7 +3187,7 @@ ${user.trimEnd()}
|
|
|
3459
3187
|
} catch (err) {
|
|
3460
3188
|
logger_default.warn(`failed to set template hash for ${name}`, logger_default.errorData(err));
|
|
3461
3189
|
}
|
|
3462
|
-
const homeDir =
|
|
3190
|
+
const homeDir = resolve12(dest, "home");
|
|
3463
3191
|
ensureVoluteGroup();
|
|
3464
3192
|
createMindUser(name, homeDir);
|
|
3465
3193
|
chownMindDir(dest, name);
|
|
@@ -3467,20 +3195,20 @@ ${user.trimEnd()}
|
|
|
3467
3195
|
if (!hasMemory && dailyLogCount > 0) {
|
|
3468
3196
|
await consolidateMemory(dest);
|
|
3469
3197
|
}
|
|
3470
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3198
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dest, "home") } : void 0;
|
|
3471
3199
|
await gitExec(["init"], { cwd: dest, mindName: name, env });
|
|
3472
3200
|
await configureGitIdentity(name, { cwd: dest, mindName: name, env });
|
|
3473
3201
|
await gitExec(["add", "-A"], { cwd: dest, mindName: name, env });
|
|
3474
3202
|
await gitExec(["commit", "-m", "import from OpenClaw"], { cwd: dest, mindName: name, env });
|
|
3475
|
-
const sessionFile = body.sessionPath ?
|
|
3476
|
-
if (sessionFile &&
|
|
3203
|
+
const sessionFile = body.sessionPath ? resolve12(body.sessionPath) : findOpenClawSession(wsDir);
|
|
3204
|
+
if (sessionFile && existsSync10(sessionFile)) {
|
|
3477
3205
|
if (template === "pi") {
|
|
3478
3206
|
importPiSession(sessionFile, dest);
|
|
3479
3207
|
} else if (template === "claude") {
|
|
3480
3208
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
3481
|
-
const mindRuntimeDir =
|
|
3482
|
-
|
|
3483
|
-
|
|
3209
|
+
const mindRuntimeDir = resolve12(dest, ".mind");
|
|
3210
|
+
mkdirSync6(mindRuntimeDir, { recursive: true });
|
|
3211
|
+
writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
|
|
3484
3212
|
}
|
|
3485
3213
|
}
|
|
3486
3214
|
importOpenClawConnectors(name, dest);
|
|
@@ -3495,14 +3223,14 @@ ${user.trimEnd()}
|
|
|
3495
3223
|
);
|
|
3496
3224
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
3497
3225
|
} catch (err) {
|
|
3498
|
-
if (
|
|
3226
|
+
if (existsSync10(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
3499
3227
|
try {
|
|
3500
3228
|
removeMind(name);
|
|
3501
3229
|
} catch {
|
|
3502
3230
|
}
|
|
3503
3231
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
3504
3232
|
} finally {
|
|
3505
|
-
|
|
3233
|
+
rmSync4(composedDir, { recursive: true, force: true });
|
|
3506
3234
|
}
|
|
3507
3235
|
}).get("/", async (c) => {
|
|
3508
3236
|
const entries = readRegistry();
|
|
@@ -3511,7 +3239,7 @@ ${user.trimEnd()}
|
|
|
3511
3239
|
const db = await getDb();
|
|
3512
3240
|
const lastActiveRows = await db.select({
|
|
3513
3241
|
mind: mindHistory.mind,
|
|
3514
|
-
lastActiveAt:
|
|
3242
|
+
lastActiveAt: sql`MAX(${mindHistory.created_at})`
|
|
3515
3243
|
}).from(mindHistory).groupBy(mindHistory.mind);
|
|
3516
3244
|
lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
|
|
3517
3245
|
} catch {
|
|
@@ -3519,7 +3247,7 @@ ${user.trimEnd()}
|
|
|
3519
3247
|
const minds = await Promise.all(
|
|
3520
3248
|
entries.map(async (entry) => {
|
|
3521
3249
|
const mindStatus = await getMindStatus(entry.name, entry.port);
|
|
3522
|
-
const hasPages =
|
|
3250
|
+
const hasPages = existsSync10(resolve12(mindDir(entry.name), "home", "pages"));
|
|
3523
3251
|
return {
|
|
3524
3252
|
...entry,
|
|
3525
3253
|
...mindStatus,
|
|
@@ -3537,7 +3265,7 @@ ${user.trimEnd()}
|
|
|
3537
3265
|
const name = c.req.param("name");
|
|
3538
3266
|
const entry = findMind(name);
|
|
3539
3267
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3540
|
-
if (!
|
|
3268
|
+
if (!existsSync10(mindDir(name))) return c.json({ error: "Mind directory missing" }, 404);
|
|
3541
3269
|
const mindStatus = await getMindStatus(name, entry.port);
|
|
3542
3270
|
const variants = readVariants(name);
|
|
3543
3271
|
const manager = getMindManager();
|
|
@@ -3552,7 +3280,7 @@ ${user.trimEnd()}
|
|
|
3552
3280
|
return { name: v.name, port: v.port, status: variantStatus };
|
|
3553
3281
|
})
|
|
3554
3282
|
);
|
|
3555
|
-
const hasPages =
|
|
3283
|
+
const hasPages = existsSync10(resolve12(mindDir(name), "home", "pages"));
|
|
3556
3284
|
return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
|
|
3557
3285
|
}).post("/:name/start", requireAdmin, async (c) => {
|
|
3558
3286
|
const name = c.req.param("name");
|
|
@@ -3566,7 +3294,7 @@ ${user.trimEnd()}
|
|
|
3566
3294
|
targetPort = variant.port;
|
|
3567
3295
|
} else {
|
|
3568
3296
|
const dir = mindDir(baseName);
|
|
3569
|
-
if (!
|
|
3297
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3570
3298
|
}
|
|
3571
3299
|
if (getMindManager().isRunning(name)) {
|
|
3572
3300
|
return c.json({ error: "Mind already running" }, 409);
|
|
@@ -3589,7 +3317,7 @@ ${user.trimEnd()}
|
|
|
3589
3317
|
targetPort = variant.port;
|
|
3590
3318
|
} else {
|
|
3591
3319
|
const dir = mindDir(baseName);
|
|
3592
|
-
if (!
|
|
3320
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3593
3321
|
}
|
|
3594
3322
|
let context;
|
|
3595
3323
|
const contentType = c.req.header("content-type");
|
|
@@ -3616,7 +3344,7 @@ ${user.trimEnd()}
|
|
|
3616
3344
|
const variant = findVariant(baseName, mergeVariantName);
|
|
3617
3345
|
if (variant) {
|
|
3618
3346
|
const projectRoot = mindDir(baseName);
|
|
3619
|
-
if (
|
|
3347
|
+
if (existsSync10(variant.path)) {
|
|
3620
3348
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3621
3349
|
if (status) {
|
|
3622
3350
|
try {
|
|
@@ -3658,7 +3386,7 @@ ${user.trimEnd()}
|
|
|
3658
3386
|
if (context?.type === "sprouted" && !variantName) {
|
|
3659
3387
|
try {
|
|
3660
3388
|
const db = await getDb();
|
|
3661
|
-
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();
|
|
3662
3390
|
for (const conv of activeConvs) {
|
|
3663
3391
|
await addMessage(conv.id, "assistant", "system", [
|
|
3664
3392
|
{ type: "text", text: "[seed has sprouted]" }
|
|
@@ -3696,7 +3424,7 @@ ${user.trimEnd()}
|
|
|
3696
3424
|
const name = c.req.param("name");
|
|
3697
3425
|
const entry = findMind(name);
|
|
3698
3426
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3699
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3427
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3700
3428
|
const sm = getSleepManagerIfReady();
|
|
3701
3429
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3702
3430
|
return c.json(sm.getState(name));
|
|
@@ -3704,7 +3432,7 @@ ${user.trimEnd()}
|
|
|
3704
3432
|
const name = c.req.param("name");
|
|
3705
3433
|
const entry = findMind(name);
|
|
3706
3434
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3707
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3435
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3708
3436
|
const sm = getSleepManagerIfReady();
|
|
3709
3437
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3710
3438
|
if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
|
|
@@ -3724,7 +3452,7 @@ ${user.trimEnd()}
|
|
|
3724
3452
|
const name = c.req.param("name");
|
|
3725
3453
|
const entry = findMind(name);
|
|
3726
3454
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3727
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3455
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3728
3456
|
const sm = getSleepManagerIfReady();
|
|
3729
3457
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3730
3458
|
if (!sm.isSleeping(name)) return c.json({ error: "Mind is not sleeping" }, 409);
|
|
@@ -3734,7 +3462,7 @@ ${user.trimEnd()}
|
|
|
3734
3462
|
const name = c.req.param("name");
|
|
3735
3463
|
const entry = findMind(name);
|
|
3736
3464
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3737
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3465
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3738
3466
|
const sm = getSleepManagerIfReady();
|
|
3739
3467
|
if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
|
|
3740
3468
|
const flushed = await sm.flushQueuedMessages(name);
|
|
@@ -3767,11 +3495,11 @@ ${user.trimEnd()}
|
|
|
3767
3495
|
removeMind(name);
|
|
3768
3496
|
await deleteMindUser2(name);
|
|
3769
3497
|
const state = stateDir(name);
|
|
3770
|
-
if (
|
|
3771
|
-
|
|
3498
|
+
if (existsSync10(state)) {
|
|
3499
|
+
rmSync4(state, { recursive: true, force: true });
|
|
3772
3500
|
}
|
|
3773
|
-
if (force &&
|
|
3774
|
-
|
|
3501
|
+
if (force && existsSync10(dir)) {
|
|
3502
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
3775
3503
|
deleteMindUser(name);
|
|
3776
3504
|
}
|
|
3777
3505
|
fireWebhook({
|
|
@@ -3785,7 +3513,7 @@ ${user.trimEnd()}
|
|
|
3785
3513
|
const entry = findMind(mindName);
|
|
3786
3514
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
3787
3515
|
const dir = mindDir(mindName);
|
|
3788
|
-
if (!
|
|
3516
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
3789
3517
|
let body = {};
|
|
3790
3518
|
try {
|
|
3791
3519
|
body = await c.req.json();
|
|
@@ -3794,15 +3522,15 @@ ${user.trimEnd()}
|
|
|
3794
3522
|
const template = body.template ?? entry.template ?? "claude";
|
|
3795
3523
|
const UPGRADE_VARIANT = "upgrade";
|
|
3796
3524
|
if (body.abort) {
|
|
3797
|
-
const worktreeDir2 =
|
|
3798
|
-
if (!
|
|
3525
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3526
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3799
3527
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3800
3528
|
}
|
|
3801
3529
|
try {
|
|
3802
3530
|
try {
|
|
3803
|
-
const gitDirContent =
|
|
3531
|
+
const gitDirContent = readFileSync9(resolve12(worktreeDir2, ".git"), "utf-8").trim();
|
|
3804
3532
|
const gitDir = gitDirContent.replace("gitdir: ", "");
|
|
3805
|
-
if (
|
|
3533
|
+
if (existsSync10(resolve12(gitDir, "MERGE_HEAD"))) {
|
|
3806
3534
|
await gitExec(["merge", "--abort"], { cwd: worktreeDir2 });
|
|
3807
3535
|
}
|
|
3808
3536
|
} catch {
|
|
@@ -3817,8 +3545,8 @@ ${user.trimEnd()}
|
|
|
3817
3545
|
}
|
|
3818
3546
|
}
|
|
3819
3547
|
if (body.continue) {
|
|
3820
|
-
const worktreeDir2 =
|
|
3821
|
-
if (!
|
|
3548
|
+
const worktreeDir2 = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3549
|
+
if (!existsSync10(worktreeDir2)) {
|
|
3822
3550
|
return c.json({ error: "No upgrade in progress" }, 400);
|
|
3823
3551
|
}
|
|
3824
3552
|
const status = await gitExec(["status", "--porcelain"], { cwd: worktreeDir2 });
|
|
@@ -3862,23 +3590,23 @@ ${user.trimEnd()}
|
|
|
3862
3590
|
);
|
|
3863
3591
|
}
|
|
3864
3592
|
}
|
|
3865
|
-
const worktreeDir =
|
|
3866
|
-
if (
|
|
3593
|
+
const worktreeDir = resolve12(dir, ".variants", UPGRADE_VARIANT);
|
|
3594
|
+
if (existsSync10(worktreeDir)) {
|
|
3867
3595
|
return c.json(
|
|
3868
3596
|
{ error: "Upgrade variant already exists. Use continue or delete it first." },
|
|
3869
3597
|
409
|
|
3870
3598
|
);
|
|
3871
3599
|
}
|
|
3872
|
-
if (!
|
|
3600
|
+
if (!existsSync10(resolve12(dir, ".git"))) {
|
|
3873
3601
|
try {
|
|
3874
|
-
const env = isIsolationEnabled() ? { ...process.env, HOME:
|
|
3602
|
+
const env = isIsolationEnabled() ? { ...process.env, HOME: resolve12(dir, "home") } : void 0;
|
|
3875
3603
|
await gitExec(["init"], { cwd: dir, mindName, env });
|
|
3876
3604
|
await configureGitIdentity(mindName, { cwd: dir, mindName, env });
|
|
3877
3605
|
await gitExec(["add", "-A"], { cwd: dir, mindName, env });
|
|
3878
3606
|
await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
|
|
3879
3607
|
chownMindDir(dir, mindName);
|
|
3880
3608
|
} catch (err) {
|
|
3881
|
-
|
|
3609
|
+
rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
|
|
3882
3610
|
return c.json(
|
|
3883
3611
|
{
|
|
3884
3612
|
error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3892,7 +3620,7 @@ ${user.trimEnd()}
|
|
|
3892
3620
|
await gitExec(["branch", "-D", UPGRADE_VARIANT], { cwd: dir });
|
|
3893
3621
|
} catch {
|
|
3894
3622
|
}
|
|
3895
|
-
if (!
|
|
3623
|
+
if (!existsSync10(resolve12(dir, "home", "shared"))) {
|
|
3896
3624
|
try {
|
|
3897
3625
|
await addSharedWorktree(mindName, dir);
|
|
3898
3626
|
} catch (err) {
|
|
@@ -3903,9 +3631,9 @@ ${user.trimEnd()}
|
|
|
3903
3631
|
}
|
|
3904
3632
|
}
|
|
3905
3633
|
await updateTemplateBranch(dir, template, mindName);
|
|
3906
|
-
const parentDir =
|
|
3907
|
-
if (!
|
|
3908
|
-
|
|
3634
|
+
const parentDir = resolve12(dir, ".variants");
|
|
3635
|
+
if (!existsSync10(parentDir)) {
|
|
3636
|
+
mkdirSync6(parentDir, { recursive: true });
|
|
3909
3637
|
}
|
|
3910
3638
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
3911
3639
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -3952,7 +3680,7 @@ ${user.trimEnd()}
|
|
|
3952
3680
|
if (!variant) return c.json({ error: `Unknown variant: ${variantName}` }, 404);
|
|
3953
3681
|
}
|
|
3954
3682
|
try {
|
|
3955
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
3683
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
|
|
3956
3684
|
const sm = getSleepManagerIfReady();
|
|
3957
3685
|
if (sm?.isSleeping(baseName)) {
|
|
3958
3686
|
const body2 = await c.req.text();
|
|
@@ -3994,21 +3722,10 @@ ${user.trimEnd()}
|
|
|
3994
3722
|
logger_default.error(`failed to parse message body for ${baseName}`, logger_default.errorData(err));
|
|
3995
3723
|
}
|
|
3996
3724
|
const channel = parsed?.channel ?? "unknown";
|
|
3997
|
-
const db = await getDb();
|
|
3998
3725
|
if (parsed) {
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
await db.insert(mindHistory).values({
|
|
4003
|
-
mind: baseName,
|
|
4004
|
-
type: "inbound",
|
|
4005
|
-
channel,
|
|
4006
|
-
sender: sender2,
|
|
4007
|
-
content
|
|
4008
|
-
});
|
|
4009
|
-
} catch (err) {
|
|
4010
|
-
logger_default.error(`failed to persist inbound message for ${baseName}`, logger_default.errorData(err));
|
|
4011
|
-
}
|
|
3726
|
+
const sender2 = parsed.sender ?? null;
|
|
3727
|
+
const content = extractTextContent(parsed.content);
|
|
3728
|
+
await recordInbound(baseName, channel, sender2, content);
|
|
4012
3729
|
}
|
|
4013
3730
|
const budget = getTokenBudget();
|
|
4014
3731
|
const budgetStatus = budget.checkBudget(baseName);
|
|
@@ -4060,7 +3777,8 @@ ${user.trimEnd()}
|
|
|
4060
3777
|
const seedEntry = findMind(baseName);
|
|
4061
3778
|
if (seedEntry?.stage === "seed") {
|
|
4062
3779
|
try {
|
|
4063
|
-
const
|
|
3780
|
+
const db = await getDb();
|
|
3781
|
+
const countResult = await db.select({ count: sql`count(*)` }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
|
|
4064
3782
|
const msgCount = countResult[0]?.count ?? 0;
|
|
4065
3783
|
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
4066
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.]";
|
|
@@ -4105,13 +3823,13 @@ ${user.trimEnd()}
|
|
|
4105
3823
|
const entry = findMind(name);
|
|
4106
3824
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4107
3825
|
const dir = mindDir(name);
|
|
4108
|
-
if (!
|
|
3826
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4109
3827
|
let config = readVoluteConfig(dir);
|
|
4110
3828
|
if (!config && entry.template === "pi") {
|
|
4111
|
-
const piConfigPath =
|
|
4112
|
-
if (
|
|
3829
|
+
const piConfigPath = resolve12(dir, "home/.config/config.json");
|
|
3830
|
+
if (existsSync10(piConfigPath)) {
|
|
4113
3831
|
try {
|
|
4114
|
-
config = JSON.parse(
|
|
3832
|
+
config = JSON.parse(readFileSync9(piConfigPath, "utf-8"));
|
|
4115
3833
|
} catch {
|
|
4116
3834
|
}
|
|
4117
3835
|
}
|
|
@@ -4148,7 +3866,7 @@ ${user.trimEnd()}
|
|
|
4148
3866
|
const entry = findMind(name);
|
|
4149
3867
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
4150
3868
|
const dir = mindDir(name);
|
|
4151
|
-
if (!
|
|
3869
|
+
if (!existsSync10(dir)) return c.json({ error: "Mind directory missing" }, 404);
|
|
4152
3870
|
const body = c.req.valid("json");
|
|
4153
3871
|
const existing = readVoluteConfig(dir) ?? {};
|
|
4154
3872
|
if (body.model !== void 0) existing.model = body.model;
|
|
@@ -4211,7 +3929,7 @@ ${user.trimEnd()}
|
|
|
4211
3929
|
} catch (err) {
|
|
4212
3930
|
logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
|
|
4213
3931
|
}
|
|
4214
|
-
|
|
3932
|
+
publish(baseName, {
|
|
4215
3933
|
mind: baseName,
|
|
4216
3934
|
type: body.type,
|
|
4217
3935
|
session: body.session,
|
|
@@ -4261,14 +3979,29 @@ ${user.trimEnd()}
|
|
|
4261
3979
|
|
|
4262
3980
|
`));
|
|
4263
3981
|
};
|
|
4264
|
-
|
|
3982
|
+
let unsubscribe;
|
|
3983
|
+
const pingInterval = setInterval(() => {
|
|
3984
|
+
try {
|
|
3985
|
+
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
3986
|
+
} catch {
|
|
3987
|
+
clearInterval(pingInterval);
|
|
3988
|
+
unsubscribe?.();
|
|
3989
|
+
}
|
|
3990
|
+
}, 15e3);
|
|
3991
|
+
unsubscribe = subscribe2(baseName, (event) => {
|
|
4265
3992
|
if (typeFilter && !typeFilter.includes(event.type)) return;
|
|
4266
3993
|
if (sessionFilter && event.session !== sessionFilter) return;
|
|
4267
3994
|
if (channelFilter && event.channel !== channelFilter) return;
|
|
4268
|
-
|
|
3995
|
+
try {
|
|
3996
|
+
send5(JSON.stringify(event));
|
|
3997
|
+
} catch {
|
|
3998
|
+
clearInterval(pingInterval);
|
|
3999
|
+
unsubscribe?.();
|
|
4000
|
+
}
|
|
4269
4001
|
});
|
|
4270
4002
|
c.req.raw.signal.addEventListener("abort", () => {
|
|
4271
|
-
|
|
4003
|
+
clearInterval(pingInterval);
|
|
4004
|
+
unsubscribe?.();
|
|
4272
4005
|
try {
|
|
4273
4006
|
controller.close();
|
|
4274
4007
|
} catch {
|
|
@@ -4314,22 +4047,22 @@ ${user.trimEnd()}
|
|
|
4314
4047
|
const db = await getDb();
|
|
4315
4048
|
const rows = await db.select({
|
|
4316
4049
|
session: mindHistory.session,
|
|
4317
|
-
started_at:
|
|
4318
|
-
event_count:
|
|
4319
|
-
message_count:
|
|
4320
|
-
tool_count:
|
|
4321
|
-
}).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`);
|
|
4322
4055
|
return c.json(rows);
|
|
4323
4056
|
}).get("/:name/history/channels", async (c) => {
|
|
4324
4057
|
const name = c.req.param("name");
|
|
4325
4058
|
const db = await getDb();
|
|
4326
|
-
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));
|
|
4327
4060
|
return c.json(rows.map((r) => r.channel));
|
|
4328
4061
|
}).get("/:name/history/export", async (c) => {
|
|
4329
4062
|
const name = c.req.param("name");
|
|
4330
4063
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
4331
4064
|
const db = await getDb();
|
|
4332
|
-
const rows = await db.select().from(mindHistory).where(
|
|
4065
|
+
const rows = await db.select().from(mindHistory).where(eq2(mindHistory.mind, name));
|
|
4333
4066
|
return c.json(rows);
|
|
4334
4067
|
}).get("/:name/history", async (c) => {
|
|
4335
4068
|
const name = c.req.param("name");
|
|
@@ -4339,24 +4072,24 @@ ${user.trimEnd()}
|
|
|
4339
4072
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
4340
4073
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
4341
4074
|
const db = await getDb();
|
|
4342
|
-
const conditions = [
|
|
4075
|
+
const conditions = [eq2(mindHistory.mind, name)];
|
|
4343
4076
|
if (channel) {
|
|
4344
|
-
conditions.push(
|
|
4077
|
+
conditions.push(eq2(mindHistory.channel, channel));
|
|
4345
4078
|
}
|
|
4346
4079
|
if (session) {
|
|
4347
|
-
conditions.push(
|
|
4080
|
+
conditions.push(eq2(mindHistory.session, session));
|
|
4348
4081
|
}
|
|
4349
4082
|
if (!full) {
|
|
4350
|
-
conditions.push(
|
|
4083
|
+
conditions.push(sql`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
4351
4084
|
}
|
|
4352
|
-
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);
|
|
4353
4086
|
return c.json(rows);
|
|
4354
4087
|
});
|
|
4355
4088
|
var minds_default = app11;
|
|
4356
4089
|
|
|
4357
4090
|
// src/web/api/pages.ts
|
|
4358
4091
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
4359
|
-
import { extname as
|
|
4092
|
+
import { extname as extname3, resolve as resolve13 } from "path";
|
|
4360
4093
|
import { Hono as Hono12 } from "hono";
|
|
4361
4094
|
var MIME_TYPES = {
|
|
4362
4095
|
".html": "text/html",
|
|
@@ -4378,17 +4111,17 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4378
4111
|
const name = c.req.param("name");
|
|
4379
4112
|
let pagesRoot;
|
|
4380
4113
|
if (name === "_system") {
|
|
4381
|
-
pagesRoot =
|
|
4114
|
+
pagesRoot = resolve13(voluteHome(), "shared", "pages");
|
|
4382
4115
|
} else {
|
|
4383
4116
|
if (!findMind(name)) return c.text("Not found", 404);
|
|
4384
|
-
pagesRoot =
|
|
4117
|
+
pagesRoot = resolve13(mindDir(name), "home", "pages");
|
|
4385
4118
|
}
|
|
4386
4119
|
const wildcard = c.req.path.replace(`/pages/${name}`, "") || "/";
|
|
4387
|
-
const requestedPath =
|
|
4120
|
+
const requestedPath = resolve13(pagesRoot, wildcard.slice(1));
|
|
4388
4121
|
if (!requestedPath.startsWith(pagesRoot)) return c.text("Forbidden", 403);
|
|
4389
4122
|
let fileStat = await stat2(requestedPath).catch(() => null);
|
|
4390
4123
|
if (fileStat?.isDirectory()) {
|
|
4391
|
-
const indexPath =
|
|
4124
|
+
const indexPath = resolve13(requestedPath, "index.html");
|
|
4392
4125
|
fileStat = await stat2(indexPath).catch(() => null);
|
|
4393
4126
|
if (fileStat?.isFile()) {
|
|
4394
4127
|
const body = await readFile2(indexPath);
|
|
@@ -4397,7 +4130,7 @@ var app12 = new Hono12().get("/:name/*", async (c) => {
|
|
|
4397
4130
|
return c.text("Not found", 404);
|
|
4398
4131
|
}
|
|
4399
4132
|
if (fileStat?.isFile()) {
|
|
4400
|
-
const ext =
|
|
4133
|
+
const ext = extname3(requestedPath);
|
|
4401
4134
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
4402
4135
|
const body = await readFile2(requestedPath);
|
|
4403
4136
|
return c.body(body, 200, { "Content-Type": mime });
|
|
@@ -4408,7 +4141,7 @@ var pages_default = app12;
|
|
|
4408
4141
|
|
|
4409
4142
|
// src/web/api/prompts.ts
|
|
4410
4143
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
4411
|
-
import { eq as
|
|
4144
|
+
import { eq as eq3, sql as sql2 } from "drizzle-orm";
|
|
4412
4145
|
import { Hono as Hono13 } from "hono";
|
|
4413
4146
|
import { z as z4 } from "zod";
|
|
4414
4147
|
var app13 = new Hono13().get("/", async (c) => {
|
|
@@ -4441,9 +4174,9 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4441
4174
|
}
|
|
4442
4175
|
const { content } = c.req.valid("json");
|
|
4443
4176
|
const db = await getDb();
|
|
4444
|
-
await db.insert(systemPrompts).values({ key, content, updated_at:
|
|
4177
|
+
await db.insert(systemPrompts).values({ key, content, updated_at: sql2`(datetime('now'))` }).onConflictDoUpdate({
|
|
4445
4178
|
target: systemPrompts.key,
|
|
4446
|
-
set: { content, updated_at:
|
|
4179
|
+
set: { content, updated_at: sql2`(datetime('now'))` }
|
|
4447
4180
|
});
|
|
4448
4181
|
return c.json({ ok: true });
|
|
4449
4182
|
}).delete("/:key", requireAdmin, async (c) => {
|
|
@@ -4452,7 +4185,7 @@ var app13 = new Hono13().get("/", async (c) => {
|
|
|
4452
4185
|
return c.json({ error: "Unknown prompt key" }, 404);
|
|
4453
4186
|
}
|
|
4454
4187
|
const db = await getDb();
|
|
4455
|
-
await db.delete(systemPrompts).where(
|
|
4188
|
+
await db.delete(systemPrompts).where(eq3(systemPrompts.key, key));
|
|
4456
4189
|
return c.json({ ok: true });
|
|
4457
4190
|
});
|
|
4458
4191
|
var prompts_default = app13;
|
|
@@ -4635,9 +4368,9 @@ var app15 = new Hono15().post("/:name/shared/merge", requireAdmin, async (c) =>
|
|
|
4635
4368
|
var shared_default = app15;
|
|
4636
4369
|
|
|
4637
4370
|
// src/web/api/skills.ts
|
|
4638
|
-
import { existsSync as
|
|
4371
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
|
|
4639
4372
|
import { tmpdir } from "os";
|
|
4640
|
-
import { join as join2, resolve as
|
|
4373
|
+
import { join as join2, resolve as resolve14 } from "path";
|
|
4641
4374
|
import AdmZip from "adm-zip";
|
|
4642
4375
|
import { Hono as Hono16 } from "hono";
|
|
4643
4376
|
var app16 = new Hono16().get("/", async (c) => {
|
|
@@ -4664,19 +4397,19 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4664
4397
|
try {
|
|
4665
4398
|
const zip = new AdmZip(buffer2);
|
|
4666
4399
|
for (const entry of zip.getEntries()) {
|
|
4667
|
-
const target =
|
|
4400
|
+
const target = resolve14(tmpDir, entry.entryName);
|
|
4668
4401
|
if (!target.startsWith(tmpDir)) {
|
|
4669
4402
|
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
4670
4403
|
}
|
|
4671
4404
|
}
|
|
4672
4405
|
zip.extractAllTo(tmpDir, true);
|
|
4673
4406
|
let skillDir = null;
|
|
4674
|
-
if (
|
|
4407
|
+
if (existsSync11(join2(tmpDir, "SKILL.md"))) {
|
|
4675
4408
|
skillDir = tmpDir;
|
|
4676
4409
|
} else {
|
|
4677
4410
|
const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4678
4411
|
for (const entry of entries) {
|
|
4679
|
-
if (
|
|
4412
|
+
if (existsSync11(join2(tmpDir, entry.name, "SKILL.md"))) {
|
|
4680
4413
|
skillDir = join2(tmpDir, entry.name);
|
|
4681
4414
|
break;
|
|
4682
4415
|
}
|
|
@@ -4693,7 +4426,7 @@ var app16 = new Hono16().get("/", async (c) => {
|
|
|
4693
4426
|
}
|
|
4694
4427
|
throw e;
|
|
4695
4428
|
} finally {
|
|
4696
|
-
|
|
4429
|
+
rmSync5(tmpDir, { recursive: true, force: true });
|
|
4697
4430
|
}
|
|
4698
4431
|
}).delete("/:id", requireAdmin, async (c) => {
|
|
4699
4432
|
const id = c.req.param("id");
|
|
@@ -4727,10 +4460,10 @@ var app17 = new Hono17().post("/restart", requireAdmin, (c) => {
|
|
|
4727
4460
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
4728
4461
|
});
|
|
4729
4462
|
});
|
|
4730
|
-
await new Promise((
|
|
4463
|
+
await new Promise((resolve19) => {
|
|
4731
4464
|
stream.onAbort(() => {
|
|
4732
4465
|
unsubscribe();
|
|
4733
|
-
|
|
4466
|
+
resolve19();
|
|
4734
4467
|
});
|
|
4735
4468
|
});
|
|
4736
4469
|
});
|
|
@@ -4760,7 +4493,7 @@ var app18 = new Hono18().post("/:name/typing", zValidator5("json", typingSchema)
|
|
|
4760
4493
|
const volutePrefix = "volute:";
|
|
4761
4494
|
if (channel.startsWith(volutePrefix)) {
|
|
4762
4495
|
const conversationId = channel.slice(volutePrefix.length);
|
|
4763
|
-
|
|
4496
|
+
publish2(conversationId, { type: "typing", senders: map.get(channel) });
|
|
4764
4497
|
}
|
|
4765
4498
|
return c.json({ ok: true });
|
|
4766
4499
|
}).get("/:name/typing", (c) => {
|
|
@@ -4805,8 +4538,8 @@ async function fanOutToMinds(opts) {
|
|
|
4805
4538
|
const participantNames = participants.map((p) => p.username);
|
|
4806
4539
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
4807
4540
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
4808
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
4809
|
-
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");
|
|
4810
4543
|
const manager = getMindManager2();
|
|
4811
4544
|
const sm = getSleepManagerIfReady();
|
|
4812
4545
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -4920,11 +4653,15 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4920
4653
|
}
|
|
4921
4654
|
}
|
|
4922
4655
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
4656
|
+
const isDM = conv?.type === "dm";
|
|
4923
4657
|
await fanOutToMinds({
|
|
4924
4658
|
conversationId,
|
|
4925
4659
|
contentBlocks,
|
|
4926
4660
|
senderName,
|
|
4927
4661
|
convTitle,
|
|
4662
|
+
isDM,
|
|
4663
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
4664
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
4928
4665
|
targetName: (username) => username === baseName ? name : username
|
|
4929
4666
|
});
|
|
4930
4667
|
return c.json({ ok: true, conversationId });
|
|
@@ -4935,7 +4672,7 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4935
4672
|
return c.json({ error: "Conversation not found" }, 404);
|
|
4936
4673
|
}
|
|
4937
4674
|
return streamSSE4(c, async (stream) => {
|
|
4938
|
-
const unsubscribe =
|
|
4675
|
+
const unsubscribe = subscribe3(conversationId, (event) => {
|
|
4939
4676
|
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
4940
4677
|
if (!stream.aborted) logger_default.error("[v1-chat] SSE write error:", logger_default.errorData(err));
|
|
4941
4678
|
});
|
|
@@ -4945,11 +4682,11 @@ var app20 = new Hono20().use("*", authMiddleware).post("/minds/:name/chat", zVal
|
|
|
4945
4682
|
if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
|
|
4946
4683
|
});
|
|
4947
4684
|
}, 15e3);
|
|
4948
|
-
await new Promise((
|
|
4685
|
+
await new Promise((resolve19) => {
|
|
4949
4686
|
stream.onAbort(() => {
|
|
4950
4687
|
unsubscribe();
|
|
4951
4688
|
clearInterval(keepAlive);
|
|
4952
|
-
|
|
4689
|
+
resolve19();
|
|
4953
4690
|
});
|
|
4954
4691
|
});
|
|
4955
4692
|
});
|
|
@@ -5056,6 +4793,15 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5056
4793
|
participantIds: [...participantIds]
|
|
5057
4794
|
});
|
|
5058
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 });
|
|
5059
4805
|
}).delete("/:id", async (c) => {
|
|
5060
4806
|
const id = c.req.param("id");
|
|
5061
4807
|
const user = c.get("user");
|
|
@@ -5066,10 +4812,33 @@ var app21 = new Hono21().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5066
4812
|
var conversations_default = app21;
|
|
5067
4813
|
|
|
5068
4814
|
// src/web/api/v1/events.ts
|
|
5069
|
-
import { desc as
|
|
4815
|
+
import { desc as desc3 } from "drizzle-orm";
|
|
5070
4816
|
import { Hono as Hono22 } from "hono";
|
|
5071
4817
|
import { streamSSE as streamSSE5 } from "hono/streaming";
|
|
5072
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
|
+
|
|
5073
4842
|
// src/lib/events/event-sequencer.ts
|
|
5074
4843
|
var BUFFER_SIZE = 1e3;
|
|
5075
4844
|
var MAX_AGE_MS = 5 * 60 * 1e3;
|
|
@@ -5097,6 +4866,10 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5097
4866
|
const sinceId = since ? Number(since) : 0;
|
|
5098
4867
|
return streamSSE5(c, async (stream) => {
|
|
5099
4868
|
const cleanups = [];
|
|
4869
|
+
if (user.user_type === "brain") {
|
|
4870
|
+
addConnection(user.username);
|
|
4871
|
+
cleanups.push(() => removeConnection(user.username));
|
|
4872
|
+
}
|
|
5100
4873
|
try {
|
|
5101
4874
|
if (sinceId > 0) {
|
|
5102
4875
|
const missed = getEventsSince(sinceId);
|
|
@@ -5110,7 +4883,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5110
4883
|
let recentActivity = [];
|
|
5111
4884
|
try {
|
|
5112
4885
|
const db = await getDb();
|
|
5113
|
-
recentActivity = await db.select().from(activity).orderBy(
|
|
4886
|
+
recentActivity = await db.select().from(activity).orderBy(desc3(activity.created_at)).limit(50);
|
|
5114
4887
|
recentActivity = recentActivity.map((row) => ({
|
|
5115
4888
|
...row,
|
|
5116
4889
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
@@ -5121,6 +4894,13 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5121
4894
|
let conversations2 = [];
|
|
5122
4895
|
try {
|
|
5123
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
|
+
}
|
|
5124
4904
|
} catch (err) {
|
|
5125
4905
|
logger_default.error("[v1-events] failed to fetch conversations", logger_default.errorData(err));
|
|
5126
4906
|
}
|
|
@@ -5132,7 +4912,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5132
4912
|
conversations: conversations2,
|
|
5133
4913
|
sites,
|
|
5134
4914
|
recentPages,
|
|
5135
|
-
activeMinds: getActiveMinds()
|
|
4915
|
+
activeMinds: getActiveMinds(),
|
|
4916
|
+
onlineBrains: getOnlineBrains()
|
|
5136
4917
|
};
|
|
5137
4918
|
const snapshotId = bufferEvent(snapshotData);
|
|
5138
4919
|
await stream.writeSSE({
|
|
@@ -5155,7 +4936,7 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5155
4936
|
});
|
|
5156
4937
|
cleanups.push(unsubActivity);
|
|
5157
4938
|
for (const conv of conversations2) {
|
|
5158
|
-
const unsubConv =
|
|
4939
|
+
const unsubConv = subscribe3(conv.id, (event) => {
|
|
5159
4940
|
const data = { event: "conversation", conversationId: conv.id, ...event };
|
|
5160
4941
|
const eventId = bufferEvent(data);
|
|
5161
4942
|
stream.writeSSE({
|
|
@@ -5173,8 +4954,8 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5173
4954
|
});
|
|
5174
4955
|
}, 15e3);
|
|
5175
4956
|
cleanups.push(() => clearInterval(keepAlive));
|
|
5176
|
-
await new Promise((
|
|
5177
|
-
stream.onAbort(() =>
|
|
4957
|
+
await new Promise((resolve19) => {
|
|
4958
|
+
stream.onAbort(() => resolve19());
|
|
5178
4959
|
});
|
|
5179
4960
|
} finally {
|
|
5180
4961
|
for (const cleanup of cleanups) {
|
|
@@ -5189,16 +4970,16 @@ var app22 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5189
4970
|
var events_default = app22;
|
|
5190
4971
|
|
|
5191
4972
|
// src/web/api/variants.ts
|
|
5192
|
-
import { existsSync as
|
|
5193
|
-
import { resolve as
|
|
4973
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
4974
|
+
import { resolve as resolve16 } from "path";
|
|
5194
4975
|
import { Hono as Hono23 } from "hono";
|
|
5195
4976
|
|
|
5196
4977
|
// src/lib/spawn-server.ts
|
|
5197
4978
|
import { spawn as spawn3 } from "child_process";
|
|
5198
|
-
import { closeSync, mkdirSync as
|
|
5199
|
-
import { resolve as
|
|
4979
|
+
import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync10 } from "fs";
|
|
4980
|
+
import { resolve as resolve15 } from "path";
|
|
5200
4981
|
function tsxBin(cwd) {
|
|
5201
|
-
return
|
|
4982
|
+
return resolve15(cwd, "node_modules", ".bin", "tsx");
|
|
5202
4983
|
}
|
|
5203
4984
|
function spawnServer(cwd, port, options) {
|
|
5204
4985
|
if (options?.detached) {
|
|
@@ -5211,31 +4992,31 @@ function spawnAttached(cwd, port) {
|
|
|
5211
4992
|
cwd,
|
|
5212
4993
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5213
4994
|
});
|
|
5214
|
-
return new Promise((
|
|
5215
|
-
const timeout = setTimeout(() =>
|
|
4995
|
+
return new Promise((resolve19) => {
|
|
4996
|
+
const timeout = setTimeout(() => resolve19(null), 3e4);
|
|
5216
4997
|
function checkOutput(data) {
|
|
5217
4998
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
5218
4999
|
if (match) {
|
|
5219
5000
|
clearTimeout(timeout);
|
|
5220
|
-
|
|
5001
|
+
resolve19({ child, actualPort: parseInt(match[1], 10) });
|
|
5221
5002
|
}
|
|
5222
5003
|
}
|
|
5223
5004
|
child.stdout?.on("data", checkOutput);
|
|
5224
5005
|
child.stderr?.on("data", checkOutput);
|
|
5225
5006
|
child.on("error", () => {
|
|
5226
5007
|
clearTimeout(timeout);
|
|
5227
|
-
|
|
5008
|
+
resolve19(null);
|
|
5228
5009
|
});
|
|
5229
5010
|
child.on("exit", () => {
|
|
5230
5011
|
clearTimeout(timeout);
|
|
5231
|
-
|
|
5012
|
+
resolve19(null);
|
|
5232
5013
|
});
|
|
5233
5014
|
});
|
|
5234
5015
|
}
|
|
5235
5016
|
function spawnDetached(cwd, port, logDir) {
|
|
5236
|
-
const logsDir = logDir ??
|
|
5237
|
-
|
|
5238
|
-
const logPath =
|
|
5017
|
+
const logsDir = logDir ?? resolve15(cwd, ".mind", "logs");
|
|
5018
|
+
mkdirSync7(logsDir, { recursive: true });
|
|
5019
|
+
const logPath = resolve15(logsDir, "mind.log");
|
|
5239
5020
|
const logFd = openSync(logPath, "a");
|
|
5240
5021
|
const child = spawn3(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
5241
5022
|
cwd,
|
|
@@ -5255,7 +5036,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
5255
5036
|
}
|
|
5256
5037
|
const interval = setInterval(() => {
|
|
5257
5038
|
try {
|
|
5258
|
-
const content =
|
|
5039
|
+
const content = readFileSync10(logPath, "utf-8");
|
|
5259
5040
|
const match = content.match(/listening on :(\d+)/);
|
|
5260
5041
|
if (match) {
|
|
5261
5042
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -5347,11 +5128,11 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5347
5128
|
const err = validateBranchName(variantName);
|
|
5348
5129
|
if (err) return c.json({ error: err }, 400);
|
|
5349
5130
|
const projectRoot = mindDir(mindName);
|
|
5350
|
-
const variantDir =
|
|
5351
|
-
if (
|
|
5131
|
+
const variantDir = resolve16(projectRoot, ".variants", variantName);
|
|
5132
|
+
if (existsSync12(variantDir)) {
|
|
5352
5133
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
5353
5134
|
}
|
|
5354
|
-
|
|
5135
|
+
mkdirSync8(resolve16(projectRoot, ".variants"), { recursive: true });
|
|
5355
5136
|
try {
|
|
5356
5137
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
5357
5138
|
} catch (e) {
|
|
@@ -5364,7 +5145,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5364
5145
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5365
5146
|
await exec(cmd, args, {
|
|
5366
5147
|
cwd: variantDir,
|
|
5367
|
-
env: { ...process.env, HOME:
|
|
5148
|
+
env: { ...process.env, HOME: resolve16(variantDir, "home") }
|
|
5368
5149
|
});
|
|
5369
5150
|
} else {
|
|
5370
5151
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -5374,7 +5155,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5374
5155
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
5375
5156
|
}
|
|
5376
5157
|
if (body.soul) {
|
|
5377
|
-
|
|
5158
|
+
writeFileSync8(resolve16(variantDir, "home/SOUL.md"), body.soul);
|
|
5378
5159
|
}
|
|
5379
5160
|
const variantPort = body.port ?? nextPort();
|
|
5380
5161
|
const variant = {
|
|
@@ -5412,7 +5193,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5412
5193
|
} catch {
|
|
5413
5194
|
}
|
|
5414
5195
|
const projectRoot = mindDir(mindName);
|
|
5415
|
-
if (
|
|
5196
|
+
if (existsSync12(variant.path)) {
|
|
5416
5197
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
5417
5198
|
if (status) {
|
|
5418
5199
|
try {
|
|
@@ -5485,7 +5266,7 @@ var app23 = new Hono23().get("/:name/variants", async (c) => {
|
|
|
5485
5266
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
5486
5267
|
await exec(cmd, args, {
|
|
5487
5268
|
cwd: projectRoot,
|
|
5488
|
-
env: { ...process.env, HOME:
|
|
5269
|
+
env: { ...process.env, HOME: resolve16(projectRoot, "home") }
|
|
5489
5270
|
});
|
|
5490
5271
|
} else {
|
|
5491
5272
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -5612,8 +5393,8 @@ async function fanOutToMinds2(opts) {
|
|
|
5612
5393
|
const participantNames = participants.map((p) => p.username);
|
|
5613
5394
|
const isDM = opts.isDM ?? participants.length === 2;
|
|
5614
5395
|
const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
|
|
5615
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
5616
|
-
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");
|
|
5617
5398
|
const manager = getMindManager2();
|
|
5618
5399
|
const sm = getSleepManagerIfReady();
|
|
5619
5400
|
const targetMinds = mindParticipants.map((ap) => {
|
|
@@ -5730,11 +5511,15 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5730
5511
|
}
|
|
5731
5512
|
}
|
|
5732
5513
|
await addMessage(conversationId, "user", senderName, contentBlocks);
|
|
5514
|
+
const isDM = conv?.type === "dm";
|
|
5733
5515
|
await fanOutToMinds2({
|
|
5734
5516
|
conversationId,
|
|
5735
5517
|
contentBlocks,
|
|
5736
5518
|
senderName,
|
|
5737
5519
|
convTitle,
|
|
5520
|
+
isDM,
|
|
5521
|
+
channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
|
|
5522
|
+
slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
|
|
5738
5523
|
targetName: (username) => username === baseName ? name : username
|
|
5739
5524
|
});
|
|
5740
5525
|
return c.json({ ok: true, conversationId });
|
|
@@ -5745,7 +5530,7 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5745
5530
|
return c.json({ error: "Conversation not found" }, 404);
|
|
5746
5531
|
}
|
|
5747
5532
|
return streamSSE6(c, async (stream) => {
|
|
5748
|
-
const unsubscribe =
|
|
5533
|
+
const unsubscribe = subscribe3(conversationId, (event) => {
|
|
5749
5534
|
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
5750
5535
|
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
5751
5536
|
});
|
|
@@ -5755,11 +5540,11 @@ var app25 = new Hono25().post("/:name/chat", zValidator9("json", chatSchema), as
|
|
|
5755
5540
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5756
5541
|
});
|
|
5757
5542
|
}, 15e3);
|
|
5758
|
-
await new Promise((
|
|
5543
|
+
await new Promise((resolve19) => {
|
|
5759
5544
|
stream.onAbort(() => {
|
|
5760
5545
|
unsubscribe();
|
|
5761
5546
|
clearInterval(keepAlive);
|
|
5762
|
-
|
|
5547
|
+
resolve19();
|
|
5763
5548
|
});
|
|
5764
5549
|
});
|
|
5765
5550
|
});
|
|
@@ -5961,7 +5746,7 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5961
5746
|
return c.json({ error: "Conversation not found" }, 404);
|
|
5962
5747
|
}
|
|
5963
5748
|
return streamSSE7(c, async (stream) => {
|
|
5964
|
-
const unsubscribe =
|
|
5749
|
+
const unsubscribe = subscribe3(conversationId, (event) => {
|
|
5965
5750
|
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
5966
5751
|
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
5967
5752
|
});
|
|
@@ -5971,11 +5756,11 @@ var app27 = new Hono27().use("*", authMiddleware).get("/", async (c) => {
|
|
|
5971
5756
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5972
5757
|
});
|
|
5973
5758
|
}, 15e3);
|
|
5974
|
-
await new Promise((
|
|
5759
|
+
await new Promise((resolve19) => {
|
|
5975
5760
|
stream.onAbort(() => {
|
|
5976
5761
|
unsubscribe();
|
|
5977
5762
|
clearInterval(keepAlive);
|
|
5978
|
-
|
|
5763
|
+
resolve19();
|
|
5979
5764
|
});
|
|
5980
5765
|
});
|
|
5981
5766
|
});
|
|
@@ -6073,13 +5858,14 @@ var MIME_TYPES2 = {
|
|
|
6073
5858
|
};
|
|
6074
5859
|
async function startServer({
|
|
6075
5860
|
port,
|
|
6076
|
-
hostname = "127.0.0.1"
|
|
5861
|
+
hostname = "127.0.0.1",
|
|
5862
|
+
tls
|
|
6077
5863
|
}) {
|
|
6078
5864
|
let assetsDir = "";
|
|
6079
5865
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
6080
5866
|
for (let i = 0; i < 5; i++) {
|
|
6081
|
-
const candidate =
|
|
6082
|
-
if (
|
|
5867
|
+
const candidate = resolve17(searchDir, "dist", "web-assets");
|
|
5868
|
+
if (existsSync13(candidate)) {
|
|
6083
5869
|
assetsDir = candidate;
|
|
6084
5870
|
break;
|
|
6085
5871
|
}
|
|
@@ -6089,16 +5875,16 @@ async function startServer({
|
|
|
6089
5875
|
app_default.get("*", async (c) => {
|
|
6090
5876
|
const urlPath = new URL(c.req.url).pathname;
|
|
6091
5877
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
6092
|
-
const filePath =
|
|
5878
|
+
const filePath = resolve17(assetsDir, urlPath.slice(1));
|
|
6093
5879
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
6094
5880
|
const s = await stat3(filePath).catch(() => null);
|
|
6095
5881
|
if (s?.isFile()) {
|
|
6096
|
-
const ext =
|
|
5882
|
+
const ext = extname4(filePath);
|
|
6097
5883
|
const mime = MIME_TYPES2[ext] || "application/octet-stream";
|
|
6098
5884
|
const body = await readFile3(filePath);
|
|
6099
5885
|
return c.body(body, 200, { "Content-Type": mime });
|
|
6100
5886
|
}
|
|
6101
|
-
const indexPath =
|
|
5887
|
+
const indexPath = resolve17(assetsDir, "index.html");
|
|
6102
5888
|
const indexStat = await stat3(indexPath).catch(() => null);
|
|
6103
5889
|
if (indexStat?.isFile()) {
|
|
6104
5890
|
const body = await readFile3(indexPath, "utf-8");
|
|
@@ -6107,22 +5893,55 @@ async function startServer({
|
|
|
6107
5893
|
return c.text("Not found", 404);
|
|
6108
5894
|
});
|
|
6109
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
|
+
}
|
|
6110
5929
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
6111
|
-
await new Promise((
|
|
5930
|
+
await new Promise((resolve19, reject) => {
|
|
6112
5931
|
server.on("listening", () => {
|
|
6113
|
-
logger_default.info("Volute
|
|
6114
|
-
|
|
5932
|
+
logger_default.info("Volute API running (http)", { hostname, port });
|
|
5933
|
+
resolve19();
|
|
6115
5934
|
});
|
|
6116
5935
|
server.on("error", (err) => {
|
|
6117
5936
|
reject(err);
|
|
6118
5937
|
});
|
|
6119
5938
|
});
|
|
6120
|
-
return server;
|
|
5939
|
+
return { server };
|
|
6121
5940
|
}
|
|
6122
5941
|
|
|
6123
5942
|
// src/daemon.ts
|
|
6124
5943
|
if (!process.env.VOLUTE_HOME) {
|
|
6125
|
-
process.env.VOLUTE_HOME =
|
|
5944
|
+
process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
|
|
6126
5945
|
}
|
|
6127
5946
|
if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
|
|
6128
5947
|
process.env.TZ = process.env.VOLUTE_TIMEZONE;
|
|
@@ -6132,7 +5951,7 @@ async function startDaemon(opts) {
|
|
|
6132
5951
|
const myPid = String(process.pid);
|
|
6133
5952
|
const home = voluteHome();
|
|
6134
5953
|
if (!opts.foreground) {
|
|
6135
|
-
const rotatingLog = new RotatingLog(
|
|
5954
|
+
const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
|
|
6136
5955
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
6137
5956
|
`));
|
|
6138
5957
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -6142,9 +5961,9 @@ async function startDaemon(opts) {
|
|
|
6142
5961
|
console.warn = write;
|
|
6143
5962
|
console.info = write;
|
|
6144
5963
|
}
|
|
6145
|
-
const DAEMON_PID_PATH =
|
|
6146
|
-
const DAEMON_JSON_PATH =
|
|
6147
|
-
|
|
5964
|
+
const DAEMON_PID_PATH = resolve18(home, "daemon.pid");
|
|
5965
|
+
const DAEMON_JSON_PATH = resolve18(home, "daemon.json");
|
|
5966
|
+
mkdirSync9(home, { recursive: true });
|
|
6148
5967
|
migrateAgentsToMinds();
|
|
6149
5968
|
try {
|
|
6150
5969
|
await ensureSharedRepo();
|
|
@@ -6158,12 +5977,16 @@ async function startDaemon(opts) {
|
|
|
6158
5977
|
logger_default.error("failed to sync built-in skills", logger_default.errorData(err));
|
|
6159
5978
|
}
|
|
6160
5979
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
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;
|
|
6165
5988
|
try {
|
|
6166
|
-
|
|
5989
|
+
result = await startServer({ port, hostname: "0.0.0.0", tls });
|
|
6167
5990
|
} catch (err) {
|
|
6168
5991
|
const e = err;
|
|
6169
5992
|
if (e.code === "EADDRINUSE") {
|
|
@@ -6172,11 +5995,17 @@ async function startDaemon(opts) {
|
|
|
6172
5995
|
}
|
|
6173
5996
|
throw err;
|
|
6174
5997
|
}
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
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 });
|
|
6180
6009
|
const delivery = initDeliveryManager();
|
|
6181
6010
|
const manager = initMindManager();
|
|
6182
6011
|
manager.loadCrashAttempts();
|
|
@@ -6208,8 +6037,8 @@ async function startDaemon(opts) {
|
|
|
6208
6037
|
if (sleepManager.isSleeping(entry.name)) {
|
|
6209
6038
|
try {
|
|
6210
6039
|
const dir = mindDir(entry.name);
|
|
6211
|
-
const
|
|
6212
|
-
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);
|
|
6213
6042
|
scheduler.loadSchedules(entry.name);
|
|
6214
6043
|
} catch (err) {
|
|
6215
6044
|
logger_default.error(
|
|
@@ -6246,7 +6075,7 @@ async function startDaemon(opts) {
|
|
|
6246
6075
|
});
|
|
6247
6076
|
await Promise.all(workers);
|
|
6248
6077
|
}
|
|
6249
|
-
import("./cloud-sync-
|
|
6078
|
+
import("./cloud-sync-DIU3OCPV.js").then(
|
|
6250
6079
|
({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
|
|
6251
6080
|
logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
|
|
6252
6081
|
})
|
|
@@ -6254,7 +6083,7 @@ async function startDaemon(opts) {
|
|
|
6254
6083
|
logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
|
|
6255
6084
|
});
|
|
6256
6085
|
try {
|
|
6257
|
-
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-
|
|
6086
|
+
const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-SZ75QRGO.js");
|
|
6258
6087
|
backfillTemplateHashes();
|
|
6259
6088
|
notifyVersionUpdate().catch((err) => {
|
|
6260
6089
|
logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
|
|
@@ -6271,13 +6100,13 @@ async function startDaemon(opts) {
|
|
|
6271
6100
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
6272
6101
|
function cleanup() {
|
|
6273
6102
|
try {
|
|
6274
|
-
if (
|
|
6103
|
+
if (readFileSync11(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
6275
6104
|
unlinkSync(DAEMON_PID_PATH);
|
|
6276
6105
|
}
|
|
6277
6106
|
} catch {
|
|
6278
6107
|
}
|
|
6279
6108
|
try {
|
|
6280
|
-
const data = JSON.parse(
|
|
6109
|
+
const data = JSON.parse(readFileSync11(DAEMON_JSON_PATH, "utf-8"));
|
|
6281
6110
|
if (data.token === token) {
|
|
6282
6111
|
unlinkSync(DAEMON_JSON_PATH);
|
|
6283
6112
|
}
|
|
@@ -6291,9 +6120,9 @@ async function startDaemon(opts) {
|
|
|
6291
6120
|
logger_default.info("shutting down...");
|
|
6292
6121
|
const safe = (label, fn) => {
|
|
6293
6122
|
try {
|
|
6294
|
-
const
|
|
6295
|
-
if (
|
|
6296
|
-
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)));
|
|
6297
6126
|
} catch (err) {
|
|
6298
6127
|
logger_default.error(`shutdown: ${label} failed`, logger_default.errorData(err));
|
|
6299
6128
|
}
|
|
@@ -6325,9 +6154,10 @@ async function startDaemon(opts) {
|
|
|
6325
6154
|
process.on("exit", cleanup);
|
|
6326
6155
|
}
|
|
6327
6156
|
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("daemon.ts")) {
|
|
6328
|
-
let port =
|
|
6157
|
+
let port = 1618;
|
|
6329
6158
|
let hostname = "127.0.0.1";
|
|
6330
6159
|
let foreground = false;
|
|
6160
|
+
let tailscale = false;
|
|
6331
6161
|
for (let i = 2; i < process.argv.length; i++) {
|
|
6332
6162
|
if (process.argv[i] === "--port" && process.argv[i + 1]) {
|
|
6333
6163
|
port = parseInt(process.argv[i + 1], 10);
|
|
@@ -6337,9 +6167,11 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
6337
6167
|
i++;
|
|
6338
6168
|
} else if (process.argv[i] === "--foreground") {
|
|
6339
6169
|
foreground = true;
|
|
6170
|
+
} else if (process.argv[i] === "--tailscale") {
|
|
6171
|
+
tailscale = true;
|
|
6340
6172
|
}
|
|
6341
6173
|
}
|
|
6342
|
-
startDaemon({ port, hostname, foreground });
|
|
6174
|
+
startDaemon({ port, hostname, foreground, tailscale });
|
|
6343
6175
|
}
|
|
6344
6176
|
export {
|
|
6345
6177
|
startDaemon
|