volute 0.17.0 → 0.18.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/dist/{chunk-CE7WMOVW.js → chunk-AYB7XAWO.js} +323 -25
- package/dist/{chunk-MIJIAGGG.js → chunk-FW5API7X.js} +7 -5
- package/dist/{chunk-3FC42ZBM.js → chunk-GK4E7LM7.js} +3 -0
- package/dist/cli.js +18 -6
- package/dist/connectors/discord.js +1 -1
- package/dist/connectors/slack.js +1 -1
- package/dist/connectors/telegram.js +1 -1
- package/dist/{daemon-restart-VRQMZLBK.js → daemon-restart-2HVTHZAT.js} +1 -1
- package/dist/daemon.js +1080 -432
- package/dist/{history-5F4WQW7S.js → history-YUEKTJ2N.js} +4 -1
- package/dist/{mind-manager-ETNCPQJN.js → mind-manager-Z7O7PN2O.js} +1 -1
- package/dist/{package-4GTJGUXI.js → package-OKLFO7UY.js} +3 -1
- package/dist/{send-4GKDO26C.js → send-BNDTLUPM.js} +2 -2
- package/dist/skill-2Y42P4JY.js +287 -0
- package/dist/{up-LT3X5Q26.js → up-7B3BWF2U.js} +1 -1
- package/dist/web-assets/assets/index-CtiimdWK.css +1 -0
- package/dist/web-assets/assets/index-kt1_EcuO.js +63 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0007_system_prompts.sql +5 -0
- package/drizzle/0008_volute_channels.sql +24 -0
- package/drizzle/0009_shared_skills.sql +9 -0
- package/drizzle/meta/0007_snapshot.json +7 -0
- package/drizzle/meta/0008_snapshot.json +7 -0
- package/drizzle/meta/0009_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +3 -1
- package/templates/_base/.init/.config/prompts.json +5 -0
- package/templates/_base/_skills/volute-mind/SKILL.md +17 -1
- package/templates/_base/src/lib/router.ts +45 -28
- package/templates/_base/src/lib/routing.ts +4 -1
- package/templates/_base/src/lib/startup.ts +43 -0
- package/templates/claude/src/agent.ts +4 -3
- package/templates/claude/src/lib/hooks/reply-instructions.ts +3 -1
- package/templates/pi/src/agent.ts +5 -6
- package/templates/pi/src/lib/reply-instructions-extension.ts +3 -1
- package/dist/web-assets/assets/index-BcmT7Qxo.js +0 -63
- package/dist/web-assets/assets/index-DG01TyLb.css +0 -1
- /package/dist/{chunk-77ISBIKI.js → chunk-6DVBMLVN.js} +0 -0
package/dist/daemon.js
CHANGED
|
@@ -10,15 +10,30 @@ import {
|
|
|
10
10
|
readSystemsConfig
|
|
11
11
|
} from "./chunk-37X7ECMF.js";
|
|
12
12
|
import {
|
|
13
|
+
PROMPT_DEFAULTS,
|
|
14
|
+
PROMPT_KEYS,
|
|
13
15
|
RotatingLog,
|
|
14
16
|
clearJsonMap,
|
|
17
|
+
conversationParticipants,
|
|
18
|
+
conversations,
|
|
19
|
+
getDb,
|
|
15
20
|
getMindManager,
|
|
21
|
+
getMindPromptDefaults,
|
|
22
|
+
getPrompt,
|
|
23
|
+
getPromptIfCustom,
|
|
16
24
|
initMindManager,
|
|
17
25
|
loadJsonMap,
|
|
18
26
|
logBuffer,
|
|
19
27
|
logger_default,
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
messages,
|
|
29
|
+
mindHistory,
|
|
30
|
+
saveJsonMap,
|
|
31
|
+
sessions,
|
|
32
|
+
sharedSkills,
|
|
33
|
+
substitute,
|
|
34
|
+
systemPrompts,
|
|
35
|
+
users
|
|
36
|
+
} from "./chunk-AYB7XAWO.js";
|
|
22
37
|
import {
|
|
23
38
|
findOpenClawSession,
|
|
24
39
|
importOpenClawConnectors,
|
|
@@ -37,7 +52,7 @@ import {
|
|
|
37
52
|
import {
|
|
38
53
|
CHANNELS,
|
|
39
54
|
getChannelDriver
|
|
40
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-FW5API7X.js";
|
|
41
56
|
import {
|
|
42
57
|
exec,
|
|
43
58
|
gitExec,
|
|
@@ -60,7 +75,7 @@ import "./chunk-D424ZQGI.js";
|
|
|
60
75
|
import {
|
|
61
76
|
buildVoluteSlug,
|
|
62
77
|
writeChannelEntry
|
|
63
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-GK4E7LM7.js";
|
|
64
79
|
import {
|
|
65
80
|
addMind,
|
|
66
81
|
addVariant,
|
|
@@ -85,15 +100,13 @@ import {
|
|
|
85
100
|
validateMindName,
|
|
86
101
|
voluteHome
|
|
87
102
|
} from "./chunk-M77QBTEH.js";
|
|
88
|
-
import
|
|
89
|
-
__export
|
|
90
|
-
} from "./chunk-K3NQKI34.js";
|
|
103
|
+
import "./chunk-K3NQKI34.js";
|
|
91
104
|
|
|
92
105
|
// src/daemon.ts
|
|
93
106
|
import { randomBytes } from "crypto";
|
|
94
|
-
import { mkdirSync as
|
|
107
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
95
108
|
import { homedir as homedir2 } from "os";
|
|
96
|
-
import { resolve as
|
|
109
|
+
import { resolve as resolve18 } from "path";
|
|
97
110
|
import { format } from "util";
|
|
98
111
|
|
|
99
112
|
// src/lib/connector-manager.ts
|
|
@@ -343,19 +356,19 @@ var ConnectorManager = class {
|
|
|
343
356
|
const stopKey = `${mindName}:${type}`;
|
|
344
357
|
this.stopping.add(stopKey);
|
|
345
358
|
mindMap.delete(type);
|
|
346
|
-
await new Promise((
|
|
347
|
-
tracked.child.on("exit", () =>
|
|
359
|
+
await new Promise((resolve19) => {
|
|
360
|
+
tracked.child.on("exit", () => resolve19());
|
|
348
361
|
try {
|
|
349
362
|
tracked.child.kill("SIGTERM");
|
|
350
363
|
} catch {
|
|
351
|
-
|
|
364
|
+
resolve19();
|
|
352
365
|
}
|
|
353
366
|
setTimeout(() => {
|
|
354
367
|
try {
|
|
355
368
|
tracked.child.kill("SIGKILL");
|
|
356
369
|
} catch {
|
|
357
370
|
}
|
|
358
|
-
|
|
371
|
+
resolve19();
|
|
359
372
|
}, 5e3);
|
|
360
373
|
});
|
|
361
374
|
this.stopping.delete(stopKey);
|
|
@@ -632,9 +645,9 @@ var MailPoller = class {
|
|
|
632
645
|
}
|
|
633
646
|
const channel = `mail:${email.from.address}`;
|
|
634
647
|
const sender = email.from.name || email.from.address;
|
|
635
|
-
const
|
|
648
|
+
const text = formatEmailContent(email);
|
|
636
649
|
const body = JSON.stringify({
|
|
637
|
-
content: [{ type: "text", text
|
|
650
|
+
content: [{ type: "text", text }],
|
|
638
651
|
channel,
|
|
639
652
|
sender,
|
|
640
653
|
platform: "Email",
|
|
@@ -1164,129 +1177,12 @@ import { createMiddleware } from "hono/factory";
|
|
|
1164
1177
|
// src/lib/auth.ts
|
|
1165
1178
|
import { compareSync, hashSync } from "bcryptjs";
|
|
1166
1179
|
import { and, count, eq } from "drizzle-orm";
|
|
1167
|
-
|
|
1168
|
-
// src/lib/db.ts
|
|
1169
|
-
import { chmodSync, existsSync as existsSync5 } from "fs";
|
|
1170
|
-
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
1171
|
-
import { fileURLToPath } from "url";
|
|
1172
|
-
import { drizzle } from "drizzle-orm/libsql";
|
|
1173
|
-
import { migrate } from "drizzle-orm/libsql/migrator";
|
|
1174
|
-
|
|
1175
|
-
// src/lib/schema.ts
|
|
1176
|
-
var schema_exports = {};
|
|
1177
|
-
__export(schema_exports, {
|
|
1178
|
-
conversationParticipants: () => conversationParticipants,
|
|
1179
|
-
conversations: () => conversations,
|
|
1180
|
-
messages: () => messages,
|
|
1181
|
-
mindHistory: () => mindHistory,
|
|
1182
|
-
sessions: () => sessions,
|
|
1183
|
-
users: () => users
|
|
1184
|
-
});
|
|
1185
|
-
import { sql } from "drizzle-orm";
|
|
1186
|
-
import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
1187
|
-
var users = sqliteTable("users", {
|
|
1188
|
-
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
1189
|
-
username: text("username").unique().notNull(),
|
|
1190
|
-
password_hash: text("password_hash").notNull(),
|
|
1191
|
-
role: text("role").notNull().default("pending"),
|
|
1192
|
-
user_type: text("user_type").notNull().default("human"),
|
|
1193
|
-
created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
|
|
1194
|
-
});
|
|
1195
|
-
var conversations = sqliteTable(
|
|
1196
|
-
"conversations",
|
|
1197
|
-
{
|
|
1198
|
-
id: text("id").primaryKey(),
|
|
1199
|
-
mind_name: text("mind_name").notNull(),
|
|
1200
|
-
channel: text("channel").notNull(),
|
|
1201
|
-
user_id: integer("user_id").references(() => users.id),
|
|
1202
|
-
title: text("title"),
|
|
1203
|
-
created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
|
|
1204
|
-
updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
|
|
1205
|
-
},
|
|
1206
|
-
(table) => [
|
|
1207
|
-
index("idx_conversations_mind_name").on(table.mind_name),
|
|
1208
|
-
index("idx_conversations_user_id").on(table.user_id),
|
|
1209
|
-
index("idx_conversations_updated_at").on(table.updated_at)
|
|
1210
|
-
]
|
|
1211
|
-
);
|
|
1212
|
-
var mindHistory = sqliteTable(
|
|
1213
|
-
"mind_history",
|
|
1214
|
-
{
|
|
1215
|
-
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
1216
|
-
mind: text("mind").notNull(),
|
|
1217
|
-
channel: text("channel"),
|
|
1218
|
-
session: text("session"),
|
|
1219
|
-
sender: text("sender"),
|
|
1220
|
-
message_id: text("message_id"),
|
|
1221
|
-
type: text("type").notNull(),
|
|
1222
|
-
content: text("content"),
|
|
1223
|
-
metadata: text("metadata"),
|
|
1224
|
-
created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
|
|
1225
|
-
},
|
|
1226
|
-
(table) => [
|
|
1227
|
-
index("idx_mind_history_mind").on(table.mind),
|
|
1228
|
-
index("idx_mind_history_mind_channel").on(table.mind, table.channel),
|
|
1229
|
-
index("idx_mind_history_mind_type").on(table.mind, table.type)
|
|
1230
|
-
]
|
|
1231
|
-
);
|
|
1232
|
-
var conversationParticipants = sqliteTable(
|
|
1233
|
-
"conversation_participants",
|
|
1234
|
-
{
|
|
1235
|
-
conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
1236
|
-
user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1237
|
-
role: text("role").notNull().default("member"),
|
|
1238
|
-
joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
|
|
1239
|
-
},
|
|
1240
|
-
(table) => [
|
|
1241
|
-
uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
|
|
1242
|
-
index("idx_cp_user_id").on(table.user_id)
|
|
1243
|
-
]
|
|
1244
|
-
);
|
|
1245
|
-
var sessions = sqliteTable("sessions", {
|
|
1246
|
-
id: text("id").primaryKey(),
|
|
1247
|
-
userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
|
|
1248
|
-
createdAt: integer("created_at").notNull()
|
|
1249
|
-
});
|
|
1250
|
-
var messages = sqliteTable(
|
|
1251
|
-
"messages",
|
|
1252
|
-
{
|
|
1253
|
-
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
1254
|
-
conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
1255
|
-
role: text("role").notNull(),
|
|
1256
|
-
sender_name: text("sender_name"),
|
|
1257
|
-
content: text("content").notNull(),
|
|
1258
|
-
created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
|
|
1259
|
-
},
|
|
1260
|
-
(table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
|
|
1261
|
-
);
|
|
1262
|
-
|
|
1263
|
-
// src/lib/db.ts
|
|
1264
|
-
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
1265
|
-
var migrationsFolder = existsSync5(resolve6(__dirname, "../drizzle")) ? resolve6(__dirname, "../drizzle") : resolve6(__dirname, "../../drizzle");
|
|
1266
|
-
var db = null;
|
|
1267
|
-
async function getDb() {
|
|
1268
|
-
if (db) return db;
|
|
1269
|
-
const dbPath = process.env.VOLUTE_DB_PATH || resolve6(voluteHome(), "volute.db");
|
|
1270
|
-
db = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
|
|
1271
|
-
await migrate(db, { migrationsFolder });
|
|
1272
|
-
try {
|
|
1273
|
-
chmodSync(dbPath, 384);
|
|
1274
|
-
} catch (err) {
|
|
1275
|
-
console.error(
|
|
1276
|
-
`[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
|
|
1277
|
-
err
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
return db;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// src/lib/auth.ts
|
|
1284
1180
|
async function createUser(username, password) {
|
|
1285
|
-
const
|
|
1181
|
+
const db = await getDb();
|
|
1286
1182
|
const hash = hashSync(password, 10);
|
|
1287
|
-
const [{ value }] = await
|
|
1183
|
+
const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "human"));
|
|
1288
1184
|
const role = value === 0 ? "admin" : "pending";
|
|
1289
|
-
const [result] = await
|
|
1185
|
+
const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning({
|
|
1290
1186
|
id: users.id,
|
|
1291
1187
|
username: users.username,
|
|
1292
1188
|
role: users.role,
|
|
@@ -1296,8 +1192,8 @@ async function createUser(username, password) {
|
|
|
1296
1192
|
return result;
|
|
1297
1193
|
}
|
|
1298
1194
|
async function verifyUser(username, password) {
|
|
1299
|
-
const
|
|
1300
|
-
const row = await
|
|
1195
|
+
const db = await getDb();
|
|
1196
|
+
const row = await db.select().from(users).where(eq(users.username, username)).get();
|
|
1301
1197
|
if (!row) return null;
|
|
1302
1198
|
if (row.user_type === "mind") return null;
|
|
1303
1199
|
if (!compareSync(password, row.password_hash)) return null;
|
|
@@ -1305,8 +1201,8 @@ async function verifyUser(username, password) {
|
|
|
1305
1201
|
return user;
|
|
1306
1202
|
}
|
|
1307
1203
|
async function getUser(id) {
|
|
1308
|
-
const
|
|
1309
|
-
const row = await
|
|
1204
|
+
const db = await getDb();
|
|
1205
|
+
const row = await db.select({
|
|
1310
1206
|
id: users.id,
|
|
1311
1207
|
username: users.username,
|
|
1312
1208
|
role: users.role,
|
|
@@ -1316,8 +1212,8 @@ async function getUser(id) {
|
|
|
1316
1212
|
return row ?? null;
|
|
1317
1213
|
}
|
|
1318
1214
|
async function getUserByUsername(username) {
|
|
1319
|
-
const
|
|
1320
|
-
const row = await
|
|
1215
|
+
const db = await getDb();
|
|
1216
|
+
const row = await db.select({
|
|
1321
1217
|
id: users.id,
|
|
1322
1218
|
username: users.username,
|
|
1323
1219
|
role: users.role,
|
|
@@ -1327,8 +1223,8 @@ async function getUserByUsername(username) {
|
|
|
1327
1223
|
return row ?? null;
|
|
1328
1224
|
}
|
|
1329
1225
|
async function listUsers() {
|
|
1330
|
-
const
|
|
1331
|
-
return
|
|
1226
|
+
const db = await getDb();
|
|
1227
|
+
return db.select({
|
|
1332
1228
|
id: users.id,
|
|
1333
1229
|
username: users.username,
|
|
1334
1230
|
role: users.role,
|
|
@@ -1337,8 +1233,8 @@ async function listUsers() {
|
|
|
1337
1233
|
}).from(users).orderBy(users.created_at).all();
|
|
1338
1234
|
}
|
|
1339
1235
|
async function listPendingUsers() {
|
|
1340
|
-
const
|
|
1341
|
-
return
|
|
1236
|
+
const db = await getDb();
|
|
1237
|
+
return db.select({
|
|
1342
1238
|
id: users.id,
|
|
1343
1239
|
username: users.username,
|
|
1344
1240
|
role: users.role,
|
|
@@ -1347,8 +1243,8 @@ async function listPendingUsers() {
|
|
|
1347
1243
|
}).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
|
|
1348
1244
|
}
|
|
1349
1245
|
async function listUsersByType(userType) {
|
|
1350
|
-
const
|
|
1351
|
-
return
|
|
1246
|
+
const db = await getDb();
|
|
1247
|
+
return db.select({
|
|
1352
1248
|
id: users.id,
|
|
1353
1249
|
username: users.username,
|
|
1354
1250
|
role: users.role,
|
|
@@ -1357,8 +1253,8 @@ async function listUsersByType(userType) {
|
|
|
1357
1253
|
}).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
|
|
1358
1254
|
}
|
|
1359
1255
|
async function getOrCreateMindUser(mindName) {
|
|
1360
|
-
const
|
|
1361
|
-
const existing = await
|
|
1256
|
+
const db = await getDb();
|
|
1257
|
+
const existing = await db.select({
|
|
1362
1258
|
id: users.id,
|
|
1363
1259
|
username: users.username,
|
|
1364
1260
|
role: users.role,
|
|
@@ -1367,7 +1263,7 @@ async function getOrCreateMindUser(mindName) {
|
|
|
1367
1263
|
}).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
|
|
1368
1264
|
if (existing) return existing;
|
|
1369
1265
|
try {
|
|
1370
|
-
const [result] = await
|
|
1266
|
+
const [result] = await db.insert(users).values({
|
|
1371
1267
|
username: mindName,
|
|
1372
1268
|
password_hash: "!mind",
|
|
1373
1269
|
role: "mind",
|
|
@@ -1382,7 +1278,7 @@ async function getOrCreateMindUser(mindName) {
|
|
|
1382
1278
|
return result;
|
|
1383
1279
|
} catch (err) {
|
|
1384
1280
|
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
1385
|
-
const retried = await
|
|
1281
|
+
const retried = await db.select({
|
|
1386
1282
|
id: users.id,
|
|
1387
1283
|
username: users.username,
|
|
1388
1284
|
role: users.role,
|
|
@@ -1395,12 +1291,21 @@ async function getOrCreateMindUser(mindName) {
|
|
|
1395
1291
|
}
|
|
1396
1292
|
}
|
|
1397
1293
|
async function deleteMindUser2(mindName) {
|
|
1398
|
-
const
|
|
1399
|
-
await
|
|
1294
|
+
const db = await getDb();
|
|
1295
|
+
await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
|
|
1296
|
+
}
|
|
1297
|
+
async function changePassword(userId, currentPassword, newPassword) {
|
|
1298
|
+
const db = await getDb();
|
|
1299
|
+
const row = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
1300
|
+
if (!row) return false;
|
|
1301
|
+
if (!compareSync(currentPassword, row.password_hash)) return false;
|
|
1302
|
+
const hash = hashSync(newPassword, 10);
|
|
1303
|
+
await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
|
|
1304
|
+
return true;
|
|
1400
1305
|
}
|
|
1401
1306
|
async function approveUser(id) {
|
|
1402
|
-
const
|
|
1403
|
-
await
|
|
1307
|
+
const db = await getDb();
|
|
1308
|
+
await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
|
|
1404
1309
|
}
|
|
1405
1310
|
|
|
1406
1311
|
// src/web/middleware/auth.ts
|
|
@@ -1411,29 +1316,29 @@ function isValidDaemonToken(token) {
|
|
|
1411
1316
|
}
|
|
1412
1317
|
var SESSION_MAX_AGE = 864e5;
|
|
1413
1318
|
async function createSession(userId) {
|
|
1414
|
-
const
|
|
1319
|
+
const db = await getDb();
|
|
1415
1320
|
const sessionId = crypto.randomUUID();
|
|
1416
|
-
await
|
|
1321
|
+
await db.insert(sessions).values({ id: sessionId, userId, createdAt: Date.now() });
|
|
1417
1322
|
return sessionId;
|
|
1418
1323
|
}
|
|
1419
1324
|
async function deleteSession(sessionId) {
|
|
1420
|
-
const
|
|
1421
|
-
await
|
|
1325
|
+
const db = await getDb();
|
|
1326
|
+
await db.delete(sessions).where(eq2(sessions.id, sessionId));
|
|
1422
1327
|
}
|
|
1423
1328
|
async function getSessionUserId(sessionId) {
|
|
1424
|
-
const
|
|
1425
|
-
const row = await
|
|
1329
|
+
const db = await getDb();
|
|
1330
|
+
const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
|
|
1426
1331
|
if (!row) return void 0;
|
|
1427
1332
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
1428
|
-
await
|
|
1333
|
+
await db.delete(sessions).where(eq2(sessions.id, sessionId));
|
|
1429
1334
|
return void 0;
|
|
1430
1335
|
}
|
|
1431
1336
|
return row.userId;
|
|
1432
1337
|
}
|
|
1433
1338
|
async function cleanExpiredSessions() {
|
|
1434
|
-
const
|
|
1339
|
+
const db = await getDb();
|
|
1435
1340
|
const cutoff = Date.now() - SESSION_MAX_AGE;
|
|
1436
|
-
await
|
|
1341
|
+
await db.delete(sessions).where(lt(sessions.createdAt, cutoff));
|
|
1437
1342
|
}
|
|
1438
1343
|
var requireAdmin = createMiddleware(async (c, next) => {
|
|
1439
1344
|
const user = c.get("user");
|
|
@@ -1464,13 +1369,13 @@ var authMiddleware = createMiddleware(async (c, next) => {
|
|
|
1464
1369
|
});
|
|
1465
1370
|
|
|
1466
1371
|
// src/web/server.ts
|
|
1467
|
-
import { existsSync as
|
|
1372
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1468
1373
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1469
|
-
import { dirname as
|
|
1374
|
+
import { dirname as dirname2, extname as extname2, resolve as resolve17 } from "path";
|
|
1470
1375
|
import { serve } from "@hono/node-server";
|
|
1471
1376
|
|
|
1472
1377
|
// src/web/app.ts
|
|
1473
|
-
import { Hono as
|
|
1378
|
+
import { Hono as Hono21 } from "hono";
|
|
1474
1379
|
import { bodyLimit } from "hono/body-limit";
|
|
1475
1380
|
import { csrf } from "hono/csrf";
|
|
1476
1381
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -1484,6 +1389,17 @@ var credentialsSchema = z.object({
|
|
|
1484
1389
|
username: z.string().min(1),
|
|
1485
1390
|
password: z.string().min(1)
|
|
1486
1391
|
});
|
|
1392
|
+
var changePasswordSchema = z.object({
|
|
1393
|
+
currentPassword: z.string().min(1),
|
|
1394
|
+
newPassword: z.string().min(1)
|
|
1395
|
+
});
|
|
1396
|
+
var authenticated = new Hono().use(authMiddleware).post("/change-password", zValidator("json", changePasswordSchema), async (c) => {
|
|
1397
|
+
const user = c.get("user");
|
|
1398
|
+
const { currentPassword, newPassword } = c.req.valid("json");
|
|
1399
|
+
const ok = await changePassword(user.id, currentPassword, newPassword);
|
|
1400
|
+
if (!ok) return c.json({ error: "Current password is incorrect" }, 400);
|
|
1401
|
+
return c.json({ ok: true });
|
|
1402
|
+
});
|
|
1487
1403
|
var admin = new Hono().use(authMiddleware).get("/users", async (c) => {
|
|
1488
1404
|
const user = c.get("user");
|
|
1489
1405
|
if (user.role !== "admin") return c.json({ error: "Forbidden" }, 403);
|
|
@@ -1543,7 +1459,7 @@ var app = new Hono().post("/register", zValidator("json", credentialsSchema), as
|
|
|
1543
1459
|
const user = await getUser(userId);
|
|
1544
1460
|
if (!user) return c.json({ error: "Not logged in" }, 401);
|
|
1545
1461
|
return c.json({ id: user.id, username: user.username, role: user.role });
|
|
1546
|
-
}).route("/", admin);
|
|
1462
|
+
}).route("/", admin).route("/", authenticated);
|
|
1547
1463
|
var auth_default = app;
|
|
1548
1464
|
|
|
1549
1465
|
// src/web/api/channels.ts
|
|
@@ -1797,9 +1713,9 @@ var sharedEnvApp = new Hono4().get("/", (c) => {
|
|
|
1797
1713
|
var env_default = app4;
|
|
1798
1714
|
|
|
1799
1715
|
// src/web/api/files.ts
|
|
1800
|
-
import { existsSync as
|
|
1716
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1801
1717
|
import { readdir, readFile } from "fs/promises";
|
|
1802
|
-
import { resolve as
|
|
1718
|
+
import { resolve as resolve6 } from "path";
|
|
1803
1719
|
import { Hono as Hono5 } from "hono";
|
|
1804
1720
|
var ALLOWED_FILES = /* @__PURE__ */ new Set(["SOUL.md", "MEMORY.md", "CLAUDE.md", "VOLUTE.md"]);
|
|
1805
1721
|
var app5 = new Hono5().get("/:name/files", async (c) => {
|
|
@@ -1807,8 +1723,8 @@ var app5 = new Hono5().get("/:name/files", async (c) => {
|
|
|
1807
1723
|
const entry = findMind(name);
|
|
1808
1724
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1809
1725
|
const dir = mindDir(name);
|
|
1810
|
-
const homeDir =
|
|
1811
|
-
if (!
|
|
1726
|
+
const homeDir = resolve6(dir, "home");
|
|
1727
|
+
if (!existsSync5(homeDir)) return c.json({ error: "Home directory missing" }, 404);
|
|
1812
1728
|
const allFiles = await readdir(homeDir);
|
|
1813
1729
|
const files = allFiles.filter((f) => f.endsWith(".md") && ALLOWED_FILES.has(f));
|
|
1814
1730
|
return c.json(files);
|
|
@@ -1821,8 +1737,8 @@ var app5 = new Hono5().get("/:name/files", async (c) => {
|
|
|
1821
1737
|
const entry = findMind(name);
|
|
1822
1738
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1823
1739
|
const dir = mindDir(name);
|
|
1824
|
-
const filePath =
|
|
1825
|
-
if (!
|
|
1740
|
+
const filePath = resolve6(dir, "home", filename);
|
|
1741
|
+
if (!existsSync5(filePath)) {
|
|
1826
1742
|
return c.json({ error: "File not found" }, 404);
|
|
1827
1743
|
}
|
|
1828
1744
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -1832,16 +1748,16 @@ var files_default = app5;
|
|
|
1832
1748
|
|
|
1833
1749
|
// src/web/api/logs.ts
|
|
1834
1750
|
import { spawn as spawn2 } from "child_process";
|
|
1835
|
-
import { existsSync as
|
|
1836
|
-
import { resolve as
|
|
1751
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1752
|
+
import { resolve as resolve7 } from "path";
|
|
1837
1753
|
import { Hono as Hono6 } from "hono";
|
|
1838
1754
|
import { streamSSE } from "hono/streaming";
|
|
1839
1755
|
var app6 = new Hono6().get("/:name/logs", async (c) => {
|
|
1840
1756
|
const name = c.req.param("name");
|
|
1841
1757
|
const entry = findMind(name);
|
|
1842
1758
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1843
|
-
const logFile =
|
|
1844
|
-
if (!
|
|
1759
|
+
const logFile = resolve7(stateDir(name), "logs", "mind.log");
|
|
1760
|
+
if (!existsSync6(logFile)) {
|
|
1845
1761
|
return c.json({ error: "No log file found" }, 404);
|
|
1846
1762
|
}
|
|
1847
1763
|
return streamSSE(c, async (stream) => {
|
|
@@ -1859,17 +1775,17 @@ var app6 = new Hono6().get("/:name/logs", async (c) => {
|
|
|
1859
1775
|
stream.onAbort(() => {
|
|
1860
1776
|
tail.kill();
|
|
1861
1777
|
});
|
|
1862
|
-
await new Promise((
|
|
1863
|
-
tail.on("exit",
|
|
1864
|
-
stream.onAbort(
|
|
1778
|
+
await new Promise((resolve19) => {
|
|
1779
|
+
tail.on("exit", resolve19);
|
|
1780
|
+
stream.onAbort(resolve19);
|
|
1865
1781
|
});
|
|
1866
1782
|
});
|
|
1867
1783
|
}).get("/:name/logs/tail", async (c) => {
|
|
1868
1784
|
const name = c.req.param("name");
|
|
1869
1785
|
const entry = findMind(name);
|
|
1870
1786
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
1871
|
-
const logFile =
|
|
1872
|
-
if (!
|
|
1787
|
+
const logFile = resolve7(stateDir(name), "logs", "mind.log");
|
|
1788
|
+
if (!existsSync6(logFile)) {
|
|
1873
1789
|
return c.json({ error: "No log file found" }, 404);
|
|
1874
1790
|
}
|
|
1875
1791
|
const nParam = parseInt(c.req.query("n") ?? "50", 10);
|
|
@@ -1879,44 +1795,421 @@ var app6 = new Hono6().get("/:name/logs", async (c) => {
|
|
|
1879
1795
|
tail.stdout.on("data", (data) => {
|
|
1880
1796
|
output += data.toString();
|
|
1881
1797
|
});
|
|
1882
|
-
await new Promise((
|
|
1883
|
-
tail.on("exit",
|
|
1798
|
+
await new Promise((resolve19) => {
|
|
1799
|
+
tail.on("exit", resolve19);
|
|
1884
1800
|
});
|
|
1885
1801
|
return c.text(output);
|
|
1886
1802
|
});
|
|
1887
1803
|
var logs_default = app6;
|
|
1888
1804
|
|
|
1889
|
-
// src/web/api/
|
|
1805
|
+
// src/web/api/mind-skills.ts
|
|
1806
|
+
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
1807
|
+
import { Hono as Hono7 } from "hono";
|
|
1808
|
+
import { z as z2 } from "zod";
|
|
1809
|
+
|
|
1810
|
+
// src/lib/skills.ts
|
|
1890
1811
|
import {
|
|
1891
1812
|
cpSync,
|
|
1892
|
-
existsSync as
|
|
1893
|
-
mkdirSync as
|
|
1894
|
-
readdirSync as
|
|
1895
|
-
readFileSync as
|
|
1813
|
+
existsSync as existsSync7,
|
|
1814
|
+
mkdirSync as mkdirSync3,
|
|
1815
|
+
readdirSync as readdirSync2,
|
|
1816
|
+
readFileSync as readFileSync4,
|
|
1896
1817
|
rmSync,
|
|
1818
|
+
writeFileSync as writeFileSync3
|
|
1819
|
+
} from "fs";
|
|
1820
|
+
import { tmpdir } from "os";
|
|
1821
|
+
import { basename, join, resolve as resolve8 } from "path";
|
|
1822
|
+
import { eq as eq3, sql } from "drizzle-orm";
|
|
1823
|
+
var VALID_SKILL_ID = /^[a-zA-Z0-9_-]+$/;
|
|
1824
|
+
function validateSkillId(id) {
|
|
1825
|
+
if (!id || !VALID_SKILL_ID.test(id)) {
|
|
1826
|
+
throw new Error(`Invalid skill ID: ${id}`);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function sharedSkillsDir() {
|
|
1830
|
+
return resolve8(voluteHome(), "skills");
|
|
1831
|
+
}
|
|
1832
|
+
function parseSkillMd(content) {
|
|
1833
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1834
|
+
if (!match) return { name: "", description: "" };
|
|
1835
|
+
const frontmatter = match[1];
|
|
1836
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
1837
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
1838
|
+
return {
|
|
1839
|
+
name: nameMatch?.[1].trim() ?? "",
|
|
1840
|
+
description: descMatch?.[1].trim() ?? ""
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
async function listSharedSkills() {
|
|
1844
|
+
const db = await getDb();
|
|
1845
|
+
return db.select().from(sharedSkills).all();
|
|
1846
|
+
}
|
|
1847
|
+
async function getSharedSkill(id) {
|
|
1848
|
+
const db = await getDb();
|
|
1849
|
+
return db.select().from(sharedSkills).where(eq3(sharedSkills.id, id)).get();
|
|
1850
|
+
}
|
|
1851
|
+
async function importSkillFromDir(sourceDir, author) {
|
|
1852
|
+
const skillMdPath = join(sourceDir, "SKILL.md");
|
|
1853
|
+
if (!existsSync7(skillMdPath)) {
|
|
1854
|
+
throw new Error("SKILL.md not found in source directory");
|
|
1855
|
+
}
|
|
1856
|
+
const content = readFileSync4(skillMdPath, "utf-8");
|
|
1857
|
+
const { name, description } = parseSkillMd(content);
|
|
1858
|
+
const id = basename(sourceDir);
|
|
1859
|
+
if (!id || id === "." || id === "..") {
|
|
1860
|
+
throw new Error("Invalid skill directory name");
|
|
1861
|
+
}
|
|
1862
|
+
validateSkillId(id);
|
|
1863
|
+
const destDir = join(sharedSkillsDir(), id);
|
|
1864
|
+
if (existsSync7(destDir)) rmSync(destDir, { recursive: true });
|
|
1865
|
+
mkdirSync3(destDir, { recursive: true });
|
|
1866
|
+
cpSync(sourceDir, destDir, { recursive: true });
|
|
1867
|
+
const upstreamPath = join(destDir, ".upstream.json");
|
|
1868
|
+
if (existsSync7(upstreamPath)) rmSync(upstreamPath);
|
|
1869
|
+
const db = await getDb();
|
|
1870
|
+
const existing = await db.select().from(sharedSkills).where(eq3(sharedSkills.id, id)).get();
|
|
1871
|
+
const version = existing ? existing.version + 1 : 1;
|
|
1872
|
+
await db.insert(sharedSkills).values({ id, name: name || id, description, author, version }).onConflictDoUpdate({
|
|
1873
|
+
target: sharedSkills.id,
|
|
1874
|
+
set: {
|
|
1875
|
+
name: name || id,
|
|
1876
|
+
description,
|
|
1877
|
+
author,
|
|
1878
|
+
version,
|
|
1879
|
+
updated_at: sql`(datetime('now'))`
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
const row = await db.select().from(sharedSkills).where(eq3(sharedSkills.id, id)).get();
|
|
1883
|
+
if (!row) throw new Error(`Failed to upsert shared skill: ${id}`);
|
|
1884
|
+
return row;
|
|
1885
|
+
}
|
|
1886
|
+
async function removeSharedSkill(id) {
|
|
1887
|
+
const db = await getDb();
|
|
1888
|
+
const existing = await db.select().from(sharedSkills).where(eq3(sharedSkills.id, id)).get();
|
|
1889
|
+
if (!existing) throw new Error(`Shared skill not found: ${id}`);
|
|
1890
|
+
await db.delete(sharedSkills).where(eq3(sharedSkills.id, id));
|
|
1891
|
+
const dir = join(sharedSkillsDir(), id);
|
|
1892
|
+
if (existsSync7(dir)) rmSync(dir, { recursive: true });
|
|
1893
|
+
}
|
|
1894
|
+
function mindSkillsDir(dir) {
|
|
1895
|
+
return resolve8(dir, "home", ".claude", "skills");
|
|
1896
|
+
}
|
|
1897
|
+
function readUpstream(skillDir) {
|
|
1898
|
+
const upstreamPath = join(skillDir, ".upstream.json");
|
|
1899
|
+
if (!existsSync7(upstreamPath)) return null;
|
|
1900
|
+
try {
|
|
1901
|
+
const data = JSON.parse(readFileSync4(upstreamPath, "utf-8"));
|
|
1902
|
+
if (typeof data?.source !== "string" || typeof data?.version !== "number" || typeof data?.baseCommit !== "string") {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
return data;
|
|
1906
|
+
} catch {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
async function installSkill(_mindName, dir, skillId) {
|
|
1911
|
+
validateSkillId(skillId);
|
|
1912
|
+
const shared = await getSharedSkill(skillId);
|
|
1913
|
+
if (!shared) throw new Error(`Shared skill not found: ${skillId}`);
|
|
1914
|
+
const sourceDir = join(sharedSkillsDir(), skillId);
|
|
1915
|
+
if (!existsSync7(sourceDir)) throw new Error(`Shared skill files not found: ${skillId}`);
|
|
1916
|
+
const destDir = join(mindSkillsDir(dir), skillId);
|
|
1917
|
+
if (existsSync7(destDir)) throw new Error(`Skill already installed: ${skillId}`);
|
|
1918
|
+
mkdirSync3(destDir, { recursive: true });
|
|
1919
|
+
cpSync(sourceDir, destDir, { recursive: true });
|
|
1920
|
+
await gitExec(["add", join("home", ".claude", "skills", skillId)], { cwd: dir });
|
|
1921
|
+
await gitExec(["commit", "-m", `Install shared skill: ${skillId}`], { cwd: dir });
|
|
1922
|
+
const commitHash = (await gitExec(["rev-parse", "HEAD"], { cwd: dir })).trim();
|
|
1923
|
+
const upstream = {
|
|
1924
|
+
source: skillId,
|
|
1925
|
+
version: shared.version,
|
|
1926
|
+
baseCommit: commitHash
|
|
1927
|
+
};
|
|
1928
|
+
writeFileSync3(join(destDir, ".upstream.json"), `${JSON.stringify(upstream, null, 2)}
|
|
1929
|
+
`);
|
|
1930
|
+
await gitExec(["add", join("home", ".claude", "skills", skillId, ".upstream.json")], {
|
|
1931
|
+
cwd: dir
|
|
1932
|
+
});
|
|
1933
|
+
await gitExec(["commit", "--amend", "--no-edit"], { cwd: dir });
|
|
1934
|
+
}
|
|
1935
|
+
async function uninstallSkill(_mindName, dir, skillId) {
|
|
1936
|
+
validateSkillId(skillId);
|
|
1937
|
+
const skillDir = join(mindSkillsDir(dir), skillId);
|
|
1938
|
+
if (!existsSync7(skillDir)) throw new Error(`Skill not installed: ${skillId}`);
|
|
1939
|
+
rmSync(skillDir, { recursive: true });
|
|
1940
|
+
await gitExec(["add", join("home", ".claude", "skills", skillId)], { cwd: dir });
|
|
1941
|
+
await gitExec(["commit", "-m", `Uninstall skill: ${skillId}`], { cwd: dir });
|
|
1942
|
+
}
|
|
1943
|
+
async function updateSkill(_mindName, dir, skillId) {
|
|
1944
|
+
validateSkillId(skillId);
|
|
1945
|
+
const skillDir = join(mindSkillsDir(dir), skillId);
|
|
1946
|
+
if (!existsSync7(skillDir)) throw new Error(`Skill not installed: ${skillId}`);
|
|
1947
|
+
const upstream = readUpstream(skillDir);
|
|
1948
|
+
if (!upstream) throw new Error(`No upstream tracking for skill: ${skillId}`);
|
|
1949
|
+
const shared = await getSharedSkill(upstream.source);
|
|
1950
|
+
if (!shared) throw new Error(`Shared skill no longer exists: ${upstream.source}`);
|
|
1951
|
+
if (shared.version <= upstream.version) {
|
|
1952
|
+
return { status: "up-to-date" };
|
|
1953
|
+
}
|
|
1954
|
+
const sourceDir = join(sharedSkillsDir(), upstream.source);
|
|
1955
|
+
if (!existsSync7(sourceDir)) throw new Error(`Shared skill files missing: ${upstream.source}`);
|
|
1956
|
+
const relSkillPath = join("home", ".claude", "skills", skillId);
|
|
1957
|
+
const currentFiles = listFilesRecursive(skillDir).filter((f) => f !== ".upstream.json");
|
|
1958
|
+
const newFiles = listFilesRecursive(sourceDir).filter((f) => f !== ".upstream.json");
|
|
1959
|
+
const allFiles = [.../* @__PURE__ */ new Set([...currentFiles, ...newFiles])];
|
|
1960
|
+
const conflictFiles = [];
|
|
1961
|
+
const tmpBase = join(tmpdir(), `volute-merge-${process.pid}-${Date.now()}`);
|
|
1962
|
+
mkdirSync3(tmpBase, { recursive: true });
|
|
1963
|
+
try {
|
|
1964
|
+
for (const file of allFiles) {
|
|
1965
|
+
const currentPath = join(skillDir, file);
|
|
1966
|
+
const newPath = join(sourceDir, file);
|
|
1967
|
+
const currentExists = existsSync7(currentPath);
|
|
1968
|
+
const newExists = existsSync7(newPath);
|
|
1969
|
+
if (!currentExists && newExists) {
|
|
1970
|
+
const destPath = join(skillDir, file);
|
|
1971
|
+
mkdirSync3(join(skillDir, ...file.split("/").slice(0, -1)), { recursive: true });
|
|
1972
|
+
cpSync(newPath, destPath);
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
if (currentExists && !newExists) {
|
|
1976
|
+
let baseContent2 = null;
|
|
1977
|
+
try {
|
|
1978
|
+
baseContent2 = await gitExec(
|
|
1979
|
+
["show", `${upstream.baseCommit}:${join(relSkillPath, file)}`],
|
|
1980
|
+
{ cwd: dir }
|
|
1981
|
+
);
|
|
1982
|
+
} catch {
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
const currentContent2 = readFileSync4(currentPath, "utf-8");
|
|
1986
|
+
if (currentContent2 === baseContent2) {
|
|
1987
|
+
rmSync(currentPath);
|
|
1988
|
+
}
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
let baseContent;
|
|
1992
|
+
try {
|
|
1993
|
+
baseContent = await gitExec(
|
|
1994
|
+
["show", `${upstream.baseCommit}:${join(relSkillPath, file)}`],
|
|
1995
|
+
{ cwd: dir }
|
|
1996
|
+
);
|
|
1997
|
+
} catch {
|
|
1998
|
+
baseContent = "";
|
|
1999
|
+
}
|
|
2000
|
+
const currentContent = readFileSync4(currentPath, "utf-8");
|
|
2001
|
+
const newContent = readFileSync4(newPath, "utf-8");
|
|
2002
|
+
if (currentContent === baseContent) {
|
|
2003
|
+
writeFileSync3(currentPath, newContent);
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
if (newContent === baseContent) {
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
const baseTmp = join(tmpBase, `${file}.base`);
|
|
2010
|
+
const currentTmp = join(tmpBase, `${file}.current`);
|
|
2011
|
+
const newTmp = join(tmpBase, `${file}.new`);
|
|
2012
|
+
mkdirSync3(join(tmpBase, ...file.split("/").slice(0, -1)), { recursive: true });
|
|
2013
|
+
writeFileSync3(baseTmp, baseContent);
|
|
2014
|
+
writeFileSync3(currentTmp, currentContent);
|
|
2015
|
+
writeFileSync3(newTmp, newContent);
|
|
2016
|
+
try {
|
|
2017
|
+
await exec("git", ["merge-file", currentTmp, baseTmp, newTmp]);
|
|
2018
|
+
writeFileSync3(currentPath, readFileSync4(currentTmp, "utf-8"));
|
|
2019
|
+
} catch (e) {
|
|
2020
|
+
const exitCode = e && typeof e === "object" && "code" in e ? e.code : null;
|
|
2021
|
+
if (exitCode === 1) {
|
|
2022
|
+
writeFileSync3(currentPath, readFileSync4(currentTmp, "utf-8"));
|
|
2023
|
+
conflictFiles.push(file);
|
|
2024
|
+
} else {
|
|
2025
|
+
throw e;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
} finally {
|
|
2030
|
+
rmSync(tmpBase, { recursive: true, force: true });
|
|
2031
|
+
}
|
|
2032
|
+
if (conflictFiles.length > 0) {
|
|
2033
|
+
return { status: "conflict", conflictFiles };
|
|
2034
|
+
}
|
|
2035
|
+
const upstreamInfo = {
|
|
2036
|
+
source: upstream.source,
|
|
2037
|
+
version: shared.version,
|
|
2038
|
+
baseCommit: upstream.baseCommit
|
|
2039
|
+
// will update after commit
|
|
2040
|
+
};
|
|
2041
|
+
writeFileSync3(join(skillDir, ".upstream.json"), `${JSON.stringify(upstreamInfo, null, 2)}
|
|
2042
|
+
`);
|
|
2043
|
+
await gitExec(["add", relSkillPath], { cwd: dir });
|
|
2044
|
+
await gitExec(["commit", "-m", `Update skill: ${skillId} (v${shared.version})`], { cwd: dir });
|
|
2045
|
+
const commitHash = (await gitExec(["rev-parse", "HEAD"], { cwd: dir })).trim();
|
|
2046
|
+
upstreamInfo.baseCommit = commitHash;
|
|
2047
|
+
writeFileSync3(join(skillDir, ".upstream.json"), `${JSON.stringify(upstreamInfo, null, 2)}
|
|
2048
|
+
`);
|
|
2049
|
+
await gitExec(["add", join(relSkillPath, ".upstream.json")], { cwd: dir });
|
|
2050
|
+
await gitExec(["commit", "--amend", "--no-edit"], { cwd: dir });
|
|
2051
|
+
return { status: "updated" };
|
|
2052
|
+
}
|
|
2053
|
+
async function listMindSkills(dir) {
|
|
2054
|
+
const skillsDir = mindSkillsDir(dir);
|
|
2055
|
+
if (!existsSync7(skillsDir)) return [];
|
|
2056
|
+
const entries = readdirSync2(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
2057
|
+
const sharedMap = /* @__PURE__ */ new Map();
|
|
2058
|
+
for (const s of await listSharedSkills()) {
|
|
2059
|
+
sharedMap.set(s.id, s);
|
|
2060
|
+
}
|
|
2061
|
+
const results = [];
|
|
2062
|
+
for (const entry of entries) {
|
|
2063
|
+
const skillDir = join(skillsDir, entry.name);
|
|
2064
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
2065
|
+
let name = entry.name;
|
|
2066
|
+
let description = "";
|
|
2067
|
+
if (existsSync7(skillMdPath)) {
|
|
2068
|
+
const parsed = parseSkillMd(readFileSync4(skillMdPath, "utf-8"));
|
|
2069
|
+
if (parsed.name) name = parsed.name;
|
|
2070
|
+
description = parsed.description;
|
|
2071
|
+
}
|
|
2072
|
+
const upstream = readUpstream(skillDir);
|
|
2073
|
+
let updateAvailable = false;
|
|
2074
|
+
if (upstream) {
|
|
2075
|
+
const shared = sharedMap.get(upstream.source);
|
|
2076
|
+
if (shared && shared.version > upstream.version) {
|
|
2077
|
+
updateAvailable = true;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
results.push({ id: entry.name, name, description, upstream, updateAvailable });
|
|
2081
|
+
}
|
|
2082
|
+
return results;
|
|
2083
|
+
}
|
|
2084
|
+
async function publishSkill(mindName, dir, skillId) {
|
|
2085
|
+
const skillDir = join(mindSkillsDir(dir), skillId);
|
|
2086
|
+
if (!existsSync7(skillDir)) throw new Error(`Skill not found: ${skillId}`);
|
|
2087
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
2088
|
+
if (!existsSync7(skillMdPath)) throw new Error(`SKILL.md not found in ${skillId}`);
|
|
2089
|
+
return importSkillFromDir(skillDir, mindName);
|
|
2090
|
+
}
|
|
2091
|
+
function listFilesRecursive(dir, prefix = "") {
|
|
2092
|
+
const results = [];
|
|
2093
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
2094
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2095
|
+
if (entry.isDirectory()) {
|
|
2096
|
+
results.push(...listFilesRecursive(join(dir, entry.name), rel));
|
|
2097
|
+
} else {
|
|
2098
|
+
results.push(rel);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return results;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// src/web/api/mind-skills.ts
|
|
2105
|
+
var app7 = new Hono7().get("/:name/skills", async (c) => {
|
|
2106
|
+
const name = c.req.param("name");
|
|
2107
|
+
const entry = findMind(name);
|
|
2108
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2109
|
+
const dir = mindDir(name);
|
|
2110
|
+
const skills = await listMindSkills(dir);
|
|
2111
|
+
return c.json(skills);
|
|
2112
|
+
}).post(
|
|
2113
|
+
"/:name/skills/install",
|
|
2114
|
+
requireAdmin,
|
|
2115
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2116
|
+
async (c) => {
|
|
2117
|
+
const name = c.req.param("name");
|
|
2118
|
+
const entry = findMind(name);
|
|
2119
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2120
|
+
const { skillId } = c.req.valid("json");
|
|
2121
|
+
const dir = mindDir(name);
|
|
2122
|
+
try {
|
|
2123
|
+
await installSkill(name, dir, skillId);
|
|
2124
|
+
} catch (e) {
|
|
2125
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2126
|
+
return c.json({ error: msg }, 400);
|
|
2127
|
+
}
|
|
2128
|
+
return c.json({ ok: true });
|
|
2129
|
+
}
|
|
2130
|
+
).post(
|
|
2131
|
+
"/:name/skills/update",
|
|
2132
|
+
requireAdmin,
|
|
2133
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2134
|
+
async (c) => {
|
|
2135
|
+
const name = c.req.param("name");
|
|
2136
|
+
const entry = findMind(name);
|
|
2137
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2138
|
+
const { skillId } = c.req.valid("json");
|
|
2139
|
+
const dir = mindDir(name);
|
|
2140
|
+
try {
|
|
2141
|
+
const result = await updateSkill(name, dir, skillId);
|
|
2142
|
+
return c.json(result);
|
|
2143
|
+
} catch (e) {
|
|
2144
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2145
|
+
return c.json({ error: msg }, 400);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
).post(
|
|
2149
|
+
"/:name/skills/publish",
|
|
2150
|
+
requireAdmin,
|
|
2151
|
+
zValidator2("json", z2.object({ skillId: z2.string() })),
|
|
2152
|
+
async (c) => {
|
|
2153
|
+
const name = c.req.param("name");
|
|
2154
|
+
const entry = findMind(name);
|
|
2155
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2156
|
+
const { skillId } = c.req.valid("json");
|
|
2157
|
+
const dir = mindDir(name);
|
|
2158
|
+
try {
|
|
2159
|
+
const skill = await publishSkill(name, dir, skillId);
|
|
2160
|
+
return c.json(skill);
|
|
2161
|
+
} catch (e) {
|
|
2162
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2163
|
+
return c.json({ error: msg }, 400);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
).delete("/:name/skills/:skill", requireAdmin, async (c) => {
|
|
2167
|
+
const name = c.req.param("name");
|
|
2168
|
+
const skillName = c.req.param("skill");
|
|
2169
|
+
const entry = findMind(name);
|
|
2170
|
+
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
2171
|
+
const dir = mindDir(name);
|
|
2172
|
+
try {
|
|
2173
|
+
await uninstallSkill(name, dir, skillName);
|
|
2174
|
+
} catch (e) {
|
|
2175
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2176
|
+
return c.json({ error: msg }, 400);
|
|
2177
|
+
}
|
|
2178
|
+
return c.json({ ok: true });
|
|
2179
|
+
});
|
|
2180
|
+
var mind_skills_default = app7;
|
|
2181
|
+
|
|
2182
|
+
// src/web/api/minds.ts
|
|
2183
|
+
import {
|
|
2184
|
+
cpSync as cpSync2,
|
|
2185
|
+
existsSync as existsSync8,
|
|
2186
|
+
mkdirSync as mkdirSync5,
|
|
2187
|
+
readdirSync as readdirSync4,
|
|
2188
|
+
readFileSync as readFileSync7,
|
|
2189
|
+
rmSync as rmSync2,
|
|
1897
2190
|
statSync,
|
|
1898
|
-
writeFileSync as
|
|
2191
|
+
writeFileSync as writeFileSync6
|
|
1899
2192
|
} from "fs";
|
|
1900
|
-
import { join, resolve as resolve11 } from "path";
|
|
1901
|
-
import { zValidator as
|
|
1902
|
-
import { and as and3, desc as desc2, eq as
|
|
1903
|
-
import { Hono as
|
|
1904
|
-
import { z as
|
|
2193
|
+
import { join as join2, resolve as resolve11 } from "path";
|
|
2194
|
+
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
2195
|
+
import { and as and3, desc as desc2, eq as eq5, sql as sql3 } from "drizzle-orm";
|
|
2196
|
+
import { Hono as Hono8 } from "hono";
|
|
2197
|
+
import { z as z3 } from "zod";
|
|
1905
2198
|
|
|
1906
2199
|
// src/lib/consolidate.ts
|
|
1907
|
-
import { readdirSync as
|
|
2200
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1908
2201
|
import { resolve as resolve9 } from "path";
|
|
1909
2202
|
async function consolidateMemory(mindDir2) {
|
|
1910
2203
|
const soulPath = resolve9(mindDir2, "home/SOUL.md");
|
|
1911
2204
|
const memoryPath = resolve9(mindDir2, "home/MEMORY.md");
|
|
1912
2205
|
const memoryDir = resolve9(mindDir2, "home/memory");
|
|
1913
|
-
const soul =
|
|
2206
|
+
const soul = readFileSync5(soulPath, "utf-8");
|
|
1914
2207
|
const logs = [];
|
|
1915
2208
|
try {
|
|
1916
|
-
const files =
|
|
2209
|
+
const files = readdirSync3(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
1917
2210
|
for (const filename of files) {
|
|
1918
2211
|
const date = filename.replace(".md", "");
|
|
1919
|
-
const content2 =
|
|
2212
|
+
const content2 = readFileSync5(resolve9(memoryDir, filename), "utf-8").trim();
|
|
1920
2213
|
if (content2) {
|
|
1921
2214
|
logs.push(`### ${date}
|
|
1922
2215
|
|
|
@@ -1966,7 +2259,7 @@ ${content2}`);
|
|
|
1966
2259
|
const data = await res.json();
|
|
1967
2260
|
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
1968
2261
|
if (content) {
|
|
1969
|
-
|
|
2262
|
+
writeFileSync4(memoryPath, `${content}
|
|
1970
2263
|
`);
|
|
1971
2264
|
console.log("MEMORY.md created successfully.");
|
|
1972
2265
|
} else {
|
|
@@ -1976,7 +2269,7 @@ ${content2}`);
|
|
|
1976
2269
|
|
|
1977
2270
|
// src/lib/conversations.ts
|
|
1978
2271
|
import { randomUUID } from "crypto";
|
|
1979
|
-
import { and as and2, desc, eq as
|
|
2272
|
+
import { and as and2, desc, eq as eq4, inArray, isNull, sql as sql2 } from "drizzle-orm";
|
|
1980
2273
|
|
|
1981
2274
|
// src/lib/conversation-events.ts
|
|
1982
2275
|
var subscribers = /* @__PURE__ */ new Map();
|
|
@@ -2008,13 +2301,17 @@ function publish(conversationId, event) {
|
|
|
2008
2301
|
|
|
2009
2302
|
// src/lib/conversations.ts
|
|
2010
2303
|
async function createConversation(mindName, channel, opts) {
|
|
2011
|
-
const
|
|
2304
|
+
const db = await getDb();
|
|
2012
2305
|
const id = randomUUID();
|
|
2013
|
-
|
|
2306
|
+
const type = opts?.type ?? "dm";
|
|
2307
|
+
const name = opts?.name ?? null;
|
|
2308
|
+
await db.transaction(async (tx) => {
|
|
2014
2309
|
await tx.insert(conversations).values({
|
|
2015
2310
|
id,
|
|
2016
2311
|
mind_name: mindName,
|
|
2017
2312
|
channel,
|
|
2313
|
+
type,
|
|
2314
|
+
name,
|
|
2018
2315
|
user_id: opts?.userId ?? null,
|
|
2019
2316
|
title: opts?.title ?? null
|
|
2020
2317
|
});
|
|
@@ -2032,6 +2329,8 @@ async function createConversation(mindName, channel, opts) {
|
|
|
2032
2329
|
id,
|
|
2033
2330
|
mind_name: mindName,
|
|
2034
2331
|
channel,
|
|
2332
|
+
type,
|
|
2333
|
+
name,
|
|
2035
2334
|
user_id: opts?.userId ?? null,
|
|
2036
2335
|
title: opts?.title ?? null,
|
|
2037
2336
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2039,41 +2338,58 @@ async function createConversation(mindName, channel, opts) {
|
|
|
2039
2338
|
};
|
|
2040
2339
|
}
|
|
2041
2340
|
async function getConversation(id) {
|
|
2042
|
-
const
|
|
2043
|
-
const row = await
|
|
2341
|
+
const db = await getDb();
|
|
2342
|
+
const row = await db.select().from(conversations).where(eq4(conversations.id, id)).get();
|
|
2044
2343
|
return row ?? null;
|
|
2045
2344
|
}
|
|
2345
|
+
async function addParticipant(conversationId, userId, role = "member") {
|
|
2346
|
+
const db = await getDb();
|
|
2347
|
+
await db.insert(conversationParticipants).values({
|
|
2348
|
+
conversation_id: conversationId,
|
|
2349
|
+
user_id: userId,
|
|
2350
|
+
role
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
async function removeParticipant(conversationId, userId) {
|
|
2354
|
+
const db = await getDb();
|
|
2355
|
+
await db.delete(conversationParticipants).where(
|
|
2356
|
+
and2(
|
|
2357
|
+
eq4(conversationParticipants.conversation_id, conversationId),
|
|
2358
|
+
eq4(conversationParticipants.user_id, userId)
|
|
2359
|
+
)
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2046
2362
|
async function getParticipants(conversationId) {
|
|
2047
|
-
const
|
|
2048
|
-
const rows = await
|
|
2363
|
+
const db = await getDb();
|
|
2364
|
+
const rows = await db.select({
|
|
2049
2365
|
userId: conversationParticipants.user_id,
|
|
2050
2366
|
username: users.username,
|
|
2051
2367
|
userType: users.user_type,
|
|
2052
2368
|
role: conversationParticipants.role
|
|
2053
|
-
}).from(conversationParticipants).innerJoin(users,
|
|
2369
|
+
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(eq4(conversationParticipants.conversation_id, conversationId)).all();
|
|
2054
2370
|
return rows;
|
|
2055
2371
|
}
|
|
2056
2372
|
async function isParticipant(conversationId, userId) {
|
|
2057
|
-
const
|
|
2058
|
-
const row = await
|
|
2373
|
+
const db = await getDb();
|
|
2374
|
+
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
2059
2375
|
and2(
|
|
2060
|
-
|
|
2061
|
-
|
|
2376
|
+
eq4(conversationParticipants.conversation_id, conversationId),
|
|
2377
|
+
eq4(conversationParticipants.user_id, userId)
|
|
2062
2378
|
)
|
|
2063
2379
|
).get();
|
|
2064
2380
|
return row != null;
|
|
2065
2381
|
}
|
|
2066
2382
|
async function listConversationsForUser(userId) {
|
|
2067
|
-
const
|
|
2068
|
-
const participantRows = await
|
|
2383
|
+
const db = await getDb();
|
|
2384
|
+
const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq4(conversationParticipants.user_id, userId)).all();
|
|
2069
2385
|
if (participantRows.length === 0) return [];
|
|
2070
2386
|
const convIds = participantRows.map((r) => r.conversation_id);
|
|
2071
|
-
return
|
|
2387
|
+
return await db.select().from(conversations).where(inArray(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
2072
2388
|
}
|
|
2073
2389
|
async function isParticipantOrOwner(conversationId, userId) {
|
|
2074
2390
|
if (await isParticipant(conversationId, userId)) return true;
|
|
2075
|
-
const
|
|
2076
|
-
const row = await
|
|
2391
|
+
const db = await getDb();
|
|
2392
|
+
const row = await db.select().from(conversations).where(and2(eq4(conversations.id, conversationId), eq4(conversations.user_id, userId))).get();
|
|
2077
2393
|
return row != null;
|
|
2078
2394
|
}
|
|
2079
2395
|
async function deleteConversationForUser(id, userId) {
|
|
@@ -2082,15 +2398,15 @@ async function deleteConversationForUser(id, userId) {
|
|
|
2082
2398
|
return true;
|
|
2083
2399
|
}
|
|
2084
2400
|
async function addMessage(conversationId, role, senderName, content) {
|
|
2085
|
-
const
|
|
2401
|
+
const db = await getDb();
|
|
2086
2402
|
const serialized = JSON.stringify(content);
|
|
2087
|
-
const [result] = await
|
|
2088
|
-
await
|
|
2403
|
+
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 });
|
|
2404
|
+
await db.update(conversations).set({ updated_at: sql2`datetime('now')` }).where(eq4(conversations.id, conversationId));
|
|
2089
2405
|
if (role === "user") {
|
|
2090
2406
|
const firstText = content.find((b) => b.type === "text");
|
|
2091
2407
|
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
2092
2408
|
if (title) {
|
|
2093
|
-
await
|
|
2409
|
+
await db.update(conversations).set({ title }).where(and2(eq4(conversations.id, conversationId), isNull(conversations.title)));
|
|
2094
2410
|
}
|
|
2095
2411
|
}
|
|
2096
2412
|
const msg = {
|
|
@@ -2112,8 +2428,8 @@ async function addMessage(conversationId, role, senderName, content) {
|
|
|
2112
2428
|
return msg;
|
|
2113
2429
|
}
|
|
2114
2430
|
async function getMessages(conversationId) {
|
|
2115
|
-
const
|
|
2116
|
-
const rows = await
|
|
2431
|
+
const db = await getDb();
|
|
2432
|
+
const rows = await db.select().from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
2117
2433
|
return rows.map((row) => {
|
|
2118
2434
|
let content;
|
|
2119
2435
|
try {
|
|
@@ -2128,15 +2444,15 @@ async function getMessages(conversationId) {
|
|
|
2128
2444
|
async function listConversationsWithParticipants(userId) {
|
|
2129
2445
|
const convs = await listConversationsForUser(userId);
|
|
2130
2446
|
if (convs.length === 0) return [];
|
|
2131
|
-
const
|
|
2447
|
+
const db = await getDb();
|
|
2132
2448
|
const convIds = convs.map((c) => c.id);
|
|
2133
|
-
const rows = await
|
|
2449
|
+
const rows = await db.select({
|
|
2134
2450
|
conversationId: conversationParticipants.conversation_id,
|
|
2135
2451
|
userId: users.id,
|
|
2136
2452
|
username: users.username,
|
|
2137
2453
|
userType: users.user_type,
|
|
2138
2454
|
role: conversationParticipants.role
|
|
2139
|
-
}).from(conversationParticipants).innerJoin(users,
|
|
2455
|
+
}).from(conversationParticipants).innerJoin(users, eq4(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIds));
|
|
2140
2456
|
const byConv = /* @__PURE__ */ new Map();
|
|
2141
2457
|
for (const r of rows) {
|
|
2142
2458
|
let arr = byConv.get(r.conversationId);
|
|
@@ -2151,32 +2467,32 @@ async function listConversationsWithParticipants(userId) {
|
|
|
2151
2467
|
role: r.role
|
|
2152
2468
|
});
|
|
2153
2469
|
}
|
|
2154
|
-
const lastMsgIds = await
|
|
2470
|
+
const lastMsgIds = await db.select({
|
|
2155
2471
|
conversationId: messages.conversation_id,
|
|
2156
2472
|
maxId: sql2`MAX(${messages.id})`
|
|
2157
2473
|
}).from(messages).where(inArray(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
|
|
2158
2474
|
const byLastMsg = /* @__PURE__ */ new Map();
|
|
2159
2475
|
if (lastMsgIds.length > 0) {
|
|
2160
|
-
const msgRows = await
|
|
2476
|
+
const msgRows = await db.select().from(messages).where(
|
|
2161
2477
|
inArray(
|
|
2162
2478
|
messages.id,
|
|
2163
2479
|
lastMsgIds.map((r) => r.maxId)
|
|
2164
2480
|
)
|
|
2165
2481
|
);
|
|
2166
2482
|
for (const m of msgRows) {
|
|
2167
|
-
let
|
|
2483
|
+
let text = "";
|
|
2168
2484
|
try {
|
|
2169
2485
|
const parsed = JSON.parse(m.content);
|
|
2170
2486
|
const blocks = Array.isArray(parsed) ? parsed : [];
|
|
2171
2487
|
const textBlock = blocks.find((b) => b.type === "text");
|
|
2172
|
-
if (textBlock && "text" in textBlock)
|
|
2488
|
+
if (textBlock && "text" in textBlock) text = textBlock.text;
|
|
2173
2489
|
} catch {
|
|
2174
|
-
|
|
2490
|
+
text = m.content;
|
|
2175
2491
|
}
|
|
2176
2492
|
byLastMsg.set(m.conversation_id, {
|
|
2177
2493
|
role: m.role,
|
|
2178
2494
|
senderName: m.sender_name,
|
|
2179
|
-
text
|
|
2495
|
+
text,
|
|
2180
2496
|
createdAt: m.created_at
|
|
2181
2497
|
});
|
|
2182
2498
|
}
|
|
@@ -2188,10 +2504,10 @@ async function listConversationsWithParticipants(userId) {
|
|
|
2188
2504
|
}));
|
|
2189
2505
|
}
|
|
2190
2506
|
async function findDMConversation(mindName, participantIds) {
|
|
2191
|
-
const
|
|
2192
|
-
const mindConvs = await
|
|
2507
|
+
const db = await getDb();
|
|
2508
|
+
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq4(conversations.mind_name, mindName), eq4(conversations.type, "dm"))).all();
|
|
2193
2509
|
for (const conv of mindConvs) {
|
|
2194
|
-
const rows = await
|
|
2510
|
+
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq4(conversationParticipants.conversation_id, conv.id)).all();
|
|
2195
2511
|
if (rows.length !== 2) continue;
|
|
2196
2512
|
const ids = new Set(rows.map((r) => r.user_id));
|
|
2197
2513
|
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
@@ -2201,17 +2517,42 @@ async function findDMConversation(mindName, participantIds) {
|
|
|
2201
2517
|
return null;
|
|
2202
2518
|
}
|
|
2203
2519
|
async function deleteConversation(id) {
|
|
2204
|
-
const
|
|
2205
|
-
await
|
|
2520
|
+
const db = await getDb();
|
|
2521
|
+
await db.delete(conversations).where(eq4(conversations.id, id));
|
|
2522
|
+
}
|
|
2523
|
+
async function createChannel(name, creatorId) {
|
|
2524
|
+
const participantIds = creatorId ? [creatorId] : [];
|
|
2525
|
+
return createConversation(null, "volute", {
|
|
2526
|
+
type: "channel",
|
|
2527
|
+
name,
|
|
2528
|
+
title: name,
|
|
2529
|
+
participantIds
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
async function getChannelByName(name) {
|
|
2533
|
+
const db = await getDb();
|
|
2534
|
+
const row = await db.select().from(conversations).where(and2(eq4(conversations.name, name), eq4(conversations.type, "channel"))).get();
|
|
2535
|
+
return row ?? null;
|
|
2536
|
+
}
|
|
2537
|
+
async function listChannels() {
|
|
2538
|
+
const db = await getDb();
|
|
2539
|
+
return await db.select().from(conversations).where(eq4(conversations.type, "channel")).orderBy(conversations.name).all();
|
|
2540
|
+
}
|
|
2541
|
+
async function joinChannel(conversationId, userId) {
|
|
2542
|
+
if (await isParticipant(conversationId, userId)) return;
|
|
2543
|
+
await addParticipant(conversationId, userId);
|
|
2544
|
+
}
|
|
2545
|
+
async function leaveChannel(conversationId, userId) {
|
|
2546
|
+
await removeParticipant(conversationId, userId);
|
|
2206
2547
|
}
|
|
2207
2548
|
|
|
2208
2549
|
// src/lib/convert-session.ts
|
|
2209
2550
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2210
|
-
import { mkdirSync as
|
|
2551
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2211
2552
|
import { homedir } from "os";
|
|
2212
2553
|
import { resolve as resolve10 } from "path";
|
|
2213
2554
|
function convertSession(opts) {
|
|
2214
|
-
const lines =
|
|
2555
|
+
const lines = readFileSync6(opts.sessionPath, "utf-8").trim().split("\n");
|
|
2215
2556
|
const sessionId = randomUUID2();
|
|
2216
2557
|
const idMap = /* @__PURE__ */ new Map();
|
|
2217
2558
|
const messages2 = [];
|
|
@@ -2326,9 +2667,9 @@ function convertSession(opts) {
|
|
|
2326
2667
|
}
|
|
2327
2668
|
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
2328
2669
|
const sdkDir = resolve10(homedir(), ".claude", "projects", projectId);
|
|
2329
|
-
|
|
2670
|
+
mkdirSync4(sdkDir, { recursive: true });
|
|
2330
2671
|
const sdkPath = resolve10(sdkDir, `${sessionId}.jsonl`);
|
|
2331
|
-
|
|
2672
|
+
writeFileSync5(sdkPath, `${sdkEvents.join("\n")}
|
|
2332
2673
|
`);
|
|
2333
2674
|
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
2334
2675
|
return sessionId;
|
|
@@ -2513,7 +2854,7 @@ function extractTextContent(content) {
|
|
|
2513
2854
|
}
|
|
2514
2855
|
function getDaemonPort() {
|
|
2515
2856
|
try {
|
|
2516
|
-
const data = JSON.parse(
|
|
2857
|
+
const data = JSON.parse(readFileSync7(resolve11(voluteHome(), "daemon.json"), "utf-8"));
|
|
2517
2858
|
return data.port;
|
|
2518
2859
|
} catch (err) {
|
|
2519
2860
|
if (err?.code !== "ENOENT") {
|
|
@@ -2576,7 +2917,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2576
2917
|
} catch {
|
|
2577
2918
|
}
|
|
2578
2919
|
if (existsSync8(tempWorktree)) {
|
|
2579
|
-
|
|
2920
|
+
rmSync2(tempWorktree, { recursive: true, force: true });
|
|
2580
2921
|
}
|
|
2581
2922
|
const templatesRoot = findTemplatesRoot();
|
|
2582
2923
|
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
@@ -2598,7 +2939,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2598
2939
|
copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
|
|
2599
2940
|
const initDir = resolve11(tempWorktree, ".init");
|
|
2600
2941
|
if (existsSync8(initDir)) {
|
|
2601
|
-
|
|
2942
|
+
rmSync2(initDir, { recursive: true, force: true });
|
|
2602
2943
|
}
|
|
2603
2944
|
await gitExec(["add", "-A"], { cwd: tempWorktree });
|
|
2604
2945
|
try {
|
|
@@ -2612,9 +2953,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
|
|
|
2612
2953
|
} catch {
|
|
2613
2954
|
}
|
|
2614
2955
|
if (existsSync8(tempWorktree)) {
|
|
2615
|
-
|
|
2956
|
+
rmSync2(tempWorktree, { recursive: true, force: true });
|
|
2616
2957
|
}
|
|
2617
|
-
|
|
2958
|
+
rmSync2(composedDir, { recursive: true, force: true });
|
|
2618
2959
|
}
|
|
2619
2960
|
}
|
|
2620
2961
|
async function mergeTemplateBranch(worktreeDir) {
|
|
@@ -2642,14 +2983,15 @@ async function npmInstallAsMind(cwd, mindName) {
|
|
|
2642
2983
|
await exec("npm", ["install"], { cwd });
|
|
2643
2984
|
}
|
|
2644
2985
|
}
|
|
2645
|
-
var createMindSchema =
|
|
2646
|
-
name:
|
|
2647
|
-
template:
|
|
2648
|
-
stage:
|
|
2649
|
-
description:
|
|
2650
|
-
model:
|
|
2986
|
+
var createMindSchema = z3.object({
|
|
2987
|
+
name: z3.string(),
|
|
2988
|
+
template: z3.string().optional(),
|
|
2989
|
+
stage: z3.enum(["seed", "sprouted"]).optional(),
|
|
2990
|
+
description: z3.string().optional(),
|
|
2991
|
+
model: z3.string().optional(),
|
|
2992
|
+
seedSoul: z3.string().optional()
|
|
2651
2993
|
});
|
|
2652
|
-
var
|
|
2994
|
+
var app8 = new Hono8().post("/", requireAdmin, zValidator3("json", createMindSchema), async (c) => {
|
|
2653
2995
|
const body = c.req.valid("json");
|
|
2654
2996
|
const { name, template = "claude" } = body;
|
|
2655
2997
|
const nameErr = validateMindName(name);
|
|
@@ -2665,11 +3007,17 @@ var app7 = new Hono7().post("/", requireAdmin, zValidator2("json", createMindSch
|
|
|
2665
3007
|
applyInitFiles(dest);
|
|
2666
3008
|
if (body.model) {
|
|
2667
3009
|
const configPath = resolve11(dest, "home/.config/config.json");
|
|
2668
|
-
const existing = existsSync8(configPath) ? JSON.parse(
|
|
3010
|
+
const existing = existsSync8(configPath) ? JSON.parse(readFileSync7(configPath, "utf-8")) : {};
|
|
2669
3011
|
existing.model = body.model;
|
|
2670
|
-
|
|
3012
|
+
writeFileSync6(configPath, `${JSON.stringify(existing, null, 2)}
|
|
2671
3013
|
`);
|
|
2672
3014
|
}
|
|
3015
|
+
const mindPrompts = await getMindPromptDefaults();
|
|
3016
|
+
writeFileSync6(
|
|
3017
|
+
resolve11(dest, "home/.config/prompts.json"),
|
|
3018
|
+
`${JSON.stringify(mindPrompts, null, 2)}
|
|
3019
|
+
`
|
|
3020
|
+
);
|
|
2673
3021
|
const port = nextPort();
|
|
2674
3022
|
addMind(name, port, body.stage);
|
|
2675
3023
|
const homeDir = resolve11(dest, "home");
|
|
@@ -2684,7 +3032,7 @@ var app7 = new Hono7().post("/", requireAdmin, zValidator2("json", createMindSch
|
|
|
2684
3032
|
await initTemplateBranch(dest, composedDir, manifest, name, env);
|
|
2685
3033
|
} catch (err) {
|
|
2686
3034
|
console.error(`[daemon] git setup failed for ${name}:`, err);
|
|
2687
|
-
|
|
3035
|
+
rmSync2(resolve11(dest, ".git"), { recursive: true, force: true });
|
|
2688
3036
|
gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
|
|
2689
3037
|
}
|
|
2690
3038
|
chownMindDir(dest, name);
|
|
@@ -2692,19 +3040,23 @@ var app7 = new Hono7().post("/", requireAdmin, zValidator2("json", createMindSch
|
|
|
2692
3040
|
const descLine = body.description ? `
|
|
2693
3041
|
The human who planted you described you as: "${body.description}"
|
|
2694
3042
|
` : "";
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
You don't have a soul yet. You're a seed \u2014 a new mind in the process of discovering who you are.
|
|
2700
|
-
${descLine}
|
|
2701
|
-
Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute sprout\` to complete the transformation.
|
|
2702
|
-
`
|
|
2703
|
-
);
|
|
3043
|
+
const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
|
|
3044
|
+
const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
|
|
3045
|
+
writeFileSync6(resolve11(dest, "home/SOUL.md"), seedSoul);
|
|
2704
3046
|
const skillsDir = resolve11(dest, manifest.skillsDir);
|
|
2705
3047
|
for (const skill of ["volute-mind", "memory", "sessions"]) {
|
|
2706
3048
|
const skillPath = resolve11(skillsDir, skill);
|
|
2707
|
-
if (existsSync8(skillPath))
|
|
3049
|
+
if (existsSync8(skillPath)) rmSync2(skillPath, { recursive: true, force: true });
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
if (body.stage !== "seed") {
|
|
3053
|
+
const customSoul = await getPromptIfCustom("default_soul");
|
|
3054
|
+
if (customSoul) {
|
|
3055
|
+
writeFileSync6(resolve11(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
|
|
3056
|
+
}
|
|
3057
|
+
const customMemory = await getPromptIfCustom("default_memory");
|
|
3058
|
+
if (customMemory) {
|
|
3059
|
+
writeFileSync6(resolve11(dest, "home/MEMORY.md"), customMemory);
|
|
2708
3060
|
}
|
|
2709
3061
|
}
|
|
2710
3062
|
return c.json({
|
|
@@ -2716,14 +3068,14 @@ Have a conversation with the human. Explore what kind of mind you want to be. Wh
|
|
|
2716
3068
|
...gitWarning && { warning: gitWarning }
|
|
2717
3069
|
});
|
|
2718
3070
|
} catch (err) {
|
|
2719
|
-
if (existsSync8(dest))
|
|
3071
|
+
if (existsSync8(dest)) rmSync2(dest, { recursive: true, force: true });
|
|
2720
3072
|
try {
|
|
2721
3073
|
removeMind(name);
|
|
2722
3074
|
} catch {
|
|
2723
3075
|
}
|
|
2724
3076
|
return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
|
|
2725
3077
|
} finally {
|
|
2726
|
-
|
|
3078
|
+
rmSync2(composedDir, { recursive: true, force: true });
|
|
2727
3079
|
}
|
|
2728
3080
|
}).post("/import", requireAdmin, async (c) => {
|
|
2729
3081
|
let body;
|
|
@@ -2736,10 +3088,10 @@ Have a conversation with the human. Explore what kind of mind you want to be. Wh
|
|
|
2736
3088
|
if (!wsDir || !existsSync8(resolve11(wsDir, "SOUL.md")) || !existsSync8(resolve11(wsDir, "IDENTITY.md"))) {
|
|
2737
3089
|
return c.json({ error: "Invalid workspace: missing SOUL.md or IDENTITY.md" }, 400);
|
|
2738
3090
|
}
|
|
2739
|
-
const soul =
|
|
2740
|
-
const identity =
|
|
3091
|
+
const soul = readFileSync7(resolve11(wsDir, "SOUL.md"), "utf-8");
|
|
3092
|
+
const identity = readFileSync7(resolve11(wsDir, "IDENTITY.md"), "utf-8");
|
|
2741
3093
|
const userPath = resolve11(wsDir, "USER.md");
|
|
2742
|
-
const user = existsSync8(userPath) ?
|
|
3094
|
+
const user = existsSync8(userPath) ? readFileSync7(userPath, "utf-8") : "";
|
|
2743
3095
|
const name = body.name ?? parseNameFromIdentity(identity) ?? "imported-mind";
|
|
2744
3096
|
const template = body.template ?? "claude";
|
|
2745
3097
|
const nameErr = validateMindName(name);
|
|
@@ -2765,26 +3117,26 @@ ${user.trimEnd()}
|
|
|
2765
3117
|
try {
|
|
2766
3118
|
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
2767
3119
|
applyInitFiles(dest);
|
|
2768
|
-
|
|
3120
|
+
writeFileSync6(resolve11(dest, "home/SOUL.md"), mergedSoul);
|
|
2769
3121
|
const wsMemoryPath = resolve11(wsDir, "MEMORY.md");
|
|
2770
3122
|
const hasMemory = existsSync8(wsMemoryPath);
|
|
2771
3123
|
if (hasMemory) {
|
|
2772
|
-
const existingMemory =
|
|
2773
|
-
|
|
3124
|
+
const existingMemory = readFileSync7(wsMemoryPath, "utf-8");
|
|
3125
|
+
writeFileSync6(
|
|
2774
3126
|
resolve11(dest, "home/MEMORY.md"),
|
|
2775
3127
|
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
2776
3128
|
);
|
|
2777
3129
|
} else if (user) {
|
|
2778
|
-
|
|
3130
|
+
writeFileSync6(resolve11(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
2779
3131
|
`);
|
|
2780
3132
|
}
|
|
2781
3133
|
const wsMemoryDir = resolve11(wsDir, "memory");
|
|
2782
3134
|
let dailyLogCount = 0;
|
|
2783
3135
|
if (existsSync8(wsMemoryDir)) {
|
|
2784
3136
|
const destMemoryDir = resolve11(dest, "home/memory");
|
|
2785
|
-
const files =
|
|
3137
|
+
const files = readdirSync4(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
2786
3138
|
for (const file of files) {
|
|
2787
|
-
|
|
3139
|
+
cpSync2(resolve11(wsMemoryDir, file), resolve11(destMemoryDir, file));
|
|
2788
3140
|
}
|
|
2789
3141
|
dailyLogCount = files.length;
|
|
2790
3142
|
}
|
|
@@ -2809,29 +3161,29 @@ ${user.trimEnd()}
|
|
|
2809
3161
|
} else if (template === "claude") {
|
|
2810
3162
|
const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
|
|
2811
3163
|
const voluteDir = resolve11(dest, ".volute");
|
|
2812
|
-
|
|
2813
|
-
|
|
3164
|
+
mkdirSync5(voluteDir, { recursive: true });
|
|
3165
|
+
writeFileSync6(resolve11(voluteDir, "session.json"), JSON.stringify({ sessionId }));
|
|
2814
3166
|
}
|
|
2815
3167
|
}
|
|
2816
3168
|
importOpenClawConnectors(name, dest);
|
|
2817
3169
|
chownMindDir(dest, name);
|
|
2818
3170
|
return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
|
|
2819
3171
|
} catch (err) {
|
|
2820
|
-
if (existsSync8(dest))
|
|
3172
|
+
if (existsSync8(dest)) rmSync2(dest, { recursive: true, force: true });
|
|
2821
3173
|
try {
|
|
2822
3174
|
removeMind(name);
|
|
2823
3175
|
} catch {
|
|
2824
3176
|
}
|
|
2825
3177
|
return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
|
|
2826
3178
|
} finally {
|
|
2827
|
-
|
|
3179
|
+
rmSync2(composedDir, { recursive: true, force: true });
|
|
2828
3180
|
}
|
|
2829
3181
|
}).get("/", async (c) => {
|
|
2830
3182
|
const entries = readRegistry();
|
|
2831
3183
|
let lastActiveMap = /* @__PURE__ */ new Map();
|
|
2832
3184
|
try {
|
|
2833
|
-
const
|
|
2834
|
-
const lastActiveRows = await
|
|
3185
|
+
const db = await getDb();
|
|
3186
|
+
const lastActiveRows = await db.select({
|
|
2835
3187
|
mind: mindHistory.mind,
|
|
2836
3188
|
lastActiveAt: sql3`MAX(${mindHistory.created_at})`
|
|
2837
3189
|
}).from(mindHistory).groupBy(mindHistory.mind);
|
|
@@ -2860,7 +3212,7 @@ ${user.trimEnd()}
|
|
|
2860
3212
|
if (!existsSync8(pagesDir)) continue;
|
|
2861
3213
|
let items;
|
|
2862
3214
|
try {
|
|
2863
|
-
items =
|
|
3215
|
+
items = readdirSync4(pagesDir);
|
|
2864
3216
|
} catch (err) {
|
|
2865
3217
|
logger_default.warn("Failed to read pages dir", { mind: entry.name, error: err.message });
|
|
2866
3218
|
continue;
|
|
@@ -2882,7 +3234,7 @@ ${user.trimEnd()}
|
|
|
2882
3234
|
const indexStat = statSync(indexPath);
|
|
2883
3235
|
pages.push({
|
|
2884
3236
|
mind: entry.name,
|
|
2885
|
-
file:
|
|
3237
|
+
file: join2(item, "index.html"),
|
|
2886
3238
|
modified: indexStat.mtime.toISOString(),
|
|
2887
3239
|
url: `/pages/${entry.name}/${item}/`
|
|
2888
3240
|
});
|
|
@@ -3036,8 +3388,8 @@ ${user.trimEnd()}
|
|
|
3036
3388
|
}
|
|
3037
3389
|
if (context?.type === "sprouted" && !variantName) {
|
|
3038
3390
|
try {
|
|
3039
|
-
const
|
|
3040
|
-
const activeConvs = await
|
|
3391
|
+
const db = await getDb();
|
|
3392
|
+
const activeConvs = await db.select({ id: conversations.id }).from(conversations).where(eq5(conversations.mind_name, baseName)).all();
|
|
3041
3393
|
for (const conv of activeConvs) {
|
|
3042
3394
|
await addMessage(conv.id, "assistant", "system", [
|
|
3043
3395
|
{ type: "text", text: "[seed has sprouted]" }
|
|
@@ -3102,10 +3454,10 @@ ${user.trimEnd()}
|
|
|
3102
3454
|
await deleteMindUser2(name);
|
|
3103
3455
|
const state = stateDir(name);
|
|
3104
3456
|
if (existsSync8(state)) {
|
|
3105
|
-
|
|
3457
|
+
rmSync2(state, { recursive: true, force: true });
|
|
3106
3458
|
}
|
|
3107
3459
|
if (force && existsSync8(dir)) {
|
|
3108
|
-
|
|
3460
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
3109
3461
|
deleteMindUser(name);
|
|
3110
3462
|
}
|
|
3111
3463
|
return c.json({ ok: true });
|
|
@@ -3199,7 +3551,7 @@ ${user.trimEnd()}
|
|
|
3199
3551
|
await updateTemplateBranch(dir, template, mindName);
|
|
3200
3552
|
const parentDir = resolve11(dir, ".variants");
|
|
3201
3553
|
if (!existsSync8(parentDir)) {
|
|
3202
|
-
|
|
3554
|
+
mkdirSync5(parentDir, { recursive: true });
|
|
3203
3555
|
}
|
|
3204
3556
|
await gitExec(["worktree", "add", "-b", UPGRADE_VARIANT, worktreeDir], { cwd: dir });
|
|
3205
3557
|
const hasConflicts = await mergeTemplateBranch(worktreeDir);
|
|
@@ -3277,12 +3629,12 @@ ${user.trimEnd()}
|
|
|
3277
3629
|
console.error(`[daemon] failed to parse message body for ${baseName}:`, err);
|
|
3278
3630
|
}
|
|
3279
3631
|
const channel = parsed?.channel ?? "unknown";
|
|
3280
|
-
const
|
|
3632
|
+
const db = await getDb();
|
|
3281
3633
|
if (parsed) {
|
|
3282
3634
|
try {
|
|
3283
3635
|
const sender2 = parsed.sender ?? null;
|
|
3284
3636
|
const content = extractTextContent(parsed.content);
|
|
3285
|
-
await
|
|
3637
|
+
await db.insert(mindHistory).values({
|
|
3286
3638
|
mind: baseName,
|
|
3287
3639
|
type: "inbound",
|
|
3288
3640
|
channel,
|
|
@@ -3329,7 +3681,7 @@ ${user.trimEnd()}
|
|
|
3329
3681
|
const seedEntry = findMind(baseName);
|
|
3330
3682
|
if (seedEntry?.stage === "seed" && parsed) {
|
|
3331
3683
|
try {
|
|
3332
|
-
const countResult = await
|
|
3684
|
+
const countResult = await db.select({ count: sql3`count(*)` }).from(mindHistory).where(eq5(mindHistory.mind, baseName));
|
|
3333
3685
|
const msgCount = countResult[0]?.count ?? 0;
|
|
3334
3686
|
if (msgCount >= 10 && msgCount % 10 === 0) {
|
|
3335
3687
|
const nudge = "\n[You've been exploring for a while. Whenever you feel ready, write your SOUL.md and MEMORY.md, then run volute sprout.]";
|
|
@@ -3353,8 +3705,8 @@ ${user.trimEnd()}
|
|
|
3353
3705
|
body: forwardBody
|
|
3354
3706
|
}).then(async (res) => {
|
|
3355
3707
|
if (!res.ok) {
|
|
3356
|
-
const
|
|
3357
|
-
console.error(`[daemon] mind ${name} responded with ${res.status}: ${
|
|
3708
|
+
const text = await res.text().catch(() => "");
|
|
3709
|
+
console.error(`[daemon] mind ${name} responded with ${res.status}: ${text}`);
|
|
3358
3710
|
}
|
|
3359
3711
|
}).catch((err) => {
|
|
3360
3712
|
console.error(`[daemon] mind ${name} unreachable on port ${port}:`, err);
|
|
@@ -3379,9 +3731,9 @@ ${user.trimEnd()}
|
|
|
3379
3731
|
if (!body.type) {
|
|
3380
3732
|
return c.json({ error: "type required" }, 400);
|
|
3381
3733
|
}
|
|
3382
|
-
const
|
|
3734
|
+
const db = await getDb();
|
|
3383
3735
|
try {
|
|
3384
|
-
await
|
|
3736
|
+
await db.insert(mindHistory).values({
|
|
3385
3737
|
mind: baseName,
|
|
3386
3738
|
type: body.type,
|
|
3387
3739
|
session: body.session ?? null,
|
|
@@ -3465,9 +3817,9 @@ ${user.trimEnd()}
|
|
|
3465
3817
|
if (!body.channel || !body.content) {
|
|
3466
3818
|
return c.json({ error: "channel and content required" }, 400);
|
|
3467
3819
|
}
|
|
3468
|
-
const
|
|
3820
|
+
const db = await getDb();
|
|
3469
3821
|
try {
|
|
3470
|
-
await
|
|
3822
|
+
await db.insert(mindHistory).values({
|
|
3471
3823
|
mind: baseName,
|
|
3472
3824
|
type: "outbound",
|
|
3473
3825
|
channel: body.channel,
|
|
@@ -3481,19 +3833,19 @@ ${user.trimEnd()}
|
|
|
3481
3833
|
return c.json({ ok: true });
|
|
3482
3834
|
}).get("/:name/history/sessions", async (c) => {
|
|
3483
3835
|
const name = c.req.param("name");
|
|
3484
|
-
const
|
|
3485
|
-
const rows = await
|
|
3836
|
+
const db = await getDb();
|
|
3837
|
+
const rows = await db.select({
|
|
3486
3838
|
session: mindHistory.session,
|
|
3487
3839
|
started_at: sql3`MIN(${mindHistory.created_at})`,
|
|
3488
3840
|
event_count: sql3`COUNT(*)`,
|
|
3489
3841
|
message_count: sql3`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
|
|
3490
3842
|
tool_count: sql3`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
|
|
3491
|
-
}).from(mindHistory).where(and3(
|
|
3843
|
+
}).from(mindHistory).where(and3(eq5(mindHistory.mind, name), sql3`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql3`MIN(${mindHistory.created_at}) DESC`);
|
|
3492
3844
|
return c.json(rows);
|
|
3493
3845
|
}).get("/:name/history/channels", async (c) => {
|
|
3494
3846
|
const name = c.req.param("name");
|
|
3495
|
-
const
|
|
3496
|
-
const rows = await
|
|
3847
|
+
const db = await getDb();
|
|
3848
|
+
const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq5(mindHistory.mind, name));
|
|
3497
3849
|
return c.json(rows.map((r) => r.channel));
|
|
3498
3850
|
}).get("/:name/history", async (c) => {
|
|
3499
3851
|
const name = c.req.param("name");
|
|
@@ -3502,26 +3854,26 @@ ${user.trimEnd()}
|
|
|
3502
3854
|
const full = c.req.query("full") === "true";
|
|
3503
3855
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
3504
3856
|
const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
|
|
3505
|
-
const
|
|
3506
|
-
const conditions = [
|
|
3857
|
+
const db = await getDb();
|
|
3858
|
+
const conditions = [eq5(mindHistory.mind, name)];
|
|
3507
3859
|
if (channel) {
|
|
3508
|
-
conditions.push(
|
|
3860
|
+
conditions.push(eq5(mindHistory.channel, channel));
|
|
3509
3861
|
}
|
|
3510
3862
|
if (session) {
|
|
3511
|
-
conditions.push(
|
|
3863
|
+
conditions.push(eq5(mindHistory.session, session));
|
|
3512
3864
|
}
|
|
3513
3865
|
if (!full) {
|
|
3514
3866
|
conditions.push(sql3`${mindHistory.type} IN ('inbound', 'outbound')`);
|
|
3515
3867
|
}
|
|
3516
|
-
const rows = await
|
|
3868
|
+
const rows = await db.select().from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.created_at)).limit(limit).offset(offset);
|
|
3517
3869
|
return c.json(rows);
|
|
3518
3870
|
});
|
|
3519
|
-
var minds_default =
|
|
3871
|
+
var minds_default = app8;
|
|
3520
3872
|
|
|
3521
3873
|
// src/web/api/pages.ts
|
|
3522
3874
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
3523
3875
|
import { extname, resolve as resolve12 } from "path";
|
|
3524
|
-
import { Hono as
|
|
3876
|
+
import { Hono as Hono9 } from "hono";
|
|
3525
3877
|
var MIME_TYPES = {
|
|
3526
3878
|
".html": "text/html",
|
|
3527
3879
|
".js": "application/javascript",
|
|
@@ -3538,7 +3890,7 @@ var MIME_TYPES = {
|
|
|
3538
3890
|
".txt": "text/plain",
|
|
3539
3891
|
".xml": "application/xml"
|
|
3540
3892
|
};
|
|
3541
|
-
var
|
|
3893
|
+
var app9 = new Hono9().get("/:name/*", async (c) => {
|
|
3542
3894
|
const name = c.req.param("name");
|
|
3543
3895
|
if (!findMind(name)) return c.text("Not found", 404);
|
|
3544
3896
|
const pagesRoot = resolve12(mindDir(name), "home", "pages");
|
|
@@ -3563,10 +3915,61 @@ var app8 = new Hono8().get("/:name/*", async (c) => {
|
|
|
3563
3915
|
}
|
|
3564
3916
|
return c.text("Not found", 404);
|
|
3565
3917
|
});
|
|
3566
|
-
var pages_default =
|
|
3918
|
+
var pages_default = app9;
|
|
3919
|
+
|
|
3920
|
+
// src/web/api/prompts.ts
|
|
3921
|
+
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
3922
|
+
import { eq as eq6, sql as sql4 } from "drizzle-orm";
|
|
3923
|
+
import { Hono as Hono10 } from "hono";
|
|
3924
|
+
import { z as z4 } from "zod";
|
|
3925
|
+
var app10 = new Hono10().get("/", async (c) => {
|
|
3926
|
+
let rows;
|
|
3927
|
+
try {
|
|
3928
|
+
const db = await getDb();
|
|
3929
|
+
rows = await db.select().from(systemPrompts).all();
|
|
3930
|
+
} catch (err) {
|
|
3931
|
+
console.error("[prompts] failed to query system_prompts:", err);
|
|
3932
|
+
return c.json({ error: "Failed to load prompts from database" }, 500);
|
|
3933
|
+
}
|
|
3934
|
+
const customMap = new Map(rows.map((r) => [r.key, r.content]));
|
|
3935
|
+
const prompts = PROMPT_KEYS.map((key) => {
|
|
3936
|
+
const meta = PROMPT_DEFAULTS[key];
|
|
3937
|
+
const custom = customMap.get(key);
|
|
3938
|
+
return {
|
|
3939
|
+
key,
|
|
3940
|
+
content: custom ?? meta.content,
|
|
3941
|
+
description: meta.description,
|
|
3942
|
+
variables: meta.variables,
|
|
3943
|
+
isCustom: custom !== void 0,
|
|
3944
|
+
category: meta.category
|
|
3945
|
+
};
|
|
3946
|
+
});
|
|
3947
|
+
return c.json(prompts);
|
|
3948
|
+
}).put("/:key", requireAdmin, zValidator4("json", z4.object({ content: z4.string() })), async (c) => {
|
|
3949
|
+
const key = c.req.param("key");
|
|
3950
|
+
if (!PROMPT_KEYS.includes(key)) {
|
|
3951
|
+
return c.json({ error: "Unknown prompt key" }, 404);
|
|
3952
|
+
}
|
|
3953
|
+
const { content } = c.req.valid("json");
|
|
3954
|
+
const db = await getDb();
|
|
3955
|
+
await db.insert(systemPrompts).values({ key, content, updated_at: sql4`(datetime('now'))` }).onConflictDoUpdate({
|
|
3956
|
+
target: systemPrompts.key,
|
|
3957
|
+
set: { content, updated_at: sql4`(datetime('now'))` }
|
|
3958
|
+
});
|
|
3959
|
+
return c.json({ ok: true });
|
|
3960
|
+
}).delete("/:key", requireAdmin, async (c) => {
|
|
3961
|
+
const key = c.req.param("key");
|
|
3962
|
+
if (!PROMPT_KEYS.includes(key)) {
|
|
3963
|
+
return c.json({ error: "Unknown prompt key" }, 404);
|
|
3964
|
+
}
|
|
3965
|
+
const db = await getDb();
|
|
3966
|
+
await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
|
|
3967
|
+
return c.json({ ok: true });
|
|
3968
|
+
});
|
|
3969
|
+
var prompts_default = app10;
|
|
3567
3970
|
|
|
3568
3971
|
// src/web/api/schedules.ts
|
|
3569
|
-
import { Hono as
|
|
3972
|
+
import { Hono as Hono11 } from "hono";
|
|
3570
3973
|
function readSchedules(name) {
|
|
3571
3974
|
return readVoluteConfig(mindDir(name))?.schedules ?? [];
|
|
3572
3975
|
}
|
|
@@ -3577,7 +3980,7 @@ function writeSchedules(name, schedules) {
|
|
|
3577
3980
|
writeVoluteConfig(dir, config);
|
|
3578
3981
|
getScheduler().loadSchedules(name);
|
|
3579
3982
|
}
|
|
3580
|
-
var
|
|
3983
|
+
var app11 = new Hono11().get("/:name/schedules", (c) => {
|
|
3581
3984
|
const name = c.req.param("name");
|
|
3582
3985
|
if (!findMind(name)) return c.json({ error: "Mind not found" }, 404);
|
|
3583
3986
|
return c.json(readSchedules(name));
|
|
@@ -3648,12 +4051,85 @@ var app9 = new Hono9().get("/:name/schedules", (c) => {
|
|
|
3648
4051
|
return c.json({ error: "Failed to reach mind" }, 502);
|
|
3649
4052
|
}
|
|
3650
4053
|
});
|
|
3651
|
-
var schedules_default =
|
|
4054
|
+
var schedules_default = app11;
|
|
4055
|
+
|
|
4056
|
+
// src/web/api/skills.ts
|
|
4057
|
+
import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
|
|
4058
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4059
|
+
import { join as join3, resolve as resolve13 } from "path";
|
|
4060
|
+
import AdmZip from "adm-zip";
|
|
4061
|
+
import { Hono as Hono12 } from "hono";
|
|
4062
|
+
var app12 = new Hono12().get("/", async (c) => {
|
|
4063
|
+
const skills = await listSharedSkills();
|
|
4064
|
+
return c.json(skills);
|
|
4065
|
+
}).get("/:id", async (c) => {
|
|
4066
|
+
const id = c.req.param("id");
|
|
4067
|
+
const skill = await getSharedSkill(id);
|
|
4068
|
+
if (!skill) return c.json({ error: "Skill not found" }, 404);
|
|
4069
|
+
const dir = join3(sharedSkillsDir(), id);
|
|
4070
|
+
const files = listFilesRecursive(dir);
|
|
4071
|
+
return c.json({ ...skill, files });
|
|
4072
|
+
}).post("/upload", requireAdmin, async (c) => {
|
|
4073
|
+
const body = await c.req.parseBody();
|
|
4074
|
+
const file = body.file;
|
|
4075
|
+
if (!file || !(file instanceof File)) {
|
|
4076
|
+
return c.json({ error: "No file uploaded" }, 400);
|
|
4077
|
+
}
|
|
4078
|
+
if (!file.name.endsWith(".zip")) {
|
|
4079
|
+
return c.json({ error: "Only .zip files are accepted" }, 400);
|
|
4080
|
+
}
|
|
4081
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
4082
|
+
const tmpDir = mkdtempSync(join3(tmpdir2(), "volute-skill-upload-"));
|
|
4083
|
+
try {
|
|
4084
|
+
const zip = new AdmZip(buffer);
|
|
4085
|
+
for (const entry of zip.getEntries()) {
|
|
4086
|
+
const target = resolve13(tmpDir, entry.entryName);
|
|
4087
|
+
if (!target.startsWith(tmpDir)) {
|
|
4088
|
+
return c.json({ error: "Invalid zip: paths must not escape archive" }, 400);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
zip.extractAllTo(tmpDir, true);
|
|
4092
|
+
let skillDir = null;
|
|
4093
|
+
if (existsSync9(join3(tmpDir, "SKILL.md"))) {
|
|
4094
|
+
skillDir = tmpDir;
|
|
4095
|
+
} else {
|
|
4096
|
+
const entries = readdirSync5(tmpDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4097
|
+
for (const entry of entries) {
|
|
4098
|
+
if (existsSync9(join3(tmpDir, entry.name, "SKILL.md"))) {
|
|
4099
|
+
skillDir = join3(tmpDir, entry.name);
|
|
4100
|
+
break;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
if (!skillDir) {
|
|
4105
|
+
return c.json({ error: "No SKILL.md found in zip (checked root and one level deep)" }, 400);
|
|
4106
|
+
}
|
|
4107
|
+
const skill = await importSkillFromDir(skillDir, "upload");
|
|
4108
|
+
return c.json(skill);
|
|
4109
|
+
} catch (e) {
|
|
4110
|
+
if (e instanceof Error && e.message.includes("Invalid skill ID")) {
|
|
4111
|
+
return c.json({ error: e.message }, 400);
|
|
4112
|
+
}
|
|
4113
|
+
throw e;
|
|
4114
|
+
} finally {
|
|
4115
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
4116
|
+
}
|
|
4117
|
+
}).delete("/:id", requireAdmin, async (c) => {
|
|
4118
|
+
const id = c.req.param("id");
|
|
4119
|
+
try {
|
|
4120
|
+
await removeSharedSkill(id);
|
|
4121
|
+
} catch (e) {
|
|
4122
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4123
|
+
return c.json({ error: msg }, 404);
|
|
4124
|
+
}
|
|
4125
|
+
return c.json({ ok: true });
|
|
4126
|
+
});
|
|
4127
|
+
var skills_default = app12;
|
|
3652
4128
|
|
|
3653
4129
|
// src/web/api/system.ts
|
|
3654
|
-
import { Hono as
|
|
4130
|
+
import { Hono as Hono13 } from "hono";
|
|
3655
4131
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
3656
|
-
var
|
|
4132
|
+
var app13 = new Hono13().post("/restart", requireAdmin, (c) => {
|
|
3657
4133
|
setTimeout(() => process.exit(1), 200);
|
|
3658
4134
|
return c.json({ ok: true });
|
|
3659
4135
|
}).post("/stop", requireAdmin, (c) => {
|
|
@@ -3670,10 +4146,10 @@ var app10 = new Hono10().post("/restart", requireAdmin, (c) => {
|
|
|
3670
4146
|
stream.writeSSE({ data: JSON.stringify(entry) }).catch(() => {
|
|
3671
4147
|
});
|
|
3672
4148
|
});
|
|
3673
|
-
await new Promise((
|
|
4149
|
+
await new Promise((resolve19) => {
|
|
3674
4150
|
stream.onAbort(() => {
|
|
3675
4151
|
unsubscribe();
|
|
3676
|
-
|
|
4152
|
+
resolve19();
|
|
3677
4153
|
});
|
|
3678
4154
|
});
|
|
3679
4155
|
});
|
|
@@ -3681,18 +4157,18 @@ var app10 = new Hono10().post("/restart", requireAdmin, (c) => {
|
|
|
3681
4157
|
const config = readSystemsConfig();
|
|
3682
4158
|
return c.json({ system: config?.system ?? null });
|
|
3683
4159
|
});
|
|
3684
|
-
var system_default =
|
|
4160
|
+
var system_default = app13;
|
|
3685
4161
|
|
|
3686
4162
|
// src/web/api/typing.ts
|
|
3687
|
-
import { zValidator as
|
|
3688
|
-
import { Hono as
|
|
3689
|
-
import { z as
|
|
3690
|
-
var typingSchema =
|
|
3691
|
-
channel:
|
|
3692
|
-
sender:
|
|
3693
|
-
active:
|
|
4163
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
4164
|
+
import { Hono as Hono14 } from "hono";
|
|
4165
|
+
import { z as z5 } from "zod";
|
|
4166
|
+
var typingSchema = z5.object({
|
|
4167
|
+
channel: z5.string().min(1),
|
|
4168
|
+
sender: z5.string().min(1),
|
|
4169
|
+
active: z5.boolean()
|
|
3694
4170
|
});
|
|
3695
|
-
var
|
|
4171
|
+
var app14 = new Hono14().post("/:name/typing", zValidator5("json", typingSchema), (c) => {
|
|
3696
4172
|
const { channel, sender, active } = c.req.valid("json");
|
|
3697
4173
|
const map = getTypingMap();
|
|
3698
4174
|
if (active) {
|
|
@@ -3709,13 +4185,13 @@ var app11 = new Hono11().post("/:name/typing", zValidator3("json", typingSchema)
|
|
|
3709
4185
|
const map = getTypingMap();
|
|
3710
4186
|
return c.json({ typing: map.get(channel) });
|
|
3711
4187
|
});
|
|
3712
|
-
var typing_default =
|
|
4188
|
+
var typing_default = app14;
|
|
3713
4189
|
|
|
3714
4190
|
// src/web/api/update.ts
|
|
3715
4191
|
import { spawn as spawn3 } from "child_process";
|
|
3716
|
-
import { Hono as
|
|
4192
|
+
import { Hono as Hono15 } from "hono";
|
|
3717
4193
|
var bin;
|
|
3718
|
-
var
|
|
4194
|
+
var app15 = new Hono15().get("/update", async (c) => {
|
|
3719
4195
|
const result = await checkForUpdate();
|
|
3720
4196
|
return c.json(result);
|
|
3721
4197
|
}).post("/update", requireAdmin, async (c) => {
|
|
@@ -3730,19 +4206,19 @@ var app12 = new Hono12().get("/update", async (c) => {
|
|
|
3730
4206
|
child.unref();
|
|
3731
4207
|
return c.json({ ok: true, message: "Updating..." });
|
|
3732
4208
|
});
|
|
3733
|
-
var update_default =
|
|
4209
|
+
var update_default = app15;
|
|
3734
4210
|
|
|
3735
4211
|
// src/web/api/variants.ts
|
|
3736
|
-
import { existsSync as
|
|
3737
|
-
import { resolve as
|
|
3738
|
-
import { Hono as
|
|
4212
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
4213
|
+
import { resolve as resolve15 } from "path";
|
|
4214
|
+
import { Hono as Hono16 } from "hono";
|
|
3739
4215
|
|
|
3740
4216
|
// src/lib/spawn-server.ts
|
|
3741
4217
|
import { spawn as spawn4 } from "child_process";
|
|
3742
|
-
import { closeSync, mkdirSync as
|
|
3743
|
-
import { resolve as
|
|
4218
|
+
import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync8 } from "fs";
|
|
4219
|
+
import { resolve as resolve14 } from "path";
|
|
3744
4220
|
function tsxBin(cwd) {
|
|
3745
|
-
return
|
|
4221
|
+
return resolve14(cwd, "node_modules", ".bin", "tsx");
|
|
3746
4222
|
}
|
|
3747
4223
|
function spawnServer(cwd, port, options) {
|
|
3748
4224
|
if (options?.detached) {
|
|
@@ -3755,31 +4231,31 @@ function spawnAttached(cwd, port) {
|
|
|
3755
4231
|
cwd,
|
|
3756
4232
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3757
4233
|
});
|
|
3758
|
-
return new Promise((
|
|
3759
|
-
const timeout = setTimeout(() =>
|
|
4234
|
+
return new Promise((resolve19) => {
|
|
4235
|
+
const timeout = setTimeout(() => resolve19(null), 3e4);
|
|
3760
4236
|
function checkOutput(data) {
|
|
3761
4237
|
const match = data.toString().match(/listening on :(\d+)/);
|
|
3762
4238
|
if (match) {
|
|
3763
4239
|
clearTimeout(timeout);
|
|
3764
|
-
|
|
4240
|
+
resolve19({ child, actualPort: parseInt(match[1], 10) });
|
|
3765
4241
|
}
|
|
3766
4242
|
}
|
|
3767
4243
|
child.stdout?.on("data", checkOutput);
|
|
3768
4244
|
child.stderr?.on("data", checkOutput);
|
|
3769
4245
|
child.on("error", () => {
|
|
3770
4246
|
clearTimeout(timeout);
|
|
3771
|
-
|
|
4247
|
+
resolve19(null);
|
|
3772
4248
|
});
|
|
3773
4249
|
child.on("exit", () => {
|
|
3774
4250
|
clearTimeout(timeout);
|
|
3775
|
-
|
|
4251
|
+
resolve19(null);
|
|
3776
4252
|
});
|
|
3777
4253
|
});
|
|
3778
4254
|
}
|
|
3779
4255
|
function spawnDetached(cwd, port, logDir) {
|
|
3780
|
-
const logsDir = logDir ??
|
|
3781
|
-
|
|
3782
|
-
const logPath =
|
|
4256
|
+
const logsDir = logDir ?? resolve14(cwd, ".volute", "logs");
|
|
4257
|
+
mkdirSync6(logsDir, { recursive: true });
|
|
4258
|
+
const logPath = resolve14(logsDir, "mind.log");
|
|
3783
4259
|
const logFd = openSync(logPath, "a");
|
|
3784
4260
|
const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
|
|
3785
4261
|
cwd,
|
|
@@ -3799,7 +4275,7 @@ function spawnDetached(cwd, port, logDir) {
|
|
|
3799
4275
|
}
|
|
3800
4276
|
const interval = setInterval(() => {
|
|
3801
4277
|
try {
|
|
3802
|
-
const content =
|
|
4278
|
+
const content = readFileSync8(logPath, "utf-8");
|
|
3803
4279
|
const match = content.match(/listening on :(\d+)/);
|
|
3804
4280
|
if (match) {
|
|
3805
4281
|
finish({ child, actualPort: parseInt(match[1], 10) });
|
|
@@ -3851,7 +4327,7 @@ async function verify(port) {
|
|
|
3851
4327
|
}
|
|
3852
4328
|
|
|
3853
4329
|
// src/web/api/variants.ts
|
|
3854
|
-
var
|
|
4330
|
+
var app16 = new Hono16().get("/:name/variants", async (c) => {
|
|
3855
4331
|
const name = c.req.param("name");
|
|
3856
4332
|
const entry = findMind(name);
|
|
3857
4333
|
if (!entry) return c.json({ error: "Mind not found" }, 404);
|
|
@@ -3881,11 +4357,11 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
3881
4357
|
const err = validateBranchName(variantName);
|
|
3882
4358
|
if (err) return c.json({ error: err }, 400);
|
|
3883
4359
|
const projectRoot = mindDir(mindName);
|
|
3884
|
-
const variantDir =
|
|
3885
|
-
if (
|
|
4360
|
+
const variantDir = resolve15(projectRoot, ".variants", variantName);
|
|
4361
|
+
if (existsSync10(variantDir)) {
|
|
3886
4362
|
return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
|
|
3887
4363
|
}
|
|
3888
|
-
|
|
4364
|
+
mkdirSync7(resolve15(projectRoot, ".variants"), { recursive: true });
|
|
3889
4365
|
try {
|
|
3890
4366
|
await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
3891
4367
|
} catch (e) {
|
|
@@ -3898,7 +4374,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
3898
4374
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
3899
4375
|
await exec(cmd, args, {
|
|
3900
4376
|
cwd: variantDir,
|
|
3901
|
-
env: { ...process.env, HOME:
|
|
4377
|
+
env: { ...process.env, HOME: resolve15(variantDir, "home") }
|
|
3902
4378
|
});
|
|
3903
4379
|
} else {
|
|
3904
4380
|
await exec("npm", ["install"], { cwd: variantDir });
|
|
@@ -3908,7 +4384,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
3908
4384
|
return c.json({ error: `npm install failed: ${msg}` }, 500);
|
|
3909
4385
|
}
|
|
3910
4386
|
if (body.soul) {
|
|
3911
|
-
|
|
4387
|
+
writeFileSync7(resolve15(variantDir, "home/SOUL.md"), body.soul);
|
|
3912
4388
|
}
|
|
3913
4389
|
const variantPort = body.port ?? nextPort();
|
|
3914
4390
|
const variant = {
|
|
@@ -3946,7 +4422,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
3946
4422
|
} catch {
|
|
3947
4423
|
}
|
|
3948
4424
|
const projectRoot = mindDir(mindName);
|
|
3949
|
-
if (
|
|
4425
|
+
if (existsSync10(variant.path)) {
|
|
3950
4426
|
const status = (await gitExec(["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
3951
4427
|
if (status) {
|
|
3952
4428
|
try {
|
|
@@ -4003,7 +4479,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
4003
4479
|
} catch (e) {
|
|
4004
4480
|
return c.json({ error: "Merge failed. Resolve conflicts manually." }, 500);
|
|
4005
4481
|
}
|
|
4006
|
-
if (
|
|
4482
|
+
if (existsSync10(variant.path)) {
|
|
4007
4483
|
try {
|
|
4008
4484
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
4009
4485
|
} catch {
|
|
@@ -4020,7 +4496,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
4020
4496
|
const [cmd, args] = wrapForIsolation("npm", ["install"], mindName);
|
|
4021
4497
|
await exec(cmd, args, {
|
|
4022
4498
|
cwd: projectRoot,
|
|
4023
|
-
env: { ...process.env, HOME:
|
|
4499
|
+
env: { ...process.env, HOME: resolve15(projectRoot, "home") }
|
|
4024
4500
|
});
|
|
4025
4501
|
} else {
|
|
4026
4502
|
await exec("npm", ["install"], { cwd: projectRoot });
|
|
@@ -4063,7 +4539,7 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
4063
4539
|
} catch {
|
|
4064
4540
|
}
|
|
4065
4541
|
}
|
|
4066
|
-
if (
|
|
4542
|
+
if (existsSync10(variant.path)) {
|
|
4067
4543
|
try {
|
|
4068
4544
|
await gitExec(["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
4069
4545
|
} catch {
|
|
@@ -4077,29 +4553,83 @@ var app13 = new Hono13().get("/:name/variants", async (c) => {
|
|
|
4077
4553
|
chownMindDir(projectRoot, mindName);
|
|
4078
4554
|
return c.json({ ok: true });
|
|
4079
4555
|
});
|
|
4080
|
-
var variants_default =
|
|
4556
|
+
var variants_default = app16;
|
|
4557
|
+
|
|
4558
|
+
// src/web/api/volute/channels.ts
|
|
4559
|
+
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
4560
|
+
import { Hono as Hono17 } from "hono";
|
|
4561
|
+
import { z as z6 } from "zod";
|
|
4562
|
+
var createSchema = z6.object({
|
|
4563
|
+
name: z6.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
|
|
4564
|
+
});
|
|
4565
|
+
var app17 = new Hono17().get("/", async (c) => {
|
|
4566
|
+
const user = c.get("user");
|
|
4567
|
+
const channels = await listChannels();
|
|
4568
|
+
const results = await Promise.all(
|
|
4569
|
+
channels.map(async (ch) => {
|
|
4570
|
+
const participants = await getParticipants(ch.id);
|
|
4571
|
+
const isMember = participants.some((p) => p.userId === user.id);
|
|
4572
|
+
return { ...ch, participantCount: participants.length, isMember };
|
|
4573
|
+
})
|
|
4574
|
+
);
|
|
4575
|
+
return c.json(results);
|
|
4576
|
+
}).post("/", zValidator6("json", createSchema), async (c) => {
|
|
4577
|
+
const user = c.get("user");
|
|
4578
|
+
const body = c.req.valid("json");
|
|
4579
|
+
try {
|
|
4580
|
+
const ch = await createChannel(body.name, user.id);
|
|
4581
|
+
return c.json(ch, 201);
|
|
4582
|
+
} catch (err) {
|
|
4583
|
+
const cause = err instanceof Error ? err.cause : null;
|
|
4584
|
+
if (cause && /UNIQUE/i.test(cause.extendedCode ?? cause.message ?? "")) {
|
|
4585
|
+
return c.json({ error: "Channel already exists" }, 409);
|
|
4586
|
+
}
|
|
4587
|
+
throw err;
|
|
4588
|
+
}
|
|
4589
|
+
}).post("/:name/join", async (c) => {
|
|
4590
|
+
const name = c.req.param("name");
|
|
4591
|
+
const user = c.get("user");
|
|
4592
|
+
const ch = await getChannelByName(name);
|
|
4593
|
+
if (!ch) return c.json({ error: "Channel not found" }, 404);
|
|
4594
|
+
await joinChannel(ch.id, user.id);
|
|
4595
|
+
return c.json({ ok: true, conversationId: ch.id });
|
|
4596
|
+
}).post("/:name/leave", async (c) => {
|
|
4597
|
+
const name = c.req.param("name");
|
|
4598
|
+
const user = c.get("user");
|
|
4599
|
+
const ch = await getChannelByName(name);
|
|
4600
|
+
if (!ch) return c.json({ error: "Channel not found" }, 404);
|
|
4601
|
+
await leaveChannel(ch.id, user.id);
|
|
4602
|
+
return c.json({ ok: true });
|
|
4603
|
+
}).get("/:name/members", async (c) => {
|
|
4604
|
+
const name = c.req.param("name");
|
|
4605
|
+
const ch = await getChannelByName(name);
|
|
4606
|
+
if (!ch) return c.json({ error: "Channel not found" }, 404);
|
|
4607
|
+
const participants = await getParticipants(ch.id);
|
|
4608
|
+
return c.json(participants);
|
|
4609
|
+
});
|
|
4610
|
+
var channels_default2 = app17;
|
|
4081
4611
|
|
|
4082
4612
|
// src/web/api/volute/chat.ts
|
|
4083
|
-
import { readFileSync as
|
|
4084
|
-
import { resolve as
|
|
4085
|
-
import { zValidator as
|
|
4086
|
-
import { Hono as
|
|
4613
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4614
|
+
import { resolve as resolve16 } from "path";
|
|
4615
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
4616
|
+
import { Hono as Hono18 } from "hono";
|
|
4087
4617
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
4088
|
-
import { z as
|
|
4089
|
-
var chatSchema =
|
|
4090
|
-
message:
|
|
4091
|
-
conversationId:
|
|
4092
|
-
sender:
|
|
4093
|
-
images:
|
|
4094
|
-
|
|
4095
|
-
media_type:
|
|
4096
|
-
data:
|
|
4618
|
+
import { z as z7 } from "zod";
|
|
4619
|
+
var chatSchema = z7.object({
|
|
4620
|
+
message: z7.string().optional(),
|
|
4621
|
+
conversationId: z7.string().optional(),
|
|
4622
|
+
sender: z7.string().optional(),
|
|
4623
|
+
images: z7.array(
|
|
4624
|
+
z7.object({
|
|
4625
|
+
media_type: z7.string(),
|
|
4626
|
+
data: z7.string()
|
|
4097
4627
|
})
|
|
4098
4628
|
).optional()
|
|
4099
4629
|
});
|
|
4100
4630
|
function getDaemonUrl() {
|
|
4101
4631
|
try {
|
|
4102
|
-
const data = JSON.parse(
|
|
4632
|
+
const data = JSON.parse(readFileSync9(resolve16(voluteHome(), "daemon.json"), "utf-8"));
|
|
4103
4633
|
return `http://${daemonLoopback()}:${data.port}`;
|
|
4104
4634
|
} catch (err) {
|
|
4105
4635
|
throw new Error(`Failed to read daemon config: ${err instanceof Error ? err.message : err}`);
|
|
@@ -4115,7 +4645,7 @@ function daemonFetchInternal(path, body) {
|
|
|
4115
4645
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
4116
4646
|
return fetch(`${daemonUrl}${path}`, { method: "POST", headers, body });
|
|
4117
4647
|
}
|
|
4118
|
-
var
|
|
4648
|
+
var app18 = new Hono18().post("/:name/chat", zValidator7("json", chatSchema), async (c) => {
|
|
4119
4649
|
const name = c.req.param("name");
|
|
4120
4650
|
const [baseName] = name.split("@", 2);
|
|
4121
4651
|
const entry = findMind(baseName);
|
|
@@ -4176,7 +4706,7 @@ var app14 = new Hono14().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
4176
4706
|
const participants = await getParticipants(conversationId);
|
|
4177
4707
|
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
4178
4708
|
const participantNames = participants.map((p) => p.username);
|
|
4179
|
-
const { getMindManager: getMindManager2 } = await import("./mind-manager-
|
|
4709
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-Z7O7PN2O.js");
|
|
4180
4710
|
const manager = getMindManager2();
|
|
4181
4711
|
const runningMinds = mindParticipants.map((ap) => {
|
|
4182
4712
|
const mindKey = ap.username === baseName ? name : ap.username;
|
|
@@ -4221,8 +4751,8 @@ var app14 = new Hono14().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
4221
4751
|
});
|
|
4222
4752
|
daemonFetchInternal(`/api/minds/${encodeURIComponent(targetName)}/message`, payload).then(async (res) => {
|
|
4223
4753
|
if (!res.ok) {
|
|
4224
|
-
const
|
|
4225
|
-
console.error(`[chat] mind ${mindName} responded ${res.status}: ${
|
|
4754
|
+
const text = await res.text().catch(() => "");
|
|
4755
|
+
console.error(`[chat] mind ${mindName} responded ${res.status}: ${text}`);
|
|
4226
4756
|
}
|
|
4227
4757
|
}).catch((err) => {
|
|
4228
4758
|
console.error(`[chat] mind ${mindName} unreachable via daemon:`, err);
|
|
@@ -4246,27 +4776,116 @@ var app14 = new Hono14().post("/:name/chat", zValidator4("json", chatSchema), as
|
|
|
4246
4776
|
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
4247
4777
|
});
|
|
4248
4778
|
}, 15e3);
|
|
4249
|
-
await new Promise((
|
|
4779
|
+
await new Promise((resolve19) => {
|
|
4250
4780
|
stream.onAbort(() => {
|
|
4251
4781
|
unsubscribe();
|
|
4252
4782
|
clearInterval(keepAlive);
|
|
4253
|
-
|
|
4783
|
+
resolve19();
|
|
4254
4784
|
});
|
|
4255
4785
|
});
|
|
4256
4786
|
});
|
|
4257
4787
|
});
|
|
4258
|
-
var
|
|
4788
|
+
var unifiedChatSchema = z7.object({
|
|
4789
|
+
message: z7.string().optional(),
|
|
4790
|
+
conversationId: z7.string(),
|
|
4791
|
+
images: z7.array(z7.object({ media_type: z7.string(), data: z7.string() })).optional()
|
|
4792
|
+
});
|
|
4793
|
+
var unifiedChatApp = new Hono18().post(
|
|
4794
|
+
"/chat",
|
|
4795
|
+
zValidator7("json", unifiedChatSchema),
|
|
4796
|
+
async (c) => {
|
|
4797
|
+
const user = c.get("user");
|
|
4798
|
+
const body = c.req.valid("json");
|
|
4799
|
+
if (!body.message && (!body.images || body.images.length === 0)) {
|
|
4800
|
+
return c.json({ error: "message or images required" }, 400);
|
|
4801
|
+
}
|
|
4802
|
+
const conv = await getConversation(body.conversationId);
|
|
4803
|
+
if (!conv) return c.json({ error: "Conversation not found" }, 404);
|
|
4804
|
+
if (user.id !== 0 && !await isParticipantOrOwner(body.conversationId, user.id)) {
|
|
4805
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
4806
|
+
}
|
|
4807
|
+
const senderName = user.username;
|
|
4808
|
+
const contentBlocks = [];
|
|
4809
|
+
if (body.message) contentBlocks.push({ type: "text", text: body.message });
|
|
4810
|
+
if (body.images) {
|
|
4811
|
+
for (const img of body.images) {
|
|
4812
|
+
contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
await addMessage(body.conversationId, "user", senderName, contentBlocks);
|
|
4816
|
+
const participants = await getParticipants(body.conversationId);
|
|
4817
|
+
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
4818
|
+
const participantNames = participants.map((p) => p.username);
|
|
4819
|
+
const { getMindManager: getMindManager2 } = await import("./mind-manager-Z7O7PN2O.js");
|
|
4820
|
+
const manager = getMindManager2();
|
|
4821
|
+
const runningMinds = mindParticipants.map((ap) => manager.isRunning(ap.username) ? ap.username : null).filter((n) => n !== null && n !== senderName);
|
|
4822
|
+
const isDM = conv.type === "dm" && participants.length === 2;
|
|
4823
|
+
const channelEntry = {
|
|
4824
|
+
platformId: body.conversationId,
|
|
4825
|
+
platform: "volute",
|
|
4826
|
+
name: conv.title ?? void 0,
|
|
4827
|
+
type: conv.type === "channel" ? "group" : isDM ? "dm" : "group"
|
|
4828
|
+
};
|
|
4829
|
+
for (const ap of mindParticipants) {
|
|
4830
|
+
const slug = buildVoluteSlug({
|
|
4831
|
+
participants,
|
|
4832
|
+
mindUsername: ap.username,
|
|
4833
|
+
convTitle: conv.title,
|
|
4834
|
+
conversationId: conv.id,
|
|
4835
|
+
convType: conv.type,
|
|
4836
|
+
convName: conv.name
|
|
4837
|
+
});
|
|
4838
|
+
try {
|
|
4839
|
+
writeChannelEntry(ap.username, slug, channelEntry);
|
|
4840
|
+
} catch (err) {
|
|
4841
|
+
console.warn(`[chat] failed to write channel entry for ${ap.username}:`, err);
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
for (const mindName of runningMinds) {
|
|
4845
|
+
const channel = buildVoluteSlug({
|
|
4846
|
+
participants,
|
|
4847
|
+
mindUsername: mindName,
|
|
4848
|
+
convTitle: conv.title,
|
|
4849
|
+
conversationId: body.conversationId,
|
|
4850
|
+
convType: conv.type,
|
|
4851
|
+
convName: conv.name
|
|
4852
|
+
});
|
|
4853
|
+
const typingMap = getTypingMap();
|
|
4854
|
+
const currentlyTyping = typingMap.get(channel);
|
|
4855
|
+
const payload = JSON.stringify({
|
|
4856
|
+
content: contentBlocks,
|
|
4857
|
+
channel,
|
|
4858
|
+
conversationId: body.conversationId,
|
|
4859
|
+
sender: senderName,
|
|
4860
|
+
participants: participantNames,
|
|
4861
|
+
participantCount: participants.length,
|
|
4862
|
+
isDM,
|
|
4863
|
+
...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
|
|
4864
|
+
});
|
|
4865
|
+
daemonFetchInternal(`/api/minds/${encodeURIComponent(mindName)}/message`, payload).then(async (res) => {
|
|
4866
|
+
if (!res.ok) {
|
|
4867
|
+
const text = await res.text().catch(() => "");
|
|
4868
|
+
console.error(`[chat] mind ${mindName} responded ${res.status}: ${text}`);
|
|
4869
|
+
}
|
|
4870
|
+
}).catch((err) => {
|
|
4871
|
+
console.error(`[chat] mind ${mindName} unreachable via daemon:`, err);
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4874
|
+
return c.json({ ok: true, conversationId: body.conversationId });
|
|
4875
|
+
}
|
|
4876
|
+
);
|
|
4877
|
+
var chat_default = app18;
|
|
4259
4878
|
|
|
4260
4879
|
// src/web/api/volute/conversations.ts
|
|
4261
|
-
import { zValidator as
|
|
4262
|
-
import { Hono as
|
|
4263
|
-
import { z as
|
|
4264
|
-
var createConvSchema =
|
|
4265
|
-
title:
|
|
4266
|
-
participantIds:
|
|
4267
|
-
participantNames:
|
|
4880
|
+
import { zValidator as zValidator8 } from "@hono/zod-validator";
|
|
4881
|
+
import { Hono as Hono19 } from "hono";
|
|
4882
|
+
import { z as z8 } from "zod";
|
|
4883
|
+
var createConvSchema = z8.object({
|
|
4884
|
+
title: z8.string().optional(),
|
|
4885
|
+
participantIds: z8.array(z8.number()).optional(),
|
|
4886
|
+
participantNames: z8.array(z8.string()).optional()
|
|
4268
4887
|
});
|
|
4269
|
-
var
|
|
4888
|
+
var app19 = new Hono19().get("/:name/conversations", async (c) => {
|
|
4270
4889
|
const name = c.req.param("name");
|
|
4271
4890
|
const user = c.get("user");
|
|
4272
4891
|
let lookupId = user.id;
|
|
@@ -4275,9 +4894,9 @@ var app15 = new Hono15().get("/:name/conversations", async (c) => {
|
|
|
4275
4894
|
lookupId = mindUser.id;
|
|
4276
4895
|
}
|
|
4277
4896
|
const all = await listConversationsForUser(lookupId);
|
|
4278
|
-
const convs = all.filter((c2) => c2.mind_name === name);
|
|
4897
|
+
const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
|
|
4279
4898
|
return c.json(convs);
|
|
4280
|
-
}).post("/:name/conversations",
|
|
4899
|
+
}).post("/:name/conversations", zValidator8("json", createConvSchema), async (c) => {
|
|
4281
4900
|
const name = c.req.param("name");
|
|
4282
4901
|
const user = c.get("user");
|
|
4283
4902
|
const body = c.req.valid("json");
|
|
@@ -4351,17 +4970,18 @@ var app15 = new Hono15().get("/:name/conversations", async (c) => {
|
|
|
4351
4970
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
4352
4971
|
return c.json({ ok: true });
|
|
4353
4972
|
});
|
|
4354
|
-
var conversations_default =
|
|
4973
|
+
var conversations_default = app19;
|
|
4355
4974
|
|
|
4356
4975
|
// src/web/api/volute/user-conversations.ts
|
|
4357
|
-
import { zValidator as
|
|
4358
|
-
import { Hono as
|
|
4359
|
-
import {
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4976
|
+
import { zValidator as zValidator9 } from "@hono/zod-validator";
|
|
4977
|
+
import { Hono as Hono20 } from "hono";
|
|
4978
|
+
import { streamSSE as streamSSE4 } from "hono/streaming";
|
|
4979
|
+
import { z as z9 } from "zod";
|
|
4980
|
+
var createSchema2 = z9.object({
|
|
4981
|
+
title: z9.string().optional(),
|
|
4982
|
+
participantNames: z9.array(z9.string()).min(1)
|
|
4363
4983
|
});
|
|
4364
|
-
var
|
|
4984
|
+
var app20 = new Hono20().use("*", authMiddleware).get("/", async (c) => {
|
|
4365
4985
|
const user = c.get("user");
|
|
4366
4986
|
const convs = await listConversationsWithParticipants(user.id);
|
|
4367
4987
|
return c.json(convs);
|
|
@@ -4373,7 +4993,7 @@ var app16 = new Hono16().use("*", authMiddleware).get("/", async (c) => {
|
|
|
4373
4993
|
}
|
|
4374
4994
|
const msgs = await getMessages(id);
|
|
4375
4995
|
return c.json(msgs);
|
|
4376
|
-
}).post("/",
|
|
4996
|
+
}).post("/", zValidator9("json", createSchema2), async (c) => {
|
|
4377
4997
|
const user = c.get("user");
|
|
4378
4998
|
const body = c.req.valid("json");
|
|
4379
4999
|
const participantIds = /* @__PURE__ */ new Set();
|
|
@@ -4403,6 +5023,31 @@ var app16 = new Hono16().use("*", authMiddleware).get("/", async (c) => {
|
|
|
4403
5023
|
participantIds: [...participantIds]
|
|
4404
5024
|
});
|
|
4405
5025
|
return c.json(conv, 201);
|
|
5026
|
+
}).get("/:id/events", async (c) => {
|
|
5027
|
+
const conversationId = c.req.param("id");
|
|
5028
|
+
const user = c.get("user");
|
|
5029
|
+
if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
|
|
5030
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
5031
|
+
}
|
|
5032
|
+
return streamSSE4(c, async (stream) => {
|
|
5033
|
+
const unsubscribe = subscribe(conversationId, (event) => {
|
|
5034
|
+
stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
|
|
5035
|
+
if (!stream.aborted) console.error("[chat] SSE write error:", err);
|
|
5036
|
+
});
|
|
5037
|
+
});
|
|
5038
|
+
const keepAlive = setInterval(() => {
|
|
5039
|
+
stream.writeSSE({ data: "" }).catch((err) => {
|
|
5040
|
+
if (!stream.aborted) console.error("[chat] SSE ping error:", err);
|
|
5041
|
+
});
|
|
5042
|
+
}, 15e3);
|
|
5043
|
+
await new Promise((resolve19) => {
|
|
5044
|
+
stream.onAbort(() => {
|
|
5045
|
+
unsubscribe();
|
|
5046
|
+
clearInterval(keepAlive);
|
|
5047
|
+
resolve19();
|
|
5048
|
+
});
|
|
5049
|
+
});
|
|
5050
|
+
});
|
|
4406
5051
|
}).delete("/:id", async (c) => {
|
|
4407
5052
|
const id = c.req.param("id");
|
|
4408
5053
|
const user = c.get("user");
|
|
@@ -4410,12 +5055,12 @@ var app16 = new Hono16().use("*", authMiddleware).get("/", async (c) => {
|
|
|
4410
5055
|
if (!deleted) return c.json({ error: "Conversation not found" }, 404);
|
|
4411
5056
|
return c.json({ ok: true });
|
|
4412
5057
|
});
|
|
4413
|
-
var user_conversations_default =
|
|
5058
|
+
var user_conversations_default = app20;
|
|
4414
5059
|
|
|
4415
5060
|
// src/web/app.ts
|
|
4416
5061
|
var httpLog = logger_default.child("http");
|
|
4417
|
-
var
|
|
4418
|
-
|
|
5062
|
+
var app21 = new Hono21();
|
|
5063
|
+
app21.onError((err, c) => {
|
|
4419
5064
|
if (err instanceof HTTPException) {
|
|
4420
5065
|
return err.getResponse();
|
|
4421
5066
|
}
|
|
@@ -4426,10 +5071,10 @@ app17.onError((err, c) => {
|
|
|
4426
5071
|
});
|
|
4427
5072
|
return c.json({ error: "Internal server error" }, 500);
|
|
4428
5073
|
});
|
|
4429
|
-
|
|
5074
|
+
app21.notFound((c) => {
|
|
4430
5075
|
return c.json({ error: "Not found" }, 404);
|
|
4431
5076
|
});
|
|
4432
|
-
|
|
5077
|
+
app21.use("*", async (c, next) => {
|
|
4433
5078
|
const start = Date.now();
|
|
4434
5079
|
await next();
|
|
4435
5080
|
const duration = Date.now() - start;
|
|
@@ -4440,7 +5085,7 @@ app17.use("*", async (c, next) => {
|
|
|
4440
5085
|
httpLog.debug("request", data);
|
|
4441
5086
|
}
|
|
4442
5087
|
});
|
|
4443
|
-
|
|
5088
|
+
app21.get("/api/health", (c) => {
|
|
4444
5089
|
let version = "unknown";
|
|
4445
5090
|
let cached = null;
|
|
4446
5091
|
try {
|
|
@@ -4455,15 +5100,18 @@ app17.get("/api/health", (c) => {
|
|
|
4455
5100
|
...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
|
|
4456
5101
|
});
|
|
4457
5102
|
});
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
5103
|
+
app21.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
|
|
5104
|
+
app21.use("/api/*", csrf());
|
|
5105
|
+
app21.use("/api/minds/*", authMiddleware);
|
|
5106
|
+
app21.use("/api/conversations/*", authMiddleware);
|
|
5107
|
+
app21.use("/api/volute/*", authMiddleware);
|
|
5108
|
+
app21.use("/api/system/*", authMiddleware);
|
|
5109
|
+
app21.use("/api/env/*", authMiddleware);
|
|
5110
|
+
app21.use("/api/prompts/*", authMiddleware);
|
|
5111
|
+
app21.use("/api/skills/*", authMiddleware);
|
|
5112
|
+
app21.route("/pages", pages_default);
|
|
5113
|
+
var routes = app21.route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default).route("/api/minds", connectors_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp);
|
|
5114
|
+
var app_default = app21;
|
|
4467
5115
|
|
|
4468
5116
|
// src/web/server.ts
|
|
4469
5117
|
var MIME_TYPES2 = {
|
|
@@ -4480,20 +5128,20 @@ async function startServer({
|
|
|
4480
5128
|
hostname = "127.0.0.1"
|
|
4481
5129
|
}) {
|
|
4482
5130
|
let assetsDir = "";
|
|
4483
|
-
let searchDir =
|
|
5131
|
+
let searchDir = dirname2(new URL(import.meta.url).pathname);
|
|
4484
5132
|
for (let i = 0; i < 5; i++) {
|
|
4485
|
-
const candidate =
|
|
4486
|
-
if (
|
|
5133
|
+
const candidate = resolve17(searchDir, "dist", "web-assets");
|
|
5134
|
+
if (existsSync11(candidate)) {
|
|
4487
5135
|
assetsDir = candidate;
|
|
4488
5136
|
break;
|
|
4489
5137
|
}
|
|
4490
|
-
searchDir =
|
|
5138
|
+
searchDir = dirname2(searchDir);
|
|
4491
5139
|
}
|
|
4492
5140
|
if (assetsDir) {
|
|
4493
5141
|
app_default.get("*", async (c) => {
|
|
4494
5142
|
const urlPath = new URL(c.req.url).pathname;
|
|
4495
5143
|
if (urlPath.startsWith("/api/")) return c.notFound();
|
|
4496
|
-
const filePath =
|
|
5144
|
+
const filePath = resolve17(assetsDir, urlPath.slice(1));
|
|
4497
5145
|
if (!filePath.startsWith(assetsDir)) return c.text("Forbidden", 403);
|
|
4498
5146
|
const s = await stat2(filePath).catch(() => null);
|
|
4499
5147
|
if (s?.isFile()) {
|
|
@@ -4502,7 +5150,7 @@ async function startServer({
|
|
|
4502
5150
|
const body = await readFile3(filePath);
|
|
4503
5151
|
return c.body(body, 200, { "Content-Type": mime });
|
|
4504
5152
|
}
|
|
4505
|
-
const indexPath =
|
|
5153
|
+
const indexPath = resolve17(assetsDir, "index.html");
|
|
4506
5154
|
const indexStat = await stat2(indexPath).catch(() => null);
|
|
4507
5155
|
if (indexStat?.isFile()) {
|
|
4508
5156
|
const body = await readFile3(indexPath, "utf-8");
|
|
@@ -4512,10 +5160,10 @@ async function startServer({
|
|
|
4512
5160
|
});
|
|
4513
5161
|
}
|
|
4514
5162
|
const server = serve({ fetch: app_default.fetch, port, hostname });
|
|
4515
|
-
await new Promise((
|
|
5163
|
+
await new Promise((resolve19, reject) => {
|
|
4516
5164
|
server.on("listening", () => {
|
|
4517
5165
|
logger_default.info("Volute UI running", { hostname, port });
|
|
4518
|
-
|
|
5166
|
+
resolve19();
|
|
4519
5167
|
});
|
|
4520
5168
|
server.on("error", (err) => {
|
|
4521
5169
|
reject(err);
|
|
@@ -4526,14 +5174,14 @@ async function startServer({
|
|
|
4526
5174
|
|
|
4527
5175
|
// src/daemon.ts
|
|
4528
5176
|
if (!process.env.VOLUTE_HOME) {
|
|
4529
|
-
process.env.VOLUTE_HOME =
|
|
5177
|
+
process.env.VOLUTE_HOME = resolve18(homedir2(), ".volute");
|
|
4530
5178
|
}
|
|
4531
5179
|
async function startDaemon(opts) {
|
|
4532
5180
|
const { port, hostname } = opts;
|
|
4533
5181
|
const myPid = String(process.pid);
|
|
4534
5182
|
const home = voluteHome();
|
|
4535
5183
|
if (!opts.foreground) {
|
|
4536
|
-
const rotatingLog = new RotatingLog(
|
|
5184
|
+
const rotatingLog = new RotatingLog(resolve18(home, "daemon.log"));
|
|
4537
5185
|
logger_default.setOutput((line) => rotatingLog.write(`${line}
|
|
4538
5186
|
`));
|
|
4539
5187
|
const write = (...args) => rotatingLog.write(`${format(...args)}
|
|
@@ -4543,9 +5191,9 @@ async function startDaemon(opts) {
|
|
|
4543
5191
|
console.warn = write;
|
|
4544
5192
|
console.info = write;
|
|
4545
5193
|
}
|
|
4546
|
-
const DAEMON_PID_PATH =
|
|
4547
|
-
const DAEMON_JSON_PATH =
|
|
4548
|
-
|
|
5194
|
+
const DAEMON_PID_PATH = resolve18(home, "daemon.pid");
|
|
5195
|
+
const DAEMON_JSON_PATH = resolve18(home, "daemon.json");
|
|
5196
|
+
mkdirSync8(home, { recursive: true });
|
|
4549
5197
|
migrateAgentsToMinds();
|
|
4550
5198
|
const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
|
|
4551
5199
|
process.env.VOLUTE_DAEMON_TOKEN = token;
|
|
@@ -4562,8 +5210,8 @@ async function startDaemon(opts) {
|
|
|
4562
5210
|
}
|
|
4563
5211
|
throw err;
|
|
4564
5212
|
}
|
|
4565
|
-
|
|
4566
|
-
|
|
5213
|
+
writeFileSync8(DAEMON_PID_PATH, myPid, { mode: 420 });
|
|
5214
|
+
writeFileSync8(DAEMON_JSON_PATH, `${JSON.stringify({ port, hostname, token }, null, 2)}
|
|
4567
5215
|
`, {
|
|
4568
5216
|
mode: 420
|
|
4569
5217
|
});
|
|
@@ -4622,13 +5270,13 @@ async function startDaemon(opts) {
|
|
|
4622
5270
|
logger_default.info(`running on ${hostname}:${port}, pid ${myPid}`);
|
|
4623
5271
|
function cleanup() {
|
|
4624
5272
|
try {
|
|
4625
|
-
if (
|
|
5273
|
+
if (readFileSync10(DAEMON_PID_PATH, "utf-8").trim() === myPid) {
|
|
4626
5274
|
unlinkSync2(DAEMON_PID_PATH);
|
|
4627
5275
|
}
|
|
4628
5276
|
} catch {
|
|
4629
5277
|
}
|
|
4630
5278
|
try {
|
|
4631
|
-
const data = JSON.parse(
|
|
5279
|
+
const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
|
|
4632
5280
|
if (data.token === token) {
|
|
4633
5281
|
unlinkSync2(DAEMON_JSON_PATH);
|
|
4634
5282
|
}
|