volute 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/{activity-events-ZMBAKLUF.js → activity-events-BBIEA2F4.js} +2 -3
- package/dist/api.d.ts +363 -168
- package/dist/{archive-4ZQYK5MN.js → archive-UA4BDFXQ.js} +2 -2
- package/dist/{auth-4TV573WE.js → auth-D3OT2ARB.js} +3 -3
- package/dist/bridge-FQHZL3MC.js +206 -0
- package/dist/chat-MHJ3L6JQ.js +58 -0
- package/dist/{chunk-PHU4DEAJ.js → chunk-2WPW7OT6.js} +3 -3
- package/dist/{chunk-5Y3PBKW6.js → chunk-2YP2TVDT.js} +138 -56
- package/dist/{chunk-USNBKHYG.js → chunk-4WXYUOAK.js} +4 -6
- package/dist/{chunk-YJA7P64S.js → chunk-AW7PFDVN.js} +5 -5
- package/dist/{chunk-OZFKBXD6.js → chunk-EHYDTZTF.js} +6 -6
- package/dist/{chunk-LX22GRG7.js → chunk-GIE6CSN5.js} +11 -8
- package/dist/{chunk-WBHMQ5OZ.js → chunk-H7OZRFJB.js} +192 -12
- package/dist/{chunk-ON3FF5JA.js → chunk-HDN7MNGD.js} +3 -3
- package/dist/chunk-IAYBDWVG.js +477 -0
- package/dist/{chunk-TZKJLDQN.js → chunk-IKRVFPWU.js} +14 -9
- package/dist/{chunk-WGOGUMPO.js → chunk-JGFVMROS.js} +13 -6
- package/dist/{chunk-3TV4GLFO.js → chunk-JKOWNZ4P.js} +3 -3
- package/dist/{chunk-NWI2425I.js → chunk-K5NAC55T.js} +1 -1
- package/dist/{chunk-HFCBO2GL.js → chunk-KDGS53OS.js} +4 -4
- package/dist/chunk-KTLFDYPT.js +61 -0
- package/dist/{chunk-V63B7DX3.js → chunk-LAC664WU.js} +7 -4
- package/dist/{chunk-3CFRE2VC.js → chunk-OQZH4PBB.js} +337 -1061
- package/dist/{chunk-2VO7453N.js → chunk-PHSAT7YL.js} +30 -54
- package/dist/{chunk-XOXLRRR2.js → chunk-RKQEHRBB.js} +4 -3
- package/dist/chunk-T6HKBWXZ.js +23 -0
- package/dist/{chunk-UTL75LP6.js → chunk-USUXRNVD.js} +22 -22
- package/dist/{chunk-J2CO4WEV.js → chunk-VIVMW2H2.js} +4 -4
- package/dist/{chunk-KTJGZ7M7.js → chunk-XBLSAVJF.js} +1 -1
- package/dist/cli.js +31 -36
- package/dist/{cloud-sync-NI2K3C7G.js → cloud-sync-T7M3ESC3.js} +15 -14
- package/dist/connectors/discord-bridge.js +158 -0
- package/dist/connectors/slack-bridge.js +119 -0
- package/dist/connectors/telegram-bridge.js +133 -0
- package/dist/conversations-M2K4253F.js +55 -0
- package/dist/create-D7J73A6H.js +45 -0
- package/dist/{create-4YBRTTJS.js → create-QWV73WXD.js} +1 -1
- package/dist/{daemon-client-Z7FAJ6JW.js → daemon-client-I42FK2BF.js} +2 -2
- package/dist/{daemon-restart-BJZ3O4U4.js → daemon-restart-M2QTYMEG.js} +7 -7
- package/dist/daemon.js +1758 -1024
- package/dist/db-IC4J52XQ.js +8 -0
- package/dist/{delete-27OYNK25.js → delete-4JYGD4VN.js} +1 -1
- package/dist/down-LVBXEULC.js +14 -0
- package/dist/{env-M336ONDP.js → env-YJMUMFIY.js} +2 -2
- package/dist/{export-HP4G5DQC.js → export-BOJQWBMA.js} +4 -4
- package/dist/{file-HUDKTRAS.js → file-CR36YUPD.js} +4 -4
- package/dist/{history-B64GTFTD.js → history-XKRTAFS2.js} +5 -5
- package/dist/{import-XIB7UV4S.js → import-SRTQXBGH.js} +4 -4
- package/dist/join-J4QU42DL.js +66 -0
- package/dist/list-R73GENNL.js +40 -0
- package/dist/{log-PBFNILJ4.js → log-ABYNVYJ3.js} +4 -4
- package/dist/{login-B5E7N7MY.js → login-3QZNR2DF.js} +4 -4
- package/dist/{login-6U7U6BNG.js → login-XX37I52P.js} +2 -2
- package/dist/{logout-XSJRYS3U.js → logout-T53VKCPU.js} +4 -4
- package/dist/{logout-UKD5LA37.js → logout-W4KOOBIT.js} +2 -2
- package/dist/{logs-3CART7O7.js → logs-U35JR2KE.js} +5 -5
- package/dist/{merge-VK2HSKMA.js → merge-LNSMSAOF.js} +4 -4
- package/dist/message-delivery-LDXLGERA.js +25 -0
- package/dist/migrate-registry-to-db-XC7T5B7P.js +110 -0
- package/dist/{mind-HZ3QSDDJ.js → mind-DI33C74K.js} +25 -25
- package/dist/{mind-activity-tracker-4G6FURY2.js → mind-activity-tracker-EN6XNXPF.js} +3 -4
- package/dist/mind-manager-M6EMUW5I.js +18 -0
- package/dist/{mind-sleep-DTV7L44D.js → mind-sleep-BTSWQNAC.js} +4 -4
- package/dist/{mind-wake-PFN4FN3T.js → mind-wake-SBAKIDVP.js} +4 -4
- package/dist/{notes-37FW2UR2.js → notes-XCER3I7M.js} +11 -21
- package/dist/{package-VZWLXPHV.js → package-7WY6VKU3.js} +1 -1
- package/dist/{pages-DIIT5HMQ.js → pages-6EBS6CBR.js} +2 -2
- package/dist/{publish-HQV7YREB.js → publish-66UB2ZFY.js} +5 -5
- package/dist/{pull-2MB4SK3C.js → pull-XCHJTM5M.js} +4 -4
- package/dist/read-36UFXN3G.js +46 -0
- package/dist/{register-EFND67FQ.js → register-6B2CXTYM.js} +2 -2
- package/dist/{registry-D2BSQ2X5.js → registry-NDNOOYG4.js} +15 -9
- package/dist/{restart-CCK7D6TV.js → restart-6ESL3NBO.js} +5 -5
- package/dist/{sandbox-EHGFF52K.js → sandbox-TGBX22DS.js} +3 -3
- package/dist/{schedule-6F7ELB2M.js → schedule-QTJMFATP.js} +5 -5
- package/dist/{seed-E5OQGWX3.js → seed-SSUCYYDF.js} +2 -2
- package/dist/{send-IH6XZKPC.js → send-ZNCJDSRP.js} +25 -19
- package/dist/{service-LLBV3R7M.js → service-6LIN3F3K.js} +4 -4
- package/dist/{setup-F6TWFYGQ.js → setup-JG4QAEBV.js} +12 -12
- package/dist/{setup-YGAAIKKZ.js → setup-JHL5ZEST.js} +2 -2
- package/dist/{shared-UMO4S7CC.js → shared-ML5I4Q2A.js} +4 -4
- package/dist/{skill-42LGFBQC.js → skill-AUAQTSP5.js} +5 -5
- package/dist/skills/dreaming/references/INSTALL.md +2 -2
- package/dist/skills/orientation/SKILL.md +3 -3
- package/dist/skills/volute-mind/SKILL.md +32 -30
- package/dist/sleep-manager-MWYHM5HV.js +29 -0
- package/dist/split-TKJ5OT3P.js +63 -0
- package/dist/{sprout-QL74KR2X.js → sprout-IJVVKSJ2.js} +6 -7
- package/dist/{start-O5JQASRC.js → start-EUJSS5R4.js} +2 -2
- package/dist/{status-FZBEBM7Q.js → status-77YEPHMW.js} +5 -5
- package/dist/{status-WXD4HXRL.js → status-7GA4SM4Y.js} +4 -4
- package/dist/{status-LV34BG6G.js → status-THLOBLWG.js} +2 -2
- package/dist/{stop-2SOG5NYF.js → stop-3XAITBBF.js} +5 -5
- package/dist/{tailscale-AJ4VL5XK.js → tailscale-NY5MUMY3.js} +1 -1
- package/dist/up-NKSMXBWR.js +17 -0
- package/dist/{update-5VUDAI3D.js → update-PTSH22AZ.js} +9 -9
- package/dist/{update-check-F5Z3ALXX.js → update-check-64FWC4Y2.js} +2 -2
- package/dist/{upgrade-QCCO33BK.js → upgrade-HA47CS4C.js} +12 -5
- package/dist/variant-7TGZHOU3.js +41 -0
- package/dist/{version-notify-USFZBWMG.js → version-notify-5Z4MNR6M.js} +26 -30
- package/dist/web-assets/assets/index-CI5wgghI.css +1 -0
- package/dist/web-assets/assets/index-is5CvJWH.js +75 -0
- package/dist/web-assets/favicon.png +0 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0017_minds.sql +16 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/.init/.config/prompts.json +2 -2
- package/templates/_base/home/VOLUTE.md +5 -5
- package/templates/_base/src/lib/startup.ts +2 -2
- package/dist/channel-ZVZV42UD.js +0 -260
- package/dist/chunk-B2CPS4QU.js +0 -283
- package/dist/chunk-SIAG3QMM.js +0 -42
- package/dist/chunk-WSLPZF72.js +0 -173
- package/dist/connector-G722WXAU.js +0 -147
- package/dist/connectors/discord.js +0 -177
- package/dist/connectors/slack.js +0 -181
- package/dist/connectors/telegram.js +0 -187
- package/dist/down-7UKFMJJZ.js +0 -14
- package/dist/message-delivery-MS5JYPZX.js +0 -25
- package/dist/mind-manager-VVK67AY3.js +0 -19
- package/dist/sleep-manager-EE4NRN2Q.js +0 -29
- package/dist/up-SDMCSVI3.js +0 -17
- package/dist/variant-WWLDY6D5.js +0 -207
- package/dist/web-assets/assets/index-CUQ31ieL.js +0 -69
- package/dist/web-assets/assets/index-CW8NSl1o.css +0 -1
|
@@ -1,81 +1,101 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readSystemsConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KDGS53OS.js";
|
|
5
5
|
import {
|
|
6
6
|
markIdle
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-K5NAC55T.js";
|
|
8
8
|
import {
|
|
9
|
-
broadcast,
|
|
10
|
-
publish,
|
|
11
|
-
subscribe
|
|
12
|
-
} from "./chunk-J2CO4WEV.js";
|
|
13
|
-
import {
|
|
14
|
-
RestartTracker,
|
|
15
|
-
RotatingLog,
|
|
16
9
|
clearJsonMap,
|
|
17
10
|
getMindManager,
|
|
18
|
-
getMindToken,
|
|
19
11
|
getPrompt,
|
|
20
12
|
loadJsonMap,
|
|
21
13
|
saveJsonMap
|
|
22
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-PHSAT7YL.js";
|
|
23
15
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
addMessage,
|
|
17
|
+
createChannel,
|
|
18
|
+
getChannelByName,
|
|
19
|
+
getParticipants,
|
|
20
|
+
joinChannel,
|
|
21
|
+
publish as publish2
|
|
22
|
+
} from "./chunk-IAYBDWVG.js";
|
|
27
23
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
getDb,
|
|
33
|
-
messages,
|
|
34
|
-
mindHistory,
|
|
35
|
-
users
|
|
36
|
-
} from "./chunk-WBHMQ5OZ.js";
|
|
24
|
+
broadcast,
|
|
25
|
+
publish,
|
|
26
|
+
subscribe
|
|
27
|
+
} from "./chunk-VIVMW2H2.js";
|
|
37
28
|
import {
|
|
38
29
|
logger_default
|
|
39
30
|
} from "./chunk-YUIHSKR6.js";
|
|
40
|
-
import {
|
|
41
|
-
readVoluteConfig
|
|
42
|
-
} from "./chunk-SIAG3QMM.js";
|
|
43
|
-
import {
|
|
44
|
-
loadMergedEnv
|
|
45
|
-
} from "./chunk-PHU4DEAJ.js";
|
|
46
31
|
import {
|
|
47
32
|
exec
|
|
48
|
-
} from "./chunk-
|
|
49
|
-
import {
|
|
50
|
-
chownMindDir,
|
|
51
|
-
isIsolationEnabled,
|
|
52
|
-
wrapForIsolation
|
|
53
|
-
} from "./chunk-XOXLRRR2.js";
|
|
33
|
+
} from "./chunk-AW7PFDVN.js";
|
|
54
34
|
import {
|
|
55
|
-
|
|
35
|
+
deliveryQueue,
|
|
56
36
|
findMind,
|
|
57
|
-
|
|
37
|
+
getBaseName,
|
|
38
|
+
getDb,
|
|
58
39
|
mindDir,
|
|
40
|
+
mindHistory,
|
|
59
41
|
readRegistry,
|
|
60
42
|
stateDir,
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
users,
|
|
44
|
+
voluteHome,
|
|
45
|
+
voluteSystemDir
|
|
46
|
+
} from "./chunk-H7OZRFJB.js";
|
|
63
47
|
|
|
64
48
|
// src/lib/daemon/sleep-manager.ts
|
|
65
49
|
import { execFile, spawn as spawnChild } from "child_process";
|
|
66
50
|
import {
|
|
67
51
|
existsSync as existsSync5,
|
|
68
|
-
mkdirSync as
|
|
52
|
+
mkdirSync as mkdirSync4,
|
|
69
53
|
readdirSync as readdirSync2,
|
|
70
54
|
readFileSync as readFileSync5,
|
|
71
55
|
readlinkSync,
|
|
72
56
|
renameSync,
|
|
73
|
-
writeFileSync as
|
|
57
|
+
writeFileSync as writeFileSync4
|
|
74
58
|
} from "fs";
|
|
75
59
|
import { resolve as resolve8 } from "path";
|
|
76
60
|
import { promisify } from "util";
|
|
77
61
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
78
|
-
import { and as
|
|
62
|
+
import { and as and3, eq as eq3, inArray as inArray2 } from "drizzle-orm";
|
|
63
|
+
|
|
64
|
+
// src/lib/volute-config.ts
|
|
65
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
66
|
+
import { dirname, resolve } from "path";
|
|
67
|
+
function readJson(path) {
|
|
68
|
+
if (!existsSync(path)) return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(`[volute-config] failed to parse ${path}: ${err}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function readVoluteConfig(mindDir2) {
|
|
77
|
+
const path = resolve(mindDir2, "home/.config/volute.json");
|
|
78
|
+
const config = readJson(path);
|
|
79
|
+
if (!config) return null;
|
|
80
|
+
const legacy = config;
|
|
81
|
+
if (!config.profile && ("displayName" in config || "description" in config || "avatar" in config)) {
|
|
82
|
+
config.profile = {
|
|
83
|
+
displayName: legacy.displayName,
|
|
84
|
+
description: legacy.description,
|
|
85
|
+
avatar: legacy.avatar
|
|
86
|
+
};
|
|
87
|
+
delete legacy.displayName;
|
|
88
|
+
delete legacy.description;
|
|
89
|
+
delete legacy.avatar;
|
|
90
|
+
}
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
function writeVoluteConfig(mindDir2, config) {
|
|
94
|
+
const path = resolve(mindDir2, "home/.config/volute.json");
|
|
95
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
96
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
79
99
|
|
|
80
100
|
// src/lib/auth.ts
|
|
81
101
|
import { compareSync, hashSync } from "bcryptjs";
|
|
@@ -208,8 +228,8 @@ async function migrateMindRoles() {
|
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
// src/lib/pages-watcher.ts
|
|
211
|
-
import { existsSync, readdirSync, statSync, watch } from "fs";
|
|
212
|
-
import { join, resolve } from "path";
|
|
231
|
+
import { existsSync as existsSync2, readdirSync, statSync, watch } from "fs";
|
|
232
|
+
import { join, resolve as resolve2 } from "path";
|
|
213
233
|
var watchers = /* @__PURE__ */ new Map();
|
|
214
234
|
var homeWatchers = /* @__PURE__ */ new Map();
|
|
215
235
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
@@ -245,18 +265,18 @@ function startPagesWatcher(mindName, pagesDir) {
|
|
|
245
265
|
}
|
|
246
266
|
function startWatcher(mindName) {
|
|
247
267
|
if (watchers.has(mindName)) return;
|
|
248
|
-
const pagesDir =
|
|
249
|
-
if (
|
|
268
|
+
const pagesDir = resolve2(mindDir(mindName), "home", "public", "pages");
|
|
269
|
+
if (existsSync2(pagesDir)) {
|
|
250
270
|
startPagesWatcher(mindName, pagesDir);
|
|
251
271
|
return;
|
|
252
272
|
}
|
|
253
273
|
if (homeWatchers.has(mindName)) return;
|
|
254
|
-
const publicDir =
|
|
255
|
-
if (!
|
|
274
|
+
const publicDir = resolve2(mindDir(mindName), "home", "public");
|
|
275
|
+
if (!existsSync2(publicDir)) return;
|
|
256
276
|
try {
|
|
257
277
|
const hw = watch(publicDir, (_eventType, filename) => {
|
|
258
278
|
if (filename !== "pages") return;
|
|
259
|
-
if (!
|
|
279
|
+
if (!existsSync2(pagesDir)) return;
|
|
260
280
|
hw.close();
|
|
261
281
|
homeWatchers.delete(mindName);
|
|
262
282
|
invalidateCache();
|
|
@@ -314,7 +334,7 @@ function scanPagesDir(dir, urlPrefix) {
|
|
|
314
334
|
}
|
|
315
335
|
for (const item of items) {
|
|
316
336
|
if (item.startsWith(".")) continue;
|
|
317
|
-
const fullPath =
|
|
337
|
+
const fullPath = resolve2(dir, item);
|
|
318
338
|
try {
|
|
319
339
|
const s = statSync(fullPath);
|
|
320
340
|
if (s.isFile() && item.endsWith(".html")) {
|
|
@@ -324,8 +344,8 @@ function scanPagesDir(dir, urlPrefix) {
|
|
|
324
344
|
url: `${urlPrefix}/${item}`
|
|
325
345
|
});
|
|
326
346
|
} else if (s.isDirectory()) {
|
|
327
|
-
const indexPath =
|
|
328
|
-
if (
|
|
347
|
+
const indexPath = resolve2(fullPath, "index.html");
|
|
348
|
+
if (existsSync2(indexPath)) {
|
|
329
349
|
const indexStat = statSync(indexPath);
|
|
330
350
|
pages.push({
|
|
331
351
|
file: join(item, "index.html"),
|
|
@@ -340,19 +360,19 @@ function scanPagesDir(dir, urlPrefix) {
|
|
|
340
360
|
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
341
361
|
return pages;
|
|
342
362
|
}
|
|
343
|
-
function buildSites() {
|
|
363
|
+
async function buildSites() {
|
|
344
364
|
const sites = [];
|
|
345
|
-
const systemPagesDir =
|
|
346
|
-
if (
|
|
365
|
+
const systemPagesDir = resolve2(voluteHome(), "shared", "pages");
|
|
366
|
+
if (existsSync2(systemPagesDir)) {
|
|
347
367
|
const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
|
|
348
368
|
if (systemPages.length > 0) {
|
|
349
369
|
sites.push({ name: "_system", label: "System", pages: systemPages });
|
|
350
370
|
}
|
|
351
371
|
}
|
|
352
|
-
const entries = readRegistry();
|
|
372
|
+
const entries = await readRegistry();
|
|
353
373
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
354
|
-
const pagesDir =
|
|
355
|
-
if (!
|
|
374
|
+
const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
|
|
375
|
+
if (!existsSync2(pagesDir)) continue;
|
|
356
376
|
const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
|
|
357
377
|
if (mindPages.length > 0) {
|
|
358
378
|
sites.push({ name: entry.name, label: entry.name, pages: mindPages });
|
|
@@ -360,12 +380,12 @@ function buildSites() {
|
|
|
360
380
|
}
|
|
361
381
|
return sites;
|
|
362
382
|
}
|
|
363
|
-
function buildRecentPages() {
|
|
364
|
-
const entries = readRegistry();
|
|
383
|
+
async function buildRecentPages() {
|
|
384
|
+
const entries = await readRegistry();
|
|
365
385
|
const pages = [];
|
|
366
386
|
for (const entry of entries) {
|
|
367
|
-
const pagesDir =
|
|
368
|
-
if (!
|
|
387
|
+
const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
|
|
388
|
+
if (!existsSync2(pagesDir)) continue;
|
|
369
389
|
let items;
|
|
370
390
|
try {
|
|
371
391
|
items = readdirSync(pagesDir);
|
|
@@ -374,7 +394,7 @@ function buildRecentPages() {
|
|
|
374
394
|
}
|
|
375
395
|
for (const item of items) {
|
|
376
396
|
if (item.startsWith(".")) continue;
|
|
377
|
-
const fullPath =
|
|
397
|
+
const fullPath = resolve2(pagesDir, item);
|
|
378
398
|
try {
|
|
379
399
|
const s = statSync(fullPath);
|
|
380
400
|
if (s.isFile() && item.endsWith(".html")) {
|
|
@@ -385,8 +405,8 @@ function buildRecentPages() {
|
|
|
385
405
|
url: `/pages/${entry.name}/${item}`
|
|
386
406
|
});
|
|
387
407
|
} else if (s.isDirectory()) {
|
|
388
|
-
const indexPath =
|
|
389
|
-
if (
|
|
408
|
+
const indexPath = resolve2(fullPath, "index.html");
|
|
409
|
+
if (existsSync2(indexPath)) {
|
|
390
410
|
const indexStat = statSync(indexPath);
|
|
391
411
|
pages.push({
|
|
392
412
|
mind: entry.name,
|
|
@@ -403,836 +423,72 @@ function buildRecentPages() {
|
|
|
403
423
|
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
404
424
|
return pages.slice(0, 10);
|
|
405
425
|
}
|
|
406
|
-
function getCachedSites() {
|
|
407
|
-
if (!sitesCache) sitesCache = buildSites();
|
|
426
|
+
async function getCachedSites() {
|
|
427
|
+
if (!sitesCache) sitesCache = await buildSites();
|
|
408
428
|
return sitesCache;
|
|
409
429
|
}
|
|
410
|
-
function getCachedRecentPages() {
|
|
411
|
-
if (!recentPagesCache) recentPagesCache = buildRecentPages();
|
|
430
|
+
async function getCachedRecentPages() {
|
|
431
|
+
if (!recentPagesCache) recentPagesCache = await buildRecentPages();
|
|
412
432
|
return recentPagesCache;
|
|
413
433
|
}
|
|
414
434
|
|
|
415
|
-
// src/
|
|
416
|
-
import {
|
|
417
|
-
import {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
435
|
+
// src/connectors/sdk.ts
|
|
436
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
437
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
438
|
+
function splitMessage(text, maxLength) {
|
|
439
|
+
const chunks = [];
|
|
440
|
+
while (text.length > maxLength) {
|
|
441
|
+
let splitAt = text.lastIndexOf("\n", maxLength);
|
|
442
|
+
if (splitAt < maxLength / 2) splitAt = maxLength;
|
|
443
|
+
chunks.push(text.slice(0, splitAt));
|
|
444
|
+
text = text.slice(splitAt).replace(/^\n/, "");
|
|
445
|
+
}
|
|
446
|
+
if (text) chunks.push(text);
|
|
447
|
+
return chunks;
|
|
448
|
+
}
|
|
449
|
+
function readChannelMap(mindName) {
|
|
450
|
+
const filePath = join2(stateDir(mindName), "channels.json");
|
|
451
|
+
if (!existsSync3(filePath)) return {};
|
|
431
452
|
try {
|
|
432
|
-
|
|
433
|
-
if (!url) return;
|
|
434
|
-
const payload = { ...event, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
435
|
-
fetch(url, {
|
|
436
|
-
method: "POST",
|
|
437
|
-
headers: getAuthHeaders(),
|
|
438
|
-
body: JSON.stringify(payload)
|
|
439
|
-
}).then((res) => {
|
|
440
|
-
if (!res.ok) {
|
|
441
|
-
slog.warn(`webhook ${event.event} returned HTTP ${res.status}`);
|
|
442
|
-
}
|
|
443
|
-
}).catch((err) => {
|
|
444
|
-
slog.warn(`webhook delivery failed for ${event.event}`, logger_default.errorData(err));
|
|
445
|
-
});
|
|
453
|
+
return JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
446
454
|
} catch (err) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
function initWebhook() {
|
|
451
|
-
const url = getWebhookUrl();
|
|
452
|
-
if (!url) return () => {
|
|
453
|
-
};
|
|
454
|
-
try {
|
|
455
|
-
const parsed = new URL(url);
|
|
456
|
-
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
457
|
-
slog.error(`VOLUTE_WEBHOOK_URL has unsupported protocol: ${parsed.protocol}`);
|
|
458
|
-
return () => {
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
} catch {
|
|
462
|
-
slog.error(`VOLUTE_WEBHOOK_URL is not a valid URL`);
|
|
463
|
-
return () => {
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
slog.info("webhook enabled");
|
|
467
|
-
return subscribe((event) => {
|
|
468
|
-
try {
|
|
469
|
-
fireWebhook({
|
|
470
|
-
event: event.type,
|
|
471
|
-
mind: event.mind,
|
|
472
|
-
data: { summary: event.summary, ...event.metadata },
|
|
473
|
-
timestamp: event.created_at
|
|
474
|
-
});
|
|
475
|
-
} catch (err) {
|
|
476
|
-
slog.error(`failed to fire webhook for ${event.type}`, logger_default.errorData(err));
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// src/lib/events/conversation-events.ts
|
|
482
|
-
var subscribers = /* @__PURE__ */ new Map();
|
|
483
|
-
function subscribe2(conversationId, callback) {
|
|
484
|
-
let set = subscribers.get(conversationId);
|
|
485
|
-
if (!set) {
|
|
486
|
-
set = /* @__PURE__ */ new Set();
|
|
487
|
-
subscribers.set(conversationId, set);
|
|
488
|
-
}
|
|
489
|
-
set.add(callback);
|
|
490
|
-
return () => {
|
|
491
|
-
set.delete(callback);
|
|
492
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
function publish2(conversationId, event) {
|
|
496
|
-
const set = subscribers.get(conversationId);
|
|
497
|
-
if (!set) return;
|
|
498
|
-
for (const cb of set) {
|
|
499
|
-
try {
|
|
500
|
-
cb(event);
|
|
501
|
-
} catch (err) {
|
|
502
|
-
console.error("[conversation-events] subscriber threw:", err);
|
|
503
|
-
set.delete(cb);
|
|
504
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// src/lib/events/conversations.ts
|
|
510
|
-
async function createConversation(mindName, channel, opts) {
|
|
511
|
-
const db = await getDb();
|
|
512
|
-
const id = randomUUID();
|
|
513
|
-
const type = opts?.type ?? "dm";
|
|
514
|
-
const name = opts?.name ?? null;
|
|
515
|
-
await db.transaction(async (tx) => {
|
|
516
|
-
await tx.insert(conversations).values({
|
|
517
|
-
id,
|
|
518
|
-
mind_name: mindName,
|
|
519
|
-
channel,
|
|
520
|
-
type,
|
|
521
|
-
name,
|
|
522
|
-
user_id: opts?.userId ?? null,
|
|
523
|
-
title: opts?.title ?? null
|
|
524
|
-
});
|
|
525
|
-
if (opts?.participantIds && opts.participantIds.length > 0) {
|
|
526
|
-
await tx.insert(conversationParticipants).values(
|
|
527
|
-
opts.participantIds.map((uid, i) => ({
|
|
528
|
-
conversation_id: id,
|
|
529
|
-
user_id: uid,
|
|
530
|
-
role: i === 0 ? "owner" : "member"
|
|
531
|
-
}))
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
fireWebhook({
|
|
536
|
-
event: "conversation_created",
|
|
537
|
-
mind: mindName ?? "",
|
|
538
|
-
data: { id, mindName, channel, type, name, title: opts?.title ?? null }
|
|
539
|
-
});
|
|
540
|
-
return {
|
|
541
|
-
id,
|
|
542
|
-
mind_name: mindName,
|
|
543
|
-
channel,
|
|
544
|
-
type,
|
|
545
|
-
name,
|
|
546
|
-
user_id: opts?.userId ?? null,
|
|
547
|
-
title: opts?.title ?? null,
|
|
548
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
549
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
async function getConversation(id) {
|
|
553
|
-
const db = await getDb();
|
|
554
|
-
const row = await db.select().from(conversations).where(eq2(conversations.id, id)).get();
|
|
555
|
-
return row ?? null;
|
|
556
|
-
}
|
|
557
|
-
async function addParticipant(conversationId, userId, role = "member") {
|
|
558
|
-
const db = await getDb();
|
|
559
|
-
await db.insert(conversationParticipants).values({
|
|
560
|
-
conversation_id: conversationId,
|
|
561
|
-
user_id: userId,
|
|
562
|
-
role
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
async function removeParticipant(conversationId, userId) {
|
|
566
|
-
const db = await getDb();
|
|
567
|
-
await db.delete(conversationParticipants).where(
|
|
568
|
-
and2(
|
|
569
|
-
eq2(conversationParticipants.conversation_id, conversationId),
|
|
570
|
-
eq2(conversationParticipants.user_id, userId)
|
|
571
|
-
)
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
async function getParticipants(conversationId) {
|
|
575
|
-
const db = await getDb();
|
|
576
|
-
const rows = await db.select({
|
|
577
|
-
userId: conversationParticipants.user_id,
|
|
578
|
-
username: users.username,
|
|
579
|
-
userType: users.user_type,
|
|
580
|
-
role: conversationParticipants.role,
|
|
581
|
-
displayName: users.display_name,
|
|
582
|
-
description: users.description,
|
|
583
|
-
avatar: users.avatar
|
|
584
|
-
}).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(eq2(conversationParticipants.conversation_id, conversationId)).all();
|
|
585
|
-
return rows;
|
|
586
|
-
}
|
|
587
|
-
async function isParticipant(conversationId, userId) {
|
|
588
|
-
const db = await getDb();
|
|
589
|
-
const row = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(
|
|
590
|
-
and2(
|
|
591
|
-
eq2(conversationParticipants.conversation_id, conversationId),
|
|
592
|
-
eq2(conversationParticipants.user_id, userId)
|
|
593
|
-
)
|
|
594
|
-
).get();
|
|
595
|
-
return row != null;
|
|
596
|
-
}
|
|
597
|
-
async function listConversationsForUser(userId) {
|
|
598
|
-
const db = await getDb();
|
|
599
|
-
const participantRows = await db.select({ conversation_id: conversationParticipants.conversation_id }).from(conversationParticipants).where(eq2(conversationParticipants.user_id, userId)).all();
|
|
600
|
-
if (participantRows.length === 0) return [];
|
|
601
|
-
const convIds = participantRows.map((r) => r.conversation_id);
|
|
602
|
-
return await db.select().from(conversations).where(inArray2(conversations.id, convIds)).orderBy(desc(conversations.updated_at)).all();
|
|
603
|
-
}
|
|
604
|
-
async function isParticipantOrOwner(conversationId, userId) {
|
|
605
|
-
if (await isParticipant(conversationId, userId)) return true;
|
|
606
|
-
const db = await getDb();
|
|
607
|
-
const row = await db.select().from(conversations).where(and2(eq2(conversations.id, conversationId), eq2(conversations.user_id, userId))).get();
|
|
608
|
-
return row != null;
|
|
609
|
-
}
|
|
610
|
-
async function deleteConversationForUser(id, userId) {
|
|
611
|
-
if (!await isParticipantOrOwner(id, userId)) return false;
|
|
612
|
-
await deleteConversation(id);
|
|
613
|
-
return true;
|
|
614
|
-
}
|
|
615
|
-
async function addMessage(conversationId, role, senderName, content) {
|
|
616
|
-
const db = await getDb();
|
|
617
|
-
const serialized = JSON.stringify(content);
|
|
618
|
-
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 });
|
|
619
|
-
await db.update(conversations).set({ updated_at: sql`datetime('now')` }).where(eq2(conversations.id, conversationId));
|
|
620
|
-
if (role === "user") {
|
|
621
|
-
const firstText = content.find((b) => b.type === "text");
|
|
622
|
-
const title = firstText ? firstText.text.slice(0, 80) : "";
|
|
623
|
-
if (title) {
|
|
624
|
-
await db.update(conversations).set({ title }).where(and2(eq2(conversations.id, conversationId), isNull(conversations.title)));
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
const msg = {
|
|
628
|
-
id: result.id,
|
|
629
|
-
conversation_id: conversationId,
|
|
630
|
-
role,
|
|
631
|
-
sender_name: senderName,
|
|
632
|
-
content,
|
|
633
|
-
created_at: result.created_at
|
|
634
|
-
};
|
|
635
|
-
publish2(conversationId, {
|
|
636
|
-
type: "message",
|
|
637
|
-
id: msg.id,
|
|
638
|
-
role: msg.role,
|
|
639
|
-
senderName: msg.sender_name,
|
|
640
|
-
content: msg.content,
|
|
641
|
-
createdAt: msg.created_at
|
|
642
|
-
});
|
|
643
|
-
const conv = await db.select({ mind_name: conversations.mind_name }).from(conversations).where(eq2(conversations.id, conversationId)).get();
|
|
644
|
-
fireWebhook({
|
|
645
|
-
event: "message_created",
|
|
646
|
-
mind: conv?.mind_name ?? "",
|
|
647
|
-
data: {
|
|
648
|
-
conversationId,
|
|
649
|
-
messageId: result.id,
|
|
650
|
-
role,
|
|
651
|
-
senderName,
|
|
652
|
-
content: content.filter((b) => b.type !== "image"),
|
|
653
|
-
createdAt: result.created_at
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
return msg;
|
|
657
|
-
}
|
|
658
|
-
async function getMessages(conversationId) {
|
|
659
|
-
const db = await getDb();
|
|
660
|
-
const rows = await db.select().from(messages).where(eq2(messages.conversation_id, conversationId)).orderBy(messages.created_at).all();
|
|
661
|
-
return rows.map(parseMessageRow);
|
|
662
|
-
}
|
|
663
|
-
async function getMessagesPaginated(conversationId, opts) {
|
|
664
|
-
const db = await getDb();
|
|
665
|
-
const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 100);
|
|
666
|
-
const conditions = [eq2(messages.conversation_id, conversationId)];
|
|
667
|
-
if (opts?.before != null) {
|
|
668
|
-
conditions.push(lt(messages.id, opts.before));
|
|
669
|
-
}
|
|
670
|
-
const rows = await db.select().from(messages).where(and2(...conditions)).orderBy(desc(messages.id)).limit(limit + 1).all();
|
|
671
|
-
const hasMore = rows.length > limit;
|
|
672
|
-
const page = rows.slice(0, limit).reverse();
|
|
673
|
-
return {
|
|
674
|
-
messages: page.map(parseMessageRow),
|
|
675
|
-
hasMore
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
function parseMessageRow(row) {
|
|
679
|
-
let content;
|
|
680
|
-
try {
|
|
681
|
-
const parsed = JSON.parse(row.content);
|
|
682
|
-
content = Array.isArray(parsed) ? parsed : [{ type: "text", text: row.content }];
|
|
683
|
-
} catch {
|
|
684
|
-
content = [{ type: "text", text: row.content }];
|
|
685
|
-
}
|
|
686
|
-
return { ...row, role: row.role, content };
|
|
687
|
-
}
|
|
688
|
-
async function listConversationsWithParticipants(userId) {
|
|
689
|
-
const convs = await listConversationsForUser(userId);
|
|
690
|
-
if (convs.length === 0) return [];
|
|
691
|
-
const db = await getDb();
|
|
692
|
-
const convIds = convs.map((c) => c.id);
|
|
693
|
-
const rows = await db.select({
|
|
694
|
-
conversationId: conversationParticipants.conversation_id,
|
|
695
|
-
userId: users.id,
|
|
696
|
-
username: users.username,
|
|
697
|
-
userType: users.user_type,
|
|
698
|
-
role: conversationParticipants.role,
|
|
699
|
-
displayName: users.display_name,
|
|
700
|
-
description: users.description,
|
|
701
|
-
avatar: users.avatar
|
|
702
|
-
}).from(conversationParticipants).innerJoin(users, eq2(conversationParticipants.user_id, users.id)).where(inArray2(conversationParticipants.conversation_id, convIds));
|
|
703
|
-
const byConv = /* @__PURE__ */ new Map();
|
|
704
|
-
for (const r of rows) {
|
|
705
|
-
let arr = byConv.get(r.conversationId);
|
|
706
|
-
if (!arr) {
|
|
707
|
-
arr = [];
|
|
708
|
-
byConv.set(r.conversationId, arr);
|
|
709
|
-
}
|
|
710
|
-
arr.push({
|
|
711
|
-
userId: r.userId,
|
|
712
|
-
username: r.username,
|
|
713
|
-
userType: r.userType,
|
|
714
|
-
role: r.role,
|
|
715
|
-
displayName: r.displayName,
|
|
716
|
-
description: r.description,
|
|
717
|
-
avatar: r.avatar
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
const lastMsgIds = await db.select({
|
|
721
|
-
conversationId: messages.conversation_id,
|
|
722
|
-
maxId: sql`MAX(${messages.id})`
|
|
723
|
-
}).from(messages).where(inArray2(messages.conversation_id, convIds)).groupBy(messages.conversation_id);
|
|
724
|
-
const byLastMsg = /* @__PURE__ */ new Map();
|
|
725
|
-
if (lastMsgIds.length > 0) {
|
|
726
|
-
const msgRows = await db.select().from(messages).where(
|
|
727
|
-
inArray2(
|
|
728
|
-
messages.id,
|
|
729
|
-
lastMsgIds.map((r) => r.maxId)
|
|
730
|
-
)
|
|
731
|
-
);
|
|
732
|
-
for (const m of msgRows) {
|
|
733
|
-
let text = "";
|
|
734
|
-
try {
|
|
735
|
-
const parsed = JSON.parse(m.content);
|
|
736
|
-
const blocks = Array.isArray(parsed) ? parsed : [];
|
|
737
|
-
const textBlock = blocks.find((b) => b.type === "text");
|
|
738
|
-
if (textBlock && "text" in textBlock) text = textBlock.text;
|
|
739
|
-
} catch {
|
|
740
|
-
text = m.content;
|
|
741
|
-
}
|
|
742
|
-
byLastMsg.set(m.conversation_id, {
|
|
743
|
-
role: m.role,
|
|
744
|
-
senderName: m.sender_name,
|
|
745
|
-
text,
|
|
746
|
-
createdAt: m.created_at
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
return convs.map((c) => ({
|
|
751
|
-
...c,
|
|
752
|
-
participants: byConv.get(c.id) ?? [],
|
|
753
|
-
lastMessage: byLastMsg.get(c.id)
|
|
754
|
-
}));
|
|
755
|
-
}
|
|
756
|
-
async function findDMConversation(mindName, participantIds) {
|
|
757
|
-
const db = await getDb();
|
|
758
|
-
const mindConvs = await db.select({ id: conversations.id }).from(conversations).where(and2(eq2(conversations.mind_name, mindName), eq2(conversations.type, "dm"))).all();
|
|
759
|
-
for (const conv of mindConvs) {
|
|
760
|
-
const rows = await db.select({ user_id: conversationParticipants.user_id }).from(conversationParticipants).where(eq2(conversationParticipants.conversation_id, conv.id)).all();
|
|
761
|
-
if (rows.length !== 2) continue;
|
|
762
|
-
const ids = new Set(rows.map((r) => r.user_id));
|
|
763
|
-
if (ids.has(participantIds[0]) && ids.has(participantIds[1])) {
|
|
764
|
-
return conv.id;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return null;
|
|
768
|
-
}
|
|
769
|
-
async function deleteConversation(id) {
|
|
770
|
-
const db = await getDb();
|
|
771
|
-
await db.delete(conversations).where(eq2(conversations.id, id));
|
|
772
|
-
}
|
|
773
|
-
async function createChannel(name, creatorId) {
|
|
774
|
-
const participantIds = creatorId ? [creatorId] : [];
|
|
775
|
-
return createConversation(null, "volute", {
|
|
776
|
-
type: "channel",
|
|
777
|
-
name,
|
|
778
|
-
title: name,
|
|
779
|
-
participantIds
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
async function getChannelByName(name) {
|
|
783
|
-
const db = await getDb();
|
|
784
|
-
const row = await db.select().from(conversations).where(and2(eq2(conversations.name, name), eq2(conversations.type, "channel"))).get();
|
|
785
|
-
return row ?? null;
|
|
786
|
-
}
|
|
787
|
-
async function listChannels() {
|
|
788
|
-
const db = await getDb();
|
|
789
|
-
return await db.select().from(conversations).where(eq2(conversations.type, "channel")).orderBy(conversations.name).all();
|
|
790
|
-
}
|
|
791
|
-
async function joinChannel(conversationId, userId) {
|
|
792
|
-
if (await isParticipant(conversationId, userId)) return;
|
|
793
|
-
await addParticipant(conversationId, userId);
|
|
794
|
-
}
|
|
795
|
-
async function leaveChannel(conversationId, userId) {
|
|
796
|
-
await removeParticipant(conversationId, userId);
|
|
797
|
-
}
|
|
798
|
-
async function getUnreadCounts(userId, conversationIds) {
|
|
799
|
-
if (conversationIds.length === 0) return {};
|
|
800
|
-
const db = await getDb();
|
|
801
|
-
const rows = await db.select({
|
|
802
|
-
conversationId: messages.conversation_id,
|
|
803
|
-
count: sql`COUNT(*)`
|
|
804
|
-
}).from(messages).leftJoin(
|
|
805
|
-
conversationReads,
|
|
806
|
-
and2(
|
|
807
|
-
eq2(conversationReads.conversation_id, messages.conversation_id),
|
|
808
|
-
eq2(conversationReads.user_id, userId)
|
|
809
|
-
)
|
|
810
|
-
).where(
|
|
811
|
-
and2(
|
|
812
|
-
inArray2(messages.conversation_id, conversationIds),
|
|
813
|
-
sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`
|
|
814
|
-
)
|
|
815
|
-
).groupBy(messages.conversation_id);
|
|
816
|
-
const result = {};
|
|
817
|
-
for (const row of rows) {
|
|
818
|
-
result[row.conversationId] = row.count;
|
|
819
|
-
}
|
|
820
|
-
return result;
|
|
821
|
-
}
|
|
822
|
-
async function markConversationRead(userId, conversationId) {
|
|
823
|
-
const db = await getDb();
|
|
824
|
-
const maxRow = await db.select({ maxId: sql`MAX(${messages.id})` }).from(messages).where(eq2(messages.conversation_id, conversationId)).get();
|
|
825
|
-
const maxId = maxRow?.maxId ?? 0;
|
|
826
|
-
if (maxId === 0) return;
|
|
827
|
-
await db.insert(conversationReads).values({ user_id: userId, conversation_id: conversationId, last_read_message_id: maxId }).onConflictDoUpdate({
|
|
828
|
-
target: [conversationReads.user_id, conversationReads.conversation_id],
|
|
829
|
-
set: { last_read_message_id: maxId }
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// src/lib/system-channel.ts
|
|
834
|
-
var SYSTEM_CHANNEL_NAME = "system";
|
|
835
|
-
var cachedChannelId = null;
|
|
836
|
-
async function ensureSystemChannel() {
|
|
837
|
-
if (cachedChannelId) return cachedChannelId;
|
|
838
|
-
const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
|
|
839
|
-
if (existing) {
|
|
840
|
-
cachedChannelId = existing.id;
|
|
841
|
-
return existing.id;
|
|
842
|
-
}
|
|
843
|
-
const conv = await createChannel(SYSTEM_CHANNEL_NAME);
|
|
844
|
-
cachedChannelId = conv.id;
|
|
845
|
-
logger_default.info("created #system channel");
|
|
846
|
-
return conv.id;
|
|
847
|
-
}
|
|
848
|
-
async function joinSystemChannel(userId) {
|
|
849
|
-
const channelId = await ensureSystemChannel();
|
|
850
|
-
await joinChannel(channelId, userId);
|
|
851
|
-
}
|
|
852
|
-
async function joinSystemChannelForMind(mindName) {
|
|
853
|
-
const user = await getOrCreateMindUser(mindName);
|
|
854
|
-
await joinSystemChannel(user.id);
|
|
855
|
-
}
|
|
856
|
-
async function announceToSystem(text) {
|
|
857
|
-
const channelId = await ensureSystemChannel();
|
|
858
|
-
await addMessage(channelId, "system", "system", [{ type: "text", text }]);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// src/lib/daemon/connector-manager.ts
|
|
862
|
-
import { spawn } from "child_process";
|
|
863
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
864
|
-
import { dirname, resolve as resolve3 } from "path";
|
|
865
|
-
|
|
866
|
-
// src/lib/connector-defs.ts
|
|
867
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
868
|
-
import { resolve as resolve2 } from "path";
|
|
869
|
-
var BUILTIN_DEFS = {
|
|
870
|
-
discord: {
|
|
871
|
-
displayName: "Discord",
|
|
872
|
-
description: "Connect to Discord as a bot",
|
|
873
|
-
envVars: [
|
|
874
|
-
{
|
|
875
|
-
name: "DISCORD_TOKEN",
|
|
876
|
-
required: true,
|
|
877
|
-
description: "Discord bot token",
|
|
878
|
-
scope: "mind"
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
name: "DISCORD_GUILD_ID",
|
|
882
|
-
required: false,
|
|
883
|
-
description: "Discord server ID (optional, for slash commands)",
|
|
884
|
-
scope: "mind"
|
|
885
|
-
}
|
|
886
|
-
]
|
|
887
|
-
},
|
|
888
|
-
slack: {
|
|
889
|
-
displayName: "Slack",
|
|
890
|
-
description: "Connect to Slack via Socket Mode",
|
|
891
|
-
envVars: [
|
|
892
|
-
{
|
|
893
|
-
name: "SLACK_BOT_TOKEN",
|
|
894
|
-
required: true,
|
|
895
|
-
description: "Slack bot token (xoxb-...)",
|
|
896
|
-
scope: "mind"
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
name: "SLACK_APP_TOKEN",
|
|
900
|
-
required: true,
|
|
901
|
-
description: "Slack app-level token (xapp-...) for Socket Mode",
|
|
902
|
-
scope: "mind"
|
|
903
|
-
}
|
|
904
|
-
]
|
|
905
|
-
},
|
|
906
|
-
telegram: {
|
|
907
|
-
displayName: "Telegram",
|
|
908
|
-
description: "Connect to Telegram via long polling",
|
|
909
|
-
envVars: [
|
|
910
|
-
{
|
|
911
|
-
name: "TELEGRAM_BOT_TOKEN",
|
|
912
|
-
required: true,
|
|
913
|
-
description: "Telegram bot token from BotFather",
|
|
914
|
-
scope: "mind"
|
|
915
|
-
}
|
|
916
|
-
]
|
|
917
|
-
}
|
|
918
|
-
};
|
|
919
|
-
function getConnectorDef(type, connectorDir) {
|
|
920
|
-
if (BUILTIN_DEFS[type]) return BUILTIN_DEFS[type];
|
|
921
|
-
if (connectorDir) {
|
|
922
|
-
const jsonPath = resolve2(connectorDir, "connector.json");
|
|
923
|
-
if (existsSync2(jsonPath)) {
|
|
924
|
-
try {
|
|
925
|
-
return JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
926
|
-
} catch (err) {
|
|
927
|
-
console.warn(`Failed to parse ${jsonPath}: ${err}`);
|
|
928
|
-
return null;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
455
|
+
console.error(`[sdk] failed to parse ${filePath}:`, err);
|
|
456
|
+
return {};
|
|
931
457
|
}
|
|
932
|
-
return null;
|
|
933
458
|
}
|
|
934
|
-
function
|
|
935
|
-
|
|
459
|
+
function writeChannelEntry(mindName, slug, entry) {
|
|
460
|
+
const dir = stateDir(mindName);
|
|
461
|
+
mkdirSync2(dir, { recursive: true });
|
|
462
|
+
const filePath = join2(dir, "channels.json");
|
|
463
|
+
const map = readChannelMap(mindName);
|
|
464
|
+
map[slug] = entry;
|
|
465
|
+
writeFileSync2(filePath, JSON.stringify(map, null, 2) + "\n");
|
|
936
466
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
942
|
-
for (let i = 0; i < 5; i++) {
|
|
943
|
-
const candidate = resolve3(searchDir, ...segments);
|
|
944
|
-
if (existsSync3(candidate)) return candidate;
|
|
945
|
-
searchDir = dirname(searchDir);
|
|
946
|
-
}
|
|
947
|
-
return null;
|
|
948
|
-
}
|
|
949
|
-
var ConnectorManager = class {
|
|
950
|
-
connectors = /* @__PURE__ */ new Map();
|
|
951
|
-
stopping = /* @__PURE__ */ new Set();
|
|
952
|
-
// "mind:type" keys currently being explicitly stopped
|
|
953
|
-
shuttingDown = false;
|
|
954
|
-
restartTracker = new RestartTracker();
|
|
955
|
-
async startConnectors(mindName, mindDir2, mindPort, daemonPort) {
|
|
956
|
-
const config = readVoluteConfig(mindDir2) ?? {};
|
|
957
|
-
const types = config.connectors ?? [];
|
|
958
|
-
await Promise.all(
|
|
959
|
-
types.map(
|
|
960
|
-
(type) => this.startConnector(mindName, mindDir2, mindPort, type, daemonPort).catch((err) => {
|
|
961
|
-
clog.warn(`failed to start connector ${type} for ${mindName}`, logger_default.errorData(err));
|
|
962
|
-
})
|
|
963
|
-
)
|
|
964
|
-
);
|
|
467
|
+
function resolveChannelId(mindName, slug) {
|
|
468
|
+
const map = readChannelMap(mindName);
|
|
469
|
+
if (map[slug]) {
|
|
470
|
+
return map[slug].platformId;
|
|
965
471
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const userConnectorDir = resolve3(voluteHome(), "connectors", type);
|
|
969
|
-
const connectorDir = existsSync3(mindConnectorDir) ? mindConnectorDir : existsSync3(userConnectorDir) ? userConnectorDir : void 0;
|
|
970
|
-
const def = getConnectorDef(type, connectorDir);
|
|
971
|
-
if (!def) return null;
|
|
972
|
-
const env = loadMergedEnv(mindName);
|
|
973
|
-
const missing = checkMissingEnvVars(def, env);
|
|
974
|
-
if (missing.length === 0) return null;
|
|
975
|
-
return {
|
|
976
|
-
missing: missing.map((v) => ({ name: v.name, description: v.description })),
|
|
977
|
-
connectorName: def.displayName
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
async startConnector(mindName, mindDir2, mindPort, type, daemonPort) {
|
|
981
|
-
const existing = this.connectors.get(mindName)?.get(type);
|
|
982
|
-
if (existing) {
|
|
983
|
-
await new Promise((res) => {
|
|
984
|
-
existing.child.on("exit", () => res());
|
|
985
|
-
try {
|
|
986
|
-
if (existing.child.pid) {
|
|
987
|
-
process.kill(-existing.child.pid, "SIGTERM");
|
|
988
|
-
} else {
|
|
989
|
-
existing.child.kill("SIGTERM");
|
|
990
|
-
}
|
|
991
|
-
} catch {
|
|
992
|
-
res();
|
|
993
|
-
}
|
|
994
|
-
setTimeout(() => {
|
|
995
|
-
try {
|
|
996
|
-
if (existing.child.pid) {
|
|
997
|
-
process.kill(-existing.child.pid, "SIGKILL");
|
|
998
|
-
} else {
|
|
999
|
-
existing.child.kill("SIGKILL");
|
|
1000
|
-
}
|
|
1001
|
-
} catch {
|
|
1002
|
-
}
|
|
1003
|
-
res();
|
|
1004
|
-
}, 3e3);
|
|
1005
|
-
});
|
|
1006
|
-
this.connectors.get(mindName)?.delete(type);
|
|
1007
|
-
}
|
|
1008
|
-
this.killOrphanConnector(mindName, type);
|
|
1009
|
-
const mindConnector = resolve3(mindDir2, "connectors", type, "index.ts");
|
|
1010
|
-
const userConnector = resolve3(voluteHome(), "connectors", type, "index.ts");
|
|
1011
|
-
const builtinConnector = this.resolveBuiltinConnector(type);
|
|
1012
|
-
let connectorScript;
|
|
1013
|
-
let runtime;
|
|
1014
|
-
if (existsSync3(mindConnector)) {
|
|
1015
|
-
connectorScript = mindConnector;
|
|
1016
|
-
runtime = resolve3(mindDir2, "node_modules", ".bin", "tsx");
|
|
1017
|
-
} else if (existsSync3(userConnector)) {
|
|
1018
|
-
connectorScript = userConnector;
|
|
1019
|
-
runtime = this.resolveVoluteTsx();
|
|
1020
|
-
} else if (builtinConnector) {
|
|
1021
|
-
connectorScript = builtinConnector;
|
|
1022
|
-
runtime = process.execPath;
|
|
1023
|
-
} else {
|
|
1024
|
-
throw new Error(`No connector code found for type: ${type}`);
|
|
1025
|
-
}
|
|
1026
|
-
const mindStateDir = stateDir(mindName);
|
|
1027
|
-
const logsDir = resolve3(mindStateDir, "logs");
|
|
1028
|
-
mkdirSync(logsDir, { recursive: true });
|
|
1029
|
-
if (isIsolationEnabled()) {
|
|
1030
|
-
try {
|
|
1031
|
-
const [base] = mindName.split("@", 2);
|
|
1032
|
-
chownMindDir(mindStateDir, base);
|
|
1033
|
-
} catch (err) {
|
|
1034
|
-
throw new Error(
|
|
1035
|
-
`Cannot start connector ${type} for ${mindName}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
const logStream = new RotatingLog(resolve3(logsDir, `${type}.log`));
|
|
1040
|
-
const mindEnv = loadMergedEnv(mindName);
|
|
1041
|
-
const prefix = `${type.toUpperCase()}_`;
|
|
1042
|
-
const connectorEnv = Object.fromEntries(
|
|
1043
|
-
Object.entries(mindEnv).filter(([k]) => k.startsWith(prefix))
|
|
1044
|
-
);
|
|
1045
|
-
const spawnOpts = {
|
|
1046
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1047
|
-
detached: true,
|
|
1048
|
-
env: {
|
|
1049
|
-
...process.env,
|
|
1050
|
-
VOLUTE_MIND_PORT: String(mindPort),
|
|
1051
|
-
VOLUTE_MIND_NAME: mindName,
|
|
1052
|
-
VOLUTE_MIND_DIR: mindDir2,
|
|
1053
|
-
...daemonPort ? {
|
|
1054
|
-
VOLUTE_DAEMON_URL: `http://${daemonLoopback()}:${daemonPort}`,
|
|
1055
|
-
VOLUTE_DAEMON_TOKEN: getMindToken(mindName) ?? void 0
|
|
1056
|
-
} : {},
|
|
1057
|
-
...connectorEnv
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
let spawnCmd;
|
|
1061
|
-
let spawnArgs;
|
|
1062
|
-
if (isIsolationEnabled()) {
|
|
1063
|
-
[spawnCmd, spawnArgs] = wrapForIsolation(runtime, [connectorScript], mindName);
|
|
1064
|
-
} else if (isSandboxEnabled()) {
|
|
1065
|
-
[spawnCmd, spawnArgs] = await wrapForSandbox(runtime, [connectorScript], mindDir2, mindName, [
|
|
1066
|
-
mindDir2,
|
|
1067
|
-
mindStateDir
|
|
1068
|
-
]);
|
|
1069
|
-
} else {
|
|
1070
|
-
spawnCmd = runtime;
|
|
1071
|
-
spawnArgs = [connectorScript];
|
|
1072
|
-
}
|
|
1073
|
-
const child = spawn(spawnCmd, spawnArgs, spawnOpts);
|
|
1074
|
-
let lastStderr = "";
|
|
1075
|
-
child.stdout?.pipe(logStream);
|
|
1076
|
-
child.stderr?.on("data", (chunk) => {
|
|
1077
|
-
logStream.write(chunk);
|
|
1078
|
-
lastStderr = chunk.toString().trim();
|
|
1079
|
-
});
|
|
1080
|
-
if (child.pid) {
|
|
1081
|
-
this.saveConnectorPid(mindName, type, child.pid);
|
|
1082
|
-
}
|
|
1083
|
-
if (!this.connectors.has(mindName)) {
|
|
1084
|
-
this.connectors.set(mindName, /* @__PURE__ */ new Map());
|
|
1085
|
-
}
|
|
1086
|
-
this.connectors.get(mindName).set(type, { child, type });
|
|
1087
|
-
const stopKey = `${mindName}:${type}`;
|
|
1088
|
-
this.restartTracker.reset(stopKey);
|
|
1089
|
-
child.on("exit", (code) => {
|
|
1090
|
-
const mindMap = this.connectors.get(mindName);
|
|
1091
|
-
if (mindMap?.get(type)?.child === child) {
|
|
1092
|
-
mindMap.delete(type);
|
|
1093
|
-
}
|
|
1094
|
-
if (this.shuttingDown) return;
|
|
1095
|
-
if (this.stopping.has(stopKey)) return;
|
|
1096
|
-
clog.error(`connector ${type} for ${mindName} exited with code ${code}`);
|
|
1097
|
-
if (lastStderr) clog.warn(`connector ${type} last output: ${lastStderr}`);
|
|
1098
|
-
const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(stopKey);
|
|
1099
|
-
if (!shouldRestart) {
|
|
1100
|
-
clog.error(`connector ${type} for ${mindName} crashed ${attempt} times \u2014 giving up`);
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
clog.info(
|
|
1104
|
-
`restarting connector ${type} for ${mindName} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, in ${delay}ms`
|
|
1105
|
-
);
|
|
1106
|
-
setTimeout(() => {
|
|
1107
|
-
if (this.shuttingDown || this.stopping.has(stopKey)) return;
|
|
1108
|
-
this.startConnector(mindName, mindDir2, mindPort, type, daemonPort).catch((err) => {
|
|
1109
|
-
clog.error(`failed to restart connector ${type} for ${mindName}`, logger_default.errorData(err));
|
|
1110
|
-
});
|
|
1111
|
-
}, delay);
|
|
1112
|
-
});
|
|
1113
|
-
clog.info(`started connector ${type} for ${mindName}`);
|
|
1114
|
-
}
|
|
1115
|
-
async stopConnector(mindName, type) {
|
|
1116
|
-
const mindMap = this.connectors.get(mindName);
|
|
1117
|
-
if (!mindMap) return;
|
|
1118
|
-
const tracked = mindMap.get(type);
|
|
1119
|
-
if (!tracked) return;
|
|
1120
|
-
const stopKey = `${mindName}:${type}`;
|
|
1121
|
-
this.stopping.add(stopKey);
|
|
1122
|
-
mindMap.delete(type);
|
|
1123
|
-
await new Promise((resolve9) => {
|
|
1124
|
-
tracked.child.on("exit", () => resolve9());
|
|
1125
|
-
try {
|
|
1126
|
-
process.kill(-tracked.child.pid, "SIGTERM");
|
|
1127
|
-
} catch {
|
|
1128
|
-
resolve9();
|
|
1129
|
-
}
|
|
1130
|
-
setTimeout(() => {
|
|
1131
|
-
try {
|
|
1132
|
-
process.kill(-tracked.child.pid, "SIGKILL");
|
|
1133
|
-
} catch {
|
|
1134
|
-
}
|
|
1135
|
-
resolve9();
|
|
1136
|
-
}, 5e3);
|
|
1137
|
-
});
|
|
1138
|
-
this.stopping.delete(stopKey);
|
|
1139
|
-
this.restartTracker.reset(stopKey);
|
|
1140
|
-
try {
|
|
1141
|
-
this.removeConnectorPid(mindName, type);
|
|
1142
|
-
} catch (err) {
|
|
1143
|
-
clog.warn(`failed to remove PID file for ${type}/${mindName}`, logger_default.errorData(err));
|
|
1144
|
-
}
|
|
1145
|
-
clog.info(`stopped connector ${type} for ${mindName}`);
|
|
1146
|
-
}
|
|
1147
|
-
async stopConnectors(mindName) {
|
|
1148
|
-
const mindMap = this.connectors.get(mindName);
|
|
1149
|
-
if (!mindMap) return;
|
|
1150
|
-
const types = [...mindMap.keys()];
|
|
1151
|
-
await Promise.all(types.map((type) => this.stopConnector(mindName, type)));
|
|
1152
|
-
this.connectors.delete(mindName);
|
|
1153
|
-
}
|
|
1154
|
-
async stopAll() {
|
|
1155
|
-
this.shuttingDown = true;
|
|
1156
|
-
const minds = [...this.connectors.keys()];
|
|
1157
|
-
await Promise.all(minds.map((name) => this.stopConnectors(name)));
|
|
1158
|
-
}
|
|
1159
|
-
getConnectorStatus(mindName) {
|
|
1160
|
-
const mindMap = this.connectors.get(mindName);
|
|
1161
|
-
if (!mindMap) return [];
|
|
1162
|
-
return [...mindMap.entries()].map(([type, tracked]) => ({
|
|
1163
|
-
type,
|
|
1164
|
-
running: !tracked.child.killed
|
|
1165
|
-
}));
|
|
1166
|
-
}
|
|
1167
|
-
connectorPidPath(mindName, type) {
|
|
1168
|
-
return resolve3(stateDir(mindName), "connectors", `${type}.pid`);
|
|
1169
|
-
}
|
|
1170
|
-
saveConnectorPid(mindName, type, pid) {
|
|
1171
|
-
const pidPath = this.connectorPidPath(mindName, type);
|
|
1172
|
-
mkdirSync(dirname(pidPath), { recursive: true });
|
|
1173
|
-
writeFileSync(pidPath, String(pid));
|
|
1174
|
-
}
|
|
1175
|
-
removeConnectorPid(mindName, type) {
|
|
1176
|
-
try {
|
|
1177
|
-
unlinkSync(this.connectorPidPath(mindName, type));
|
|
1178
|
-
} catch {
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
killOrphanConnector(mindName, type) {
|
|
1182
|
-
const pidPath = this.connectorPidPath(mindName, type);
|
|
1183
|
-
if (!existsSync3(pidPath)) return;
|
|
1184
|
-
try {
|
|
1185
|
-
const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
1186
|
-
if (pid > 0) {
|
|
1187
|
-
try {
|
|
1188
|
-
process.kill(-pid, "SIGTERM");
|
|
1189
|
-
} catch {
|
|
1190
|
-
process.kill(pid, "SIGTERM");
|
|
1191
|
-
}
|
|
1192
|
-
clog.warn(`killed orphan connector ${type} (pid ${pid})`);
|
|
1193
|
-
}
|
|
1194
|
-
} catch {
|
|
1195
|
-
}
|
|
1196
|
-
try {
|
|
1197
|
-
unlinkSync(pidPath);
|
|
1198
|
-
} catch {
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
resolveBuiltinConnector(type) {
|
|
1202
|
-
return searchUpwards("connectors", `${type}.js`);
|
|
1203
|
-
}
|
|
1204
|
-
resolveVoluteTsx() {
|
|
1205
|
-
return searchUpwards("node_modules", ".bin", "tsx") ?? "tsx";
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
var instance = null;
|
|
1209
|
-
function initConnectorManager() {
|
|
1210
|
-
if (instance) throw new Error("ConnectorManager already initialized");
|
|
1211
|
-
instance = new ConnectorManager();
|
|
1212
|
-
return instance;
|
|
1213
|
-
}
|
|
1214
|
-
function getConnectorManager() {
|
|
1215
|
-
if (!instance)
|
|
1216
|
-
throw new Error("ConnectorManager not initialized \u2014 call initConnectorManager() first");
|
|
1217
|
-
return instance;
|
|
472
|
+
const colonIndex = slug.indexOf(":");
|
|
473
|
+
return colonIndex >= 0 ? slug.slice(colonIndex + 1) : slug;
|
|
1218
474
|
}
|
|
1219
475
|
|
|
1220
476
|
// src/lib/events/mind-events.ts
|
|
1221
|
-
var
|
|
1222
|
-
function
|
|
1223
|
-
let set =
|
|
477
|
+
var subscribers = /* @__PURE__ */ new Map();
|
|
478
|
+
function subscribe2(mind, callback) {
|
|
479
|
+
let set = subscribers.get(mind);
|
|
1224
480
|
if (!set) {
|
|
1225
481
|
set = /* @__PURE__ */ new Set();
|
|
1226
|
-
|
|
482
|
+
subscribers.set(mind, set);
|
|
1227
483
|
}
|
|
1228
484
|
set.add(callback);
|
|
1229
485
|
return () => {
|
|
1230
486
|
set.delete(callback);
|
|
1231
|
-
if (set.size === 0)
|
|
487
|
+
if (set.size === 0) subscribers.delete(mind);
|
|
1232
488
|
};
|
|
1233
489
|
}
|
|
1234
490
|
function publish3(mind, event) {
|
|
1235
|
-
const set =
|
|
491
|
+
const set = subscribers.get(mind);
|
|
1236
492
|
if (!set) return;
|
|
1237
493
|
for (const cb of set) {
|
|
1238
494
|
try {
|
|
@@ -1240,7 +496,7 @@ function publish3(mind, event) {
|
|
|
1240
496
|
} catch (err) {
|
|
1241
497
|
console.error("[mind-events] subscriber threw:", err);
|
|
1242
498
|
set.delete(cb);
|
|
1243
|
-
if (set.size === 0)
|
|
499
|
+
if (set.size === 0) subscribers.delete(mind);
|
|
1244
500
|
}
|
|
1245
501
|
}
|
|
1246
502
|
}
|
|
@@ -1248,7 +504,7 @@ function publish3(mind, event) {
|
|
|
1248
504
|
// src/lib/delivery/delivery-manager.ts
|
|
1249
505
|
import { readFile, realpath } from "fs/promises";
|
|
1250
506
|
import { extname, resolve as resolve5 } from "path";
|
|
1251
|
-
import { and as
|
|
507
|
+
import { and as and2, eq as eq2, sql } from "drizzle-orm";
|
|
1252
508
|
|
|
1253
509
|
// src/lib/typing.ts
|
|
1254
510
|
var DEFAULT_TTL_MS = 1e4;
|
|
@@ -1308,7 +564,7 @@ var TypingMap = class {
|
|
|
1308
564
|
dispose() {
|
|
1309
565
|
clearInterval(this.sweepTimer);
|
|
1310
566
|
this.channels.clear();
|
|
1311
|
-
if (
|
|
567
|
+
if (instance === this) instance = void 0;
|
|
1312
568
|
}
|
|
1313
569
|
sweep() {
|
|
1314
570
|
const now = Date.now();
|
|
@@ -1324,12 +580,12 @@ var TypingMap = class {
|
|
|
1324
580
|
}
|
|
1325
581
|
}
|
|
1326
582
|
};
|
|
1327
|
-
var
|
|
583
|
+
var instance;
|
|
1328
584
|
function getTypingMap() {
|
|
1329
|
-
if (!
|
|
1330
|
-
|
|
585
|
+
if (!instance) {
|
|
586
|
+
instance = new TypingMap();
|
|
1331
587
|
}
|
|
1332
|
-
return
|
|
588
|
+
return instance;
|
|
1333
589
|
}
|
|
1334
590
|
function publishTypingForChannels(channels, map) {
|
|
1335
591
|
for (const channel of channels) {
|
|
@@ -1549,7 +805,7 @@ var DeliveryManager = class {
|
|
|
1549
805
|
* or queued for batching depending on the session's delivery mode.
|
|
1550
806
|
*/
|
|
1551
807
|
async routeAndDeliver(mindName, payload) {
|
|
1552
|
-
const
|
|
808
|
+
const baseName = await getBaseName(mindName);
|
|
1553
809
|
const config = getRoutingConfig(baseName);
|
|
1554
810
|
const meta = {
|
|
1555
811
|
channel: payload.channel,
|
|
@@ -1585,7 +841,7 @@ var DeliveryManager = class {
|
|
|
1585
841
|
const sessionConfig = resolveDeliveryMode(config, sessionName, route.rule);
|
|
1586
842
|
if (sessionConfig.delivery.mode === "batch") {
|
|
1587
843
|
dlog2.debug(`enqueueing batch message for ${mindName}/${sessionName}`);
|
|
1588
|
-
this.enqueueBatch(mindName, sessionName, payload, sessionConfig);
|
|
844
|
+
await this.enqueueBatch(mindName, sessionName, payload, sessionConfig);
|
|
1589
845
|
return { routed: true, session: sessionName, destination: "mind", mode: "batch" };
|
|
1590
846
|
}
|
|
1591
847
|
await this.deliverToMind(mindName, sessionName, payload, sessionConfig);
|
|
@@ -1595,8 +851,8 @@ var DeliveryManager = class {
|
|
|
1595
851
|
* Called when a mind's session emits a "done" event — decrements active count
|
|
1596
852
|
* and may trigger batch flush if session goes idle.
|
|
1597
853
|
*/
|
|
1598
|
-
sessionDone(mindName, session) {
|
|
1599
|
-
const
|
|
854
|
+
async sessionDone(mindName, session) {
|
|
855
|
+
const baseName = await getBaseName(mindName);
|
|
1600
856
|
if (session) {
|
|
1601
857
|
this.decrementActive(baseName, session);
|
|
1602
858
|
} else {
|
|
@@ -1614,7 +870,7 @@ var DeliveryManager = class {
|
|
|
1614
870
|
async restoreFromDb() {
|
|
1615
871
|
try {
|
|
1616
872
|
const db = await getDb();
|
|
1617
|
-
const rows = await db.select().from(deliveryQueue).where(
|
|
873
|
+
const rows = await db.select().from(deliveryQueue).where(eq2(deliveryQueue.status, "pending"));
|
|
1618
874
|
for (const row of rows) {
|
|
1619
875
|
let payload;
|
|
1620
876
|
try {
|
|
@@ -1632,7 +888,7 @@ var DeliveryManager = class {
|
|
|
1632
888
|
this.addToBatchBuffer(row.mind, row.session, payload, sessionConfig);
|
|
1633
889
|
} else {
|
|
1634
890
|
try {
|
|
1635
|
-
await db.delete(deliveryQueue).where(
|
|
891
|
+
await db.delete(deliveryQueue).where(eq2(deliveryQueue.id, row.id));
|
|
1636
892
|
} catch (err) {
|
|
1637
893
|
dlog2.warn(`failed to delete queue row ${row.id} for ${row.mind}`, logger_default.errorData(err));
|
|
1638
894
|
}
|
|
@@ -1653,7 +909,7 @@ var DeliveryManager = class {
|
|
|
1653
909
|
*/
|
|
1654
910
|
async getPending(mindName) {
|
|
1655
911
|
const db = await getDb();
|
|
1656
|
-
const rows = await db.select().from(deliveryQueue).where(
|
|
912
|
+
const rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, mindName), eq2(deliveryQueue.status, "gated")));
|
|
1657
913
|
const byChannel = /* @__PURE__ */ new Map();
|
|
1658
914
|
for (const row of rows) {
|
|
1659
915
|
const ch = row.channel ?? "unknown";
|
|
@@ -1691,18 +947,13 @@ var DeliveryManager = class {
|
|
|
1691
947
|
}
|
|
1692
948
|
this.batchBuffers.clear();
|
|
1693
949
|
this.sessionStates.clear();
|
|
1694
|
-
if (
|
|
950
|
+
if (instance2 === this) instance2 = void 0;
|
|
1695
951
|
}
|
|
1696
952
|
// --- Private ---
|
|
1697
|
-
resolvePort(mindName) {
|
|
1698
|
-
const
|
|
1699
|
-
const entry = findMind(baseName);
|
|
953
|
+
async resolvePort(mindName) {
|
|
954
|
+
const entry = await findMind(mindName);
|
|
1700
955
|
if (!entry) return null;
|
|
1701
|
-
|
|
1702
|
-
const variant = findVariant(baseName, variantName);
|
|
1703
|
-
if (!variant) return null;
|
|
1704
|
-
return { baseName, port: variant.port };
|
|
1705
|
-
}
|
|
956
|
+
const baseName = entry.parent ?? mindName;
|
|
1706
957
|
return { baseName, port: entry.port };
|
|
1707
958
|
}
|
|
1708
959
|
async postToMind(port, body) {
|
|
@@ -1728,7 +979,7 @@ var DeliveryManager = class {
|
|
|
1728
979
|
}
|
|
1729
980
|
}
|
|
1730
981
|
async deliverToMind(mindName, session, payload, sessionConfig) {
|
|
1731
|
-
const resolved = this.resolvePort(mindName);
|
|
982
|
+
const resolved = await this.resolvePort(mindName);
|
|
1732
983
|
if (!resolved) {
|
|
1733
984
|
dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
1734
985
|
return;
|
|
@@ -1765,16 +1016,16 @@ var DeliveryManager = class {
|
|
|
1765
1016
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
1766
1017
|
}
|
|
1767
1018
|
}
|
|
1768
|
-
async deliverBatchToMind(mindName, session,
|
|
1769
|
-
const resolved = this.resolvePort(mindName);
|
|
1019
|
+
async deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride) {
|
|
1020
|
+
const resolved = await this.resolvePort(mindName);
|
|
1770
1021
|
if (!resolved) {
|
|
1771
1022
|
dlog2.warn(`cannot deliver batch to ${mindName}: mind not found`);
|
|
1772
1023
|
return;
|
|
1773
1024
|
}
|
|
1774
1025
|
const { baseName, port } = resolved;
|
|
1775
1026
|
const enrichedMessages = await Promise.all(
|
|
1776
|
-
|
|
1777
|
-
const isFirst =
|
|
1027
|
+
messages.map(async (msg, i) => {
|
|
1028
|
+
const isFirst = messages.findIndex((m) => m.channel === msg.channel) === i;
|
|
1778
1029
|
if (!isFirst) return msg;
|
|
1779
1030
|
const enrichedPayload = await this.enrichWithProfiles(baseName, session, msg.payload);
|
|
1780
1031
|
return { ...msg, payload: enrichedPayload };
|
|
@@ -1788,7 +1039,7 @@ var DeliveryManager = class {
|
|
|
1788
1039
|
}
|
|
1789
1040
|
const senders = /* @__PURE__ */ new Set();
|
|
1790
1041
|
const channelSet = /* @__PURE__ */ new Set();
|
|
1791
|
-
for (const msg of
|
|
1042
|
+
for (const msg of messages) {
|
|
1792
1043
|
if (msg.sender) senders.add(msg.sender);
|
|
1793
1044
|
if (msg.channel) channelSet.add(msg.channel);
|
|
1794
1045
|
}
|
|
@@ -1798,7 +1049,7 @@ var DeliveryManager = class {
|
|
|
1798
1049
|
if (ch !== "unknown") typingMap.set(ch, baseName, { persistent: true });
|
|
1799
1050
|
}
|
|
1800
1051
|
const seenConvIds = /* @__PURE__ */ new Set();
|
|
1801
|
-
for (const msg of
|
|
1052
|
+
for (const msg of messages) {
|
|
1802
1053
|
if (msg.payload.conversationId && !seenConvIds.has(msg.payload.conversationId)) {
|
|
1803
1054
|
seenConvIds.add(msg.payload.conversationId);
|
|
1804
1055
|
typingMap.set(`volute:${msg.payload.conversationId}`, baseName, { persistent: true });
|
|
@@ -1819,10 +1070,10 @@ var DeliveryManager = class {
|
|
|
1819
1070
|
try {
|
|
1820
1071
|
const db = await getDb();
|
|
1821
1072
|
await db.delete(deliveryQueue).where(
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1073
|
+
and2(
|
|
1074
|
+
eq2(deliveryQueue.mind, baseName),
|
|
1075
|
+
eq2(deliveryQueue.session, session),
|
|
1076
|
+
eq2(deliveryQueue.status, "pending")
|
|
1826
1077
|
)
|
|
1827
1078
|
);
|
|
1828
1079
|
} catch (err) {
|
|
@@ -1838,13 +1089,13 @@ var DeliveryManager = class {
|
|
|
1838
1089
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
1839
1090
|
}
|
|
1840
1091
|
}
|
|
1841
|
-
enqueueBatch(mindName, session, payload, sessionConfig) {
|
|
1092
|
+
async enqueueBatch(mindName, session, payload, sessionConfig) {
|
|
1842
1093
|
const delivery = sessionConfig.delivery;
|
|
1843
1094
|
if (delivery.triggers?.length) {
|
|
1844
1095
|
const text = extractTextContent(payload.content);
|
|
1845
1096
|
const lower = text.toLowerCase();
|
|
1846
1097
|
if (delivery.triggers.some((t) => lower.includes(t.toLowerCase()))) {
|
|
1847
|
-
this.flushBatch(mindName, session, [
|
|
1098
|
+
await this.flushBatch(mindName, session, [
|
|
1848
1099
|
{
|
|
1849
1100
|
payload,
|
|
1850
1101
|
channel: payload.channel,
|
|
@@ -1855,14 +1106,14 @@ var DeliveryManager = class {
|
|
|
1855
1106
|
return;
|
|
1856
1107
|
}
|
|
1857
1108
|
}
|
|
1858
|
-
const
|
|
1109
|
+
const baseName = await getBaseName(mindName);
|
|
1859
1110
|
const state = this.sessionStates.get(baseName)?.get(session);
|
|
1860
1111
|
if (state && state.activeCount > 0 && payload.sender && !state.lastDeliverySenders.has(payload.sender) && payload.channel && state.lastDeliveryChannels.has(payload.channel) && Date.now() - state.lastDeliveredAt < delivery.maxWait * 1e3 && Date.now() - state.lastInterruptAt > delivery.debounce * 1e3) {
|
|
1861
1112
|
state.lastInterruptAt = Date.now();
|
|
1862
1113
|
this.persistToQueue(mindName, session, payload).catch((err) => {
|
|
1863
1114
|
dlog2.warn(`failed to persist batch message for ${mindName}/${session}`, logger_default.errorData(err));
|
|
1864
1115
|
});
|
|
1865
|
-
this.flushBatch(
|
|
1116
|
+
await this.flushBatch(
|
|
1866
1117
|
mindName,
|
|
1867
1118
|
session,
|
|
1868
1119
|
[{ payload, channel: payload.channel, sender: payload.sender, createdAt: Date.now() }],
|
|
@@ -1917,42 +1168,42 @@ var DeliveryManager = class {
|
|
|
1917
1168
|
buffer.maxWaitTimer.unref();
|
|
1918
1169
|
}
|
|
1919
1170
|
}
|
|
1920
|
-
flushBatch(mindName, session, extra, interruptOverride) {
|
|
1171
|
+
async flushBatch(mindName, session, extra, interruptOverride) {
|
|
1921
1172
|
const bufferKey = `${mindName}:${session}`;
|
|
1922
1173
|
const buffer = this.batchBuffers.get(bufferKey);
|
|
1923
|
-
const
|
|
1174
|
+
const messages = [];
|
|
1924
1175
|
if (buffer) {
|
|
1925
1176
|
if (buffer.debounceTimer) clearTimeout(buffer.debounceTimer);
|
|
1926
1177
|
if (buffer.maxWaitTimer) clearTimeout(buffer.maxWaitTimer);
|
|
1927
1178
|
buffer.debounceTimer = null;
|
|
1928
1179
|
buffer.maxWaitTimer = null;
|
|
1929
|
-
|
|
1180
|
+
messages.push(...buffer.messages.splice(0));
|
|
1930
1181
|
this.batchBuffers.delete(bufferKey);
|
|
1931
1182
|
}
|
|
1932
|
-
if (extra)
|
|
1933
|
-
if (
|
|
1934
|
-
const
|
|
1183
|
+
if (extra) messages.push(...extra);
|
|
1184
|
+
if (messages.length === 0) return;
|
|
1185
|
+
const baseName = await getBaseName(mindName);
|
|
1935
1186
|
const config = getRoutingConfig(baseName);
|
|
1936
1187
|
const sessionConfig = resolveDeliveryMode(config, session);
|
|
1937
1188
|
dlog2.info(
|
|
1938
|
-
`flushing batch for ${mindName}/${session}: ${
|
|
1189
|
+
`flushing batch for ${mindName}/${session}: ${messages.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
|
|
1939
1190
|
);
|
|
1940
|
-
this.deliverBatchToMind(mindName, session,
|
|
1191
|
+
this.deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride).catch(
|
|
1941
1192
|
(err) => {
|
|
1942
1193
|
dlog2.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
|
|
1943
1194
|
}
|
|
1944
1195
|
);
|
|
1945
1196
|
}
|
|
1946
1197
|
async gateMessage(mindName, session, payload) {
|
|
1947
|
-
const
|
|
1198
|
+
const baseName = await getBaseName(mindName);
|
|
1948
1199
|
await this.persistToQueue(baseName, session, payload, "gated");
|
|
1949
1200
|
try {
|
|
1950
1201
|
const db = await getDb();
|
|
1951
|
-
const count2 = await db.select({ count:
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1202
|
+
const count2 = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
|
|
1203
|
+
and2(
|
|
1204
|
+
eq2(deliveryQueue.mind, baseName),
|
|
1205
|
+
eq2(deliveryQueue.channel, payload.channel),
|
|
1206
|
+
eq2(deliveryQueue.status, "gated")
|
|
1956
1207
|
)
|
|
1957
1208
|
);
|
|
1958
1209
|
if ((count2[0]?.count ?? 0) <= 1) {
|
|
@@ -1982,7 +1233,7 @@ var DeliveryManager = class {
|
|
|
1982
1233
|
sender: "system",
|
|
1983
1234
|
content: [{ type: "text", text: notification }]
|
|
1984
1235
|
};
|
|
1985
|
-
const config = getRoutingConfig(mindName
|
|
1236
|
+
const config = getRoutingConfig(await getBaseName(mindName));
|
|
1986
1237
|
const sessionConfig = resolveDeliveryMode(config, "main");
|
|
1987
1238
|
await this.deliverToMind(mindName, "main", invitePayload, {
|
|
1988
1239
|
...sessionConfig,
|
|
@@ -2126,17 +1377,17 @@ var DeliveryManager = class {
|
|
|
2126
1377
|
}
|
|
2127
1378
|
}
|
|
2128
1379
|
};
|
|
2129
|
-
var
|
|
1380
|
+
var instance2;
|
|
2130
1381
|
function initDeliveryManager() {
|
|
2131
|
-
if (
|
|
2132
|
-
|
|
2133
|
-
return
|
|
1382
|
+
if (instance2) throw new Error("DeliveryManager already initialized");
|
|
1383
|
+
instance2 = new DeliveryManager();
|
|
1384
|
+
return instance2;
|
|
2134
1385
|
}
|
|
2135
1386
|
function getDeliveryManager() {
|
|
2136
|
-
if (!
|
|
1387
|
+
if (!instance2) {
|
|
2137
1388
|
throw new Error("DeliveryManager not initialized \u2014 call initDeliveryManager() first");
|
|
2138
1389
|
}
|
|
2139
|
-
return
|
|
1390
|
+
return instance2;
|
|
2140
1391
|
}
|
|
2141
1392
|
|
|
2142
1393
|
// src/lib/delivery/message-delivery.ts
|
|
@@ -2163,8 +1414,8 @@ async function recordInbound(mind, channel, sender, content) {
|
|
|
2163
1414
|
}
|
|
2164
1415
|
async function deliverMessage(mindName, payload) {
|
|
2165
1416
|
try {
|
|
2166
|
-
const
|
|
2167
|
-
const entry = findMind(baseName);
|
|
1417
|
+
const baseName = await getBaseName(mindName);
|
|
1418
|
+
const entry = await findMind(baseName);
|
|
2168
1419
|
if (!entry) {
|
|
2169
1420
|
dlog3.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
2170
1421
|
return;
|
|
@@ -2188,6 +1439,60 @@ async function deliverMessage(mindName, payload) {
|
|
|
2188
1439
|
}
|
|
2189
1440
|
}
|
|
2190
1441
|
|
|
1442
|
+
// src/lib/system-channel.ts
|
|
1443
|
+
var SYSTEM_CHANNEL_NAME = "system";
|
|
1444
|
+
var cachedChannelId = null;
|
|
1445
|
+
async function ensureSystemChannel() {
|
|
1446
|
+
if (cachedChannelId) return cachedChannelId;
|
|
1447
|
+
const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
|
|
1448
|
+
if (existing) {
|
|
1449
|
+
cachedChannelId = existing.id;
|
|
1450
|
+
return existing.id;
|
|
1451
|
+
}
|
|
1452
|
+
const conv = await createChannel(SYSTEM_CHANNEL_NAME);
|
|
1453
|
+
cachedChannelId = conv.id;
|
|
1454
|
+
logger_default.info("created #system channel");
|
|
1455
|
+
return conv.id;
|
|
1456
|
+
}
|
|
1457
|
+
async function joinSystemChannel(userId) {
|
|
1458
|
+
const channelId = await ensureSystemChannel();
|
|
1459
|
+
await joinChannel(channelId, userId);
|
|
1460
|
+
}
|
|
1461
|
+
async function joinSystemChannelForMind(mindName) {
|
|
1462
|
+
const user = await getOrCreateMindUser(mindName);
|
|
1463
|
+
await joinSystemChannel(user.id);
|
|
1464
|
+
}
|
|
1465
|
+
async function announceToSystem(text) {
|
|
1466
|
+
const channelId = await ensureSystemChannel();
|
|
1467
|
+
await addMessage(channelId, "system", "system", [{ type: "text", text }]);
|
|
1468
|
+
const participants = await getParticipants(channelId);
|
|
1469
|
+
const mindParticipants = participants.filter((p) => p.userType === "mind");
|
|
1470
|
+
const channel = "volute:#system";
|
|
1471
|
+
for (const mind of mindParticipants) {
|
|
1472
|
+
try {
|
|
1473
|
+
writeChannelEntry(mind.username, channel, {
|
|
1474
|
+
platformId: channelId,
|
|
1475
|
+
platform: "volute",
|
|
1476
|
+
name: SYSTEM_CHANNEL_NAME,
|
|
1477
|
+
type: "group"
|
|
1478
|
+
});
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
logger_default.warn(`failed to write channel entry for ${mind.username}`, logger_default.errorData(err));
|
|
1481
|
+
}
|
|
1482
|
+
deliverMessage(mind.username, {
|
|
1483
|
+
content: [{ type: "text", text }],
|
|
1484
|
+
channel,
|
|
1485
|
+
conversationId: channelId,
|
|
1486
|
+
sender: "system",
|
|
1487
|
+
participants: participants.map((p) => p.username),
|
|
1488
|
+
participantCount: participants.length,
|
|
1489
|
+
isDM: false
|
|
1490
|
+
}).catch((err) => {
|
|
1491
|
+
logger_default.warn(`failed to deliver system announcement to ${mind.username}`, logger_default.errorData(err));
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
2191
1496
|
// src/lib/daemon/mail-poller.ts
|
|
2192
1497
|
var mlog = logger_default.child("mail");
|
|
2193
1498
|
function formatEmailContent(email) {
|
|
@@ -2374,7 +1679,7 @@ var MailPoller = class {
|
|
|
2374
1679
|
await this.deliver(mind, { ...email, mind });
|
|
2375
1680
|
}
|
|
2376
1681
|
async deliver(mind, email) {
|
|
2377
|
-
const entry = findMind(mind);
|
|
1682
|
+
const entry = await findMind(mind);
|
|
2378
1683
|
if (!entry || !entry.running) {
|
|
2379
1684
|
mlog.warn(`skipping delivery to ${mind}: ${!entry ? "not found" : "not running"}`);
|
|
2380
1685
|
return;
|
|
@@ -2394,15 +1699,11 @@ var MailPoller = class {
|
|
|
2394
1699
|
}
|
|
2395
1700
|
}
|
|
2396
1701
|
};
|
|
2397
|
-
var
|
|
1702
|
+
var instance3 = null;
|
|
2398
1703
|
function initMailPoller() {
|
|
2399
|
-
if (
|
|
2400
|
-
|
|
2401
|
-
return
|
|
2402
|
-
}
|
|
2403
|
-
function getMailPoller() {
|
|
2404
|
-
if (!instance4) throw new Error("MailPoller not initialized \u2014 call initMailPoller() first");
|
|
2405
|
-
return instance4;
|
|
1704
|
+
if (instance3) throw new Error("MailPoller already initialized");
|
|
1705
|
+
instance3 = new MailPoller();
|
|
1706
|
+
return instance3;
|
|
2406
1707
|
}
|
|
2407
1708
|
async function ensureMailAddress(mindName) {
|
|
2408
1709
|
const config = readSystemsConfig();
|
|
@@ -2428,14 +1729,14 @@ async function ensureMailAddress(mindName) {
|
|
|
2428
1729
|
// src/lib/daemon/scheduler.ts
|
|
2429
1730
|
import { resolve as resolve6 } from "path";
|
|
2430
1731
|
import { CronExpressionParser } from "cron-parser";
|
|
2431
|
-
var
|
|
1732
|
+
var slog = logger_default.child("scheduler");
|
|
2432
1733
|
var Scheduler = class {
|
|
2433
1734
|
schedules = /* @__PURE__ */ new Map();
|
|
2434
1735
|
interval = null;
|
|
2435
1736
|
lastFired = /* @__PURE__ */ new Map();
|
|
2436
1737
|
// "mind:scheduleId" → epoch minute
|
|
2437
1738
|
get statePath() {
|
|
2438
|
-
return resolve6(
|
|
1739
|
+
return resolve6(voluteSystemDir(), "scheduler-state.json");
|
|
2439
1740
|
}
|
|
2440
1741
|
start() {
|
|
2441
1742
|
this.loadState();
|
|
@@ -2494,7 +1795,7 @@ var Scheduler = class {
|
|
|
2494
1795
|
prevMinute = Math.floor(prev.getTime() / 6e4);
|
|
2495
1796
|
cronCache.set(schedule.cron, prevMinute);
|
|
2496
1797
|
} catch (err) {
|
|
2497
|
-
|
|
1798
|
+
slog.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
|
|
2498
1799
|
return false;
|
|
2499
1800
|
}
|
|
2500
1801
|
}
|
|
@@ -2509,11 +1810,11 @@ var Scheduler = class {
|
|
|
2509
1810
|
const sleepState = sleepManager?.getState(mindName);
|
|
2510
1811
|
if (sleepState?.sleeping) {
|
|
2511
1812
|
if (schedule.skipWhenSleeping) {
|
|
2512
|
-
|
|
1813
|
+
slog.info(`skipped "${schedule.id}" for ${mindName} (sleeping)`);
|
|
2513
1814
|
return;
|
|
2514
1815
|
}
|
|
2515
1816
|
if (sleepState.wokenByTrigger) {
|
|
2516
|
-
|
|
1817
|
+
slog.info(`skipped "${schedule.id}" for ${mindName} (trigger-woken)`);
|
|
2517
1818
|
return;
|
|
2518
1819
|
}
|
|
2519
1820
|
}
|
|
@@ -2524,7 +1825,7 @@ var Scheduler = class {
|
|
|
2524
1825
|
try {
|
|
2525
1826
|
const output = await this.runScript(schedule.script, homeDir, mindName);
|
|
2526
1827
|
if (!output.trim()) {
|
|
2527
|
-
|
|
1828
|
+
slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
|
|
2528
1829
|
return;
|
|
2529
1830
|
}
|
|
2530
1831
|
text = output;
|
|
@@ -2532,12 +1833,12 @@ var Scheduler = class {
|
|
|
2532
1833
|
const stderr = err.stderr ?? "";
|
|
2533
1834
|
text = `[script error] ${err.message}${stderr ? `
|
|
2534
1835
|
${stderr}` : ""}`;
|
|
2535
|
-
|
|
1836
|
+
slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
|
|
2536
1837
|
}
|
|
2537
1838
|
} else if (schedule.message) {
|
|
2538
1839
|
text = schedule.message;
|
|
2539
1840
|
} else {
|
|
2540
|
-
|
|
1841
|
+
slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
|
|
2541
1842
|
return;
|
|
2542
1843
|
}
|
|
2543
1844
|
await this.deliver(mindName, {
|
|
@@ -2545,9 +1846,9 @@ ${stderr}` : ""}`;
|
|
|
2545
1846
|
channel: schedule.channel ?? "system:scheduler",
|
|
2546
1847
|
sender: schedule.id
|
|
2547
1848
|
});
|
|
2548
|
-
|
|
1849
|
+
slog.info(`fired "${schedule.id}" for ${mindName}`);
|
|
2549
1850
|
} catch (err) {
|
|
2550
|
-
|
|
1851
|
+
slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
|
|
2551
1852
|
}
|
|
2552
1853
|
}
|
|
2553
1854
|
runScript(script, cwd, mindName) {
|
|
@@ -2557,19 +1858,19 @@ ${stderr}` : ""}`;
|
|
|
2557
1858
|
return deliverMessage(mindName, payload);
|
|
2558
1859
|
}
|
|
2559
1860
|
};
|
|
2560
|
-
var
|
|
1861
|
+
var instance4 = null;
|
|
2561
1862
|
function initScheduler() {
|
|
2562
|
-
if (
|
|
2563
|
-
|
|
2564
|
-
return
|
|
1863
|
+
if (instance4) throw new Error("Scheduler already initialized");
|
|
1864
|
+
instance4 = new Scheduler();
|
|
1865
|
+
return instance4;
|
|
2565
1866
|
}
|
|
2566
1867
|
function getScheduler() {
|
|
2567
|
-
if (!
|
|
2568
|
-
return
|
|
1868
|
+
if (!instance4) throw new Error("Scheduler not initialized \u2014 call initScheduler() first");
|
|
1869
|
+
return instance4;
|
|
2569
1870
|
}
|
|
2570
1871
|
|
|
2571
1872
|
// src/lib/daemon/token-budget.ts
|
|
2572
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
1873
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2573
1874
|
import { resolve as resolve7 } from "path";
|
|
2574
1875
|
var tlog = logger_default.child("token-budget");
|
|
2575
1876
|
var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
|
|
@@ -2644,9 +1945,9 @@ var TokenBudget = class {
|
|
|
2644
1945
|
drain(mind) {
|
|
2645
1946
|
const state = this.budgets.get(mind);
|
|
2646
1947
|
if (!state) return [];
|
|
2647
|
-
const
|
|
1948
|
+
const messages = state.queue;
|
|
2648
1949
|
state.queue = [];
|
|
2649
|
-
return
|
|
1950
|
+
return messages;
|
|
2650
1951
|
}
|
|
2651
1952
|
getUsage(mind) {
|
|
2652
1953
|
const state = this.budgets.get(mind);
|
|
@@ -2693,14 +1994,14 @@ var TokenBudget = class {
|
|
|
2693
1994
|
saveBudgetState(mind, state) {
|
|
2694
1995
|
try {
|
|
2695
1996
|
const dir = stateDir(mind);
|
|
2696
|
-
|
|
1997
|
+
mkdirSync3(dir, { recursive: true });
|
|
2697
1998
|
const data = {
|
|
2698
1999
|
periodStart: state.periodStart,
|
|
2699
2000
|
tokensUsed: state.tokensUsed,
|
|
2700
2001
|
warningInjected: state.warningInjected,
|
|
2701
2002
|
queue: state.queue
|
|
2702
2003
|
};
|
|
2703
|
-
|
|
2004
|
+
writeFileSync3(this.budgetStatePath(mind), `${JSON.stringify(data)}
|
|
2704
2005
|
`);
|
|
2705
2006
|
} catch (err) {
|
|
2706
2007
|
tlog.warn(`failed to save budget state for ${mind}`, logger_default.errorData(err));
|
|
@@ -2727,8 +2028,8 @@ var TokenBudget = class {
|
|
|
2727
2028
|
return null;
|
|
2728
2029
|
}
|
|
2729
2030
|
}
|
|
2730
|
-
async replay(mindName,
|
|
2731
|
-
const summary =
|
|
2031
|
+
async replay(mindName, messages) {
|
|
2032
|
+
const summary = messages.map((m) => {
|
|
2732
2033
|
const from = m.sender ? `[${m.sender}]` : "";
|
|
2733
2034
|
const ch = m.channel ? `(${m.channel})` : "";
|
|
2734
2035
|
return `${from}${ch} ${m.textContent}`;
|
|
@@ -2738,7 +2039,7 @@ var TokenBudget = class {
|
|
|
2738
2039
|
content: [
|
|
2739
2040
|
{
|
|
2740
2041
|
type: "text",
|
|
2741
|
-
text: `[Budget replay] ${
|
|
2042
|
+
text: `[Budget replay] ${messages.length} queued message(s) from the previous budget period:
|
|
2742
2043
|
|
|
2743
2044
|
${summary}`
|
|
2744
2045
|
}
|
|
@@ -2746,40 +2047,38 @@ ${summary}`
|
|
|
2746
2047
|
channel: "system:budget-replay",
|
|
2747
2048
|
sender: "system"
|
|
2748
2049
|
});
|
|
2749
|
-
tlog.info(`replayed ${
|
|
2050
|
+
tlog.info(`replayed ${messages.length} queued message(s) for ${mindName}`);
|
|
2750
2051
|
} catch (err) {
|
|
2751
2052
|
tlog.warn(`failed to replay for ${mindName}`, logger_default.errorData(err));
|
|
2752
2053
|
const state = this.budgets.get(mindName);
|
|
2753
|
-
if (state) state.queue.push(...
|
|
2054
|
+
if (state) state.queue.push(...messages);
|
|
2754
2055
|
}
|
|
2755
2056
|
}
|
|
2756
2057
|
};
|
|
2757
|
-
var
|
|
2058
|
+
var instance5 = null;
|
|
2758
2059
|
function initTokenBudget() {
|
|
2759
|
-
if (
|
|
2760
|
-
|
|
2761
|
-
return
|
|
2060
|
+
if (instance5) throw new Error("TokenBudget already initialized");
|
|
2061
|
+
instance5 = new TokenBudget();
|
|
2062
|
+
return instance5;
|
|
2762
2063
|
}
|
|
2763
2064
|
function getTokenBudget() {
|
|
2764
|
-
if (!
|
|
2765
|
-
return
|
|
2065
|
+
if (!instance5) throw new Error("TokenBudget not initialized \u2014 call initTokenBudget() first");
|
|
2066
|
+
return instance5;
|
|
2766
2067
|
}
|
|
2767
2068
|
|
|
2768
2069
|
// src/lib/daemon/mind-service.ts
|
|
2769
2070
|
async function startMindFull(name) {
|
|
2770
|
-
const
|
|
2071
|
+
const entry = await findMind(name);
|
|
2072
|
+
const baseName = entry?.parent ?? name;
|
|
2771
2073
|
await getMindManager().startMind(name);
|
|
2772
2074
|
publish({
|
|
2773
2075
|
type: "mind_started",
|
|
2774
2076
|
mind: name,
|
|
2775
2077
|
summary: `${name} started`
|
|
2776
2078
|
}).catch((err) => logger_default.error("failed to publish mind_started activity", logger_default.errorData(err)));
|
|
2777
|
-
if (
|
|
2778
|
-
const entry = findMind(baseName);
|
|
2079
|
+
if (entry?.parent) return;
|
|
2779
2080
|
if (!entry || entry.stage === "seed") return;
|
|
2780
2081
|
const dir = mindDir(baseName);
|
|
2781
|
-
const daemonPort = process.env.VOLUTE_DAEMON_PORT ? parseInt(process.env.VOLUTE_DAEMON_PORT, 10) : void 0;
|
|
2782
|
-
await getConnectorManager().startConnectors(baseName, dir, entry.port, daemonPort);
|
|
2783
2082
|
getScheduler().loadSchedules(baseName);
|
|
2784
2083
|
ensureMailAddress(baseName).catch(
|
|
2785
2084
|
(err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
|
|
@@ -2820,11 +2119,11 @@ async function wakeMind(name) {
|
|
|
2820
2119
|
}).catch((err) => logger_default.error("failed to publish mind_waking activity", logger_default.errorData(err)));
|
|
2821
2120
|
}
|
|
2822
2121
|
async function stopMindFull(name) {
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2122
|
+
const baseName = await getBaseName(name);
|
|
2123
|
+
const isBase = baseName === name;
|
|
2124
|
+
if (isBase) {
|
|
2825
2125
|
stopWatcher(baseName);
|
|
2826
2126
|
markIdle(baseName);
|
|
2827
|
-
await getConnectorManager().stopConnectors(baseName);
|
|
2828
2127
|
getScheduler().unloadSchedules(baseName);
|
|
2829
2128
|
getTokenBudget().removeBudget(baseName);
|
|
2830
2129
|
}
|
|
@@ -2837,7 +2136,7 @@ async function stopMindFull(name) {
|
|
|
2837
2136
|
}
|
|
2838
2137
|
|
|
2839
2138
|
// src/lib/daemon/sleep-manager.ts
|
|
2840
|
-
var
|
|
2139
|
+
var slog2 = logger_default.child("sleep");
|
|
2841
2140
|
function defaultState() {
|
|
2842
2141
|
return {
|
|
2843
2142
|
sleeping: false,
|
|
@@ -2874,7 +2173,7 @@ var SleepManager = class {
|
|
|
2874
2173
|
unsubActivity = null;
|
|
2875
2174
|
transitioning = /* @__PURE__ */ new Set();
|
|
2876
2175
|
get statePath() {
|
|
2877
|
-
return resolve8(
|
|
2176
|
+
return resolve8(voluteSystemDir(), "sleep-state.json");
|
|
2878
2177
|
}
|
|
2879
2178
|
start() {
|
|
2880
2179
|
this.loadState();
|
|
@@ -2898,7 +2197,7 @@ var SleepManager = class {
|
|
|
2898
2197
|
}
|
|
2899
2198
|
}
|
|
2900
2199
|
} catch (err) {
|
|
2901
|
-
|
|
2200
|
+
slog2.warn("failed to load sleep state", logger_default.errorData(err));
|
|
2902
2201
|
}
|
|
2903
2202
|
}
|
|
2904
2203
|
saveState() {
|
|
@@ -2907,10 +2206,10 @@ var SleepManager = class {
|
|
|
2907
2206
|
if (state.sleeping) data[name] = state;
|
|
2908
2207
|
}
|
|
2909
2208
|
try {
|
|
2910
|
-
|
|
2209
|
+
writeFileSync4(this.statePath, `${JSON.stringify(data, null, 2)}
|
|
2911
2210
|
`);
|
|
2912
2211
|
} catch (err) {
|
|
2913
|
-
|
|
2212
|
+
slog2.error("failed to save sleep state", logger_default.errorData(err));
|
|
2914
2213
|
}
|
|
2915
2214
|
}
|
|
2916
2215
|
// --- Public API ---
|
|
@@ -2931,7 +2230,7 @@ var SleepManager = class {
|
|
|
2931
2230
|
const state = this.states.get(name);
|
|
2932
2231
|
if (!state?.sleeping || !state.wokenByTrigger) return;
|
|
2933
2232
|
this.markAwake(name);
|
|
2934
|
-
|
|
2233
|
+
slog2.info(`${name} trigger-wake converted to full wake`);
|
|
2935
2234
|
}
|
|
2936
2235
|
getSleepConfig(name) {
|
|
2937
2236
|
const dir = mindDir(name);
|
|
@@ -2952,7 +2251,7 @@ var SleepManager = class {
|
|
|
2952
2251
|
this.markSleeping(name, opts);
|
|
2953
2252
|
return;
|
|
2954
2253
|
}
|
|
2955
|
-
const entry = findMind(name);
|
|
2254
|
+
const entry = await findMind(name);
|
|
2956
2255
|
if (!entry) return;
|
|
2957
2256
|
const sleepConfig = this.getSleepConfig(name);
|
|
2958
2257
|
const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
|
|
@@ -2967,7 +2266,7 @@ var SleepManager = class {
|
|
|
2967
2266
|
content: preSleepMsg
|
|
2968
2267
|
});
|
|
2969
2268
|
} catch (err) {
|
|
2970
|
-
|
|
2269
|
+
slog2.error(`failed to persist pre-sleep message for ${name}`, logger_default.errorData(err));
|
|
2971
2270
|
}
|
|
2972
2271
|
try {
|
|
2973
2272
|
await fetch(`http://127.0.0.1:${entry.port}/message`, {
|
|
@@ -2979,7 +2278,7 @@ var SleepManager = class {
|
|
|
2979
2278
|
})
|
|
2980
2279
|
});
|
|
2981
2280
|
} catch (err) {
|
|
2982
|
-
|
|
2281
|
+
slog2.warn(`failed to send pre-sleep message to ${name}`, logger_default.errorData(err));
|
|
2983
2282
|
}
|
|
2984
2283
|
await this.waitForIdle(name, 12e4);
|
|
2985
2284
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
@@ -2987,7 +2286,7 @@ var SleepManager = class {
|
|
|
2987
2286
|
await this.killOrphanOnPort(entry.port);
|
|
2988
2287
|
await this.archiveSessions(name);
|
|
2989
2288
|
this.markSleeping(name, opts);
|
|
2990
|
-
|
|
2289
|
+
slog2.info(`${name} is now sleeping`);
|
|
2991
2290
|
} finally {
|
|
2992
2291
|
this.transitioning.delete(name);
|
|
2993
2292
|
}
|
|
@@ -3004,10 +2303,10 @@ var SleepManager = class {
|
|
|
3004
2303
|
try {
|
|
3005
2304
|
await wakeMind(name);
|
|
3006
2305
|
} catch (err) {
|
|
3007
|
-
|
|
2306
|
+
slog2.error(`failed to wake ${name}`, logger_default.errorData(err));
|
|
3008
2307
|
return;
|
|
3009
2308
|
}
|
|
3010
|
-
const entry = findMind(name);
|
|
2309
|
+
const entry = await findMind(name);
|
|
3011
2310
|
if (!entry) return;
|
|
3012
2311
|
if (opts?.trigger) {
|
|
3013
2312
|
state.wokenByTrigger = true;
|
|
@@ -3048,7 +2347,7 @@ var SleepManager = class {
|
|
|
3048
2347
|
content: summaryText
|
|
3049
2348
|
});
|
|
3050
2349
|
} catch (err) {
|
|
3051
|
-
|
|
2350
|
+
slog2.error(`failed to persist wake summary for ${name}`, logger_default.errorData(err));
|
|
3052
2351
|
}
|
|
3053
2352
|
try {
|
|
3054
2353
|
await fetch(`http://127.0.0.1:${entry.port}/message`, {
|
|
@@ -3060,17 +2359,17 @@ var SleepManager = class {
|
|
|
3060
2359
|
})
|
|
3061
2360
|
});
|
|
3062
2361
|
} catch (err) {
|
|
3063
|
-
|
|
2362
|
+
slog2.warn(`failed to deliver wake summary to ${name}`, logger_default.errorData(err));
|
|
3064
2363
|
}
|
|
3065
2364
|
}
|
|
3066
2365
|
const flushed = await this.flushQueuedMessages(name);
|
|
3067
2366
|
if (flushed > 0) {
|
|
3068
|
-
|
|
2367
|
+
slog2.info(`flushed ${flushed} queued message(s) for ${name}`);
|
|
3069
2368
|
}
|
|
3070
2369
|
if (!opts?.trigger) {
|
|
3071
2370
|
this.markAwake(name);
|
|
3072
2371
|
}
|
|
3073
|
-
|
|
2372
|
+
slog2.info(`${name} is now awake${opts?.trigger ? " (trigger wake)" : ""}`);
|
|
3074
2373
|
} finally {
|
|
3075
2374
|
this.transitioning.delete(name);
|
|
3076
2375
|
}
|
|
@@ -3125,20 +2424,20 @@ var SleepManager = class {
|
|
|
3125
2424
|
async flushQueuedMessages(name) {
|
|
3126
2425
|
try {
|
|
3127
2426
|
const db = await getDb();
|
|
3128
|
-
const rows = await db.select().from(deliveryQueue).where(
|
|
2427
|
+
const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
|
|
3129
2428
|
if (rows.length === 0) return 0;
|
|
3130
|
-
const { deliverMessage: deliverMessage2 } = await import("./message-delivery-
|
|
2429
|
+
const { deliverMessage: deliverMessage2 } = await import("./message-delivery-LDXLGERA.js");
|
|
3131
2430
|
const delivered = [];
|
|
3132
2431
|
for (const row of rows) {
|
|
3133
2432
|
try {
|
|
3134
2433
|
await deliverMessage2(name, JSON.parse(row.payload));
|
|
3135
2434
|
delivered.push(row.id);
|
|
3136
2435
|
} catch (err) {
|
|
3137
|
-
|
|
2436
|
+
slog2.warn(`failed to flush queued message ${row.id} for ${name}`, logger_default.errorData(err));
|
|
3138
2437
|
}
|
|
3139
2438
|
}
|
|
3140
2439
|
if (delivered.length > 0) {
|
|
3141
|
-
await db.delete(deliveryQueue).where(
|
|
2440
|
+
await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, delivered));
|
|
3142
2441
|
}
|
|
3143
2442
|
const state = this.states.get(name);
|
|
3144
2443
|
if (state) {
|
|
@@ -3146,7 +2445,7 @@ var SleepManager = class {
|
|
|
3146
2445
|
}
|
|
3147
2446
|
return delivered.length;
|
|
3148
2447
|
} catch (err) {
|
|
3149
|
-
|
|
2448
|
+
slog2.warn(`failed to flush queued messages for ${name}`, logger_default.errorData(err));
|
|
3150
2449
|
return 0;
|
|
3151
2450
|
}
|
|
3152
2451
|
}
|
|
@@ -3175,14 +2474,14 @@ var SleepManager = class {
|
|
|
3175
2474
|
const interval = CronExpressionParser2.parse(config.schedule.wake);
|
|
3176
2475
|
return interval.next().toDate().toISOString();
|
|
3177
2476
|
} catch (err) {
|
|
3178
|
-
|
|
2477
|
+
slog2.warn(`invalid wake cron "${config.schedule.wake}"`, logger_default.errorData(err));
|
|
3179
2478
|
return null;
|
|
3180
2479
|
}
|
|
3181
2480
|
}
|
|
3182
|
-
tick() {
|
|
2481
|
+
async tick() {
|
|
3183
2482
|
const now = /* @__PURE__ */ new Date();
|
|
3184
2483
|
const epochMinute = Math.floor(now.getTime() / 6e4);
|
|
3185
|
-
const registry = readRegistry();
|
|
2484
|
+
const registry = await readRegistry();
|
|
3186
2485
|
for (const entry of registry) {
|
|
3187
2486
|
if (!entry.running && !this.isSleeping(entry.name)) continue;
|
|
3188
2487
|
const config = this.getSleepConfig(entry.name);
|
|
@@ -3192,7 +2491,7 @@ var SleepManager = class {
|
|
|
3192
2491
|
const wakeAt = new Date(state.voluntaryWakeAt);
|
|
3193
2492
|
if (now >= wakeAt) {
|
|
3194
2493
|
this.initiateWake(entry.name).catch(
|
|
3195
|
-
(err) =>
|
|
2494
|
+
(err) => slog2.error(`failed voluntary wake for ${entry.name}`, logger_default.errorData(err))
|
|
3196
2495
|
);
|
|
3197
2496
|
continue;
|
|
3198
2497
|
}
|
|
@@ -3201,7 +2500,7 @@ var SleepManager = class {
|
|
|
3201
2500
|
const wakeAt = new Date(state.scheduledWakeAt);
|
|
3202
2501
|
if (now >= wakeAt) {
|
|
3203
2502
|
this.initiateWake(entry.name).catch(
|
|
3204
|
-
(err) =>
|
|
2503
|
+
(err) => slog2.error(`failed scheduled wake for ${entry.name}`, logger_default.errorData(err))
|
|
3205
2504
|
);
|
|
3206
2505
|
continue;
|
|
3207
2506
|
}
|
|
@@ -3209,7 +2508,7 @@ var SleepManager = class {
|
|
|
3209
2508
|
if (!state?.sleeping && entry.running) {
|
|
3210
2509
|
if (this.shouldSleep(config.schedule.sleep, epochMinute)) {
|
|
3211
2510
|
this.initiateSleep(entry.name).catch(
|
|
3212
|
-
(err) =>
|
|
2511
|
+
(err) => slog2.error(`failed to initiate sleep for ${entry.name}`, logger_default.errorData(err))
|
|
3213
2512
|
);
|
|
3214
2513
|
}
|
|
3215
2514
|
}
|
|
@@ -3222,7 +2521,7 @@ var SleepManager = class {
|
|
|
3222
2521
|
const prevMinute = Math.floor(prev.getTime() / 6e4);
|
|
3223
2522
|
return prevMinute === epochMinute;
|
|
3224
2523
|
} catch (err) {
|
|
3225
|
-
|
|
2524
|
+
slog2.warn(`invalid sleep cron "${cronExpr}"`, logger_default.errorData(err));
|
|
3226
2525
|
return false;
|
|
3227
2526
|
}
|
|
3228
2527
|
}
|
|
@@ -3248,7 +2547,7 @@ var SleepManager = class {
|
|
|
3248
2547
|
const sessionsDir = resolve8(dir, ".mind", "sessions");
|
|
3249
2548
|
if (existsSync5(sessionsDir)) {
|
|
3250
2549
|
const archiveDir = resolve8(sessionsDir, "archive");
|
|
3251
|
-
|
|
2550
|
+
mkdirSync4(archiveDir, { recursive: true });
|
|
3252
2551
|
for (const file of readdirSync2(sessionsDir)) {
|
|
3253
2552
|
if (file === "archive" || !file.endsWith(".json")) continue;
|
|
3254
2553
|
const src = resolve8(sessionsDir, file);
|
|
@@ -3257,14 +2556,14 @@ var SleepManager = class {
|
|
|
3257
2556
|
try {
|
|
3258
2557
|
renameSync(src, dest);
|
|
3259
2558
|
} catch (err) {
|
|
3260
|
-
|
|
2559
|
+
slog2.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
|
|
3261
2560
|
}
|
|
3262
2561
|
}
|
|
3263
2562
|
}
|
|
3264
2563
|
const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
|
|
3265
2564
|
if (existsSync5(piSessionsDir)) {
|
|
3266
2565
|
const archiveDir = resolve8(piSessionsDir, "archive");
|
|
3267
|
-
|
|
2566
|
+
mkdirSync4(archiveDir, { recursive: true });
|
|
3268
2567
|
for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
|
|
3269
2568
|
if (entry.name === "archive" || !entry.isDirectory()) continue;
|
|
3270
2569
|
const src = resolve8(piSessionsDir, entry.name);
|
|
@@ -3272,7 +2571,7 @@ var SleepManager = class {
|
|
|
3272
2571
|
try {
|
|
3273
2572
|
renameSync(src, dest);
|
|
3274
2573
|
} catch (err) {
|
|
3275
|
-
|
|
2574
|
+
slog2.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
|
|
3276
2575
|
}
|
|
3277
2576
|
}
|
|
3278
2577
|
}
|
|
@@ -3315,7 +2614,7 @@ var SleepManager = class {
|
|
|
3315
2614
|
});
|
|
3316
2615
|
return result.trim();
|
|
3317
2616
|
} catch (err) {
|
|
3318
|
-
|
|
2617
|
+
slog2.warn(`wake-context script failed for ${name}`, logger_default.errorData(err));
|
|
3319
2618
|
return "";
|
|
3320
2619
|
}
|
|
3321
2620
|
}
|
|
@@ -3329,7 +2628,7 @@ var SleepManager = class {
|
|
|
3329
2628
|
async buildQueuedSummary(name) {
|
|
3330
2629
|
try {
|
|
3331
2630
|
const db = await getDb();
|
|
3332
|
-
const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(
|
|
2631
|
+
const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
|
|
3333
2632
|
if (rows.length === 0) return "No messages arrived while you slept.";
|
|
3334
2633
|
const channelCounts = /* @__PURE__ */ new Map();
|
|
3335
2634
|
const senders = /* @__PURE__ */ new Set();
|
|
@@ -3342,7 +2641,7 @@ var SleepManager = class {
|
|
|
3342
2641
|
const senderNote = senders.size > 0 ? ` from ${[...senders].join(", ")}` : "";
|
|
3343
2642
|
return `${rows.length} message${rows.length === 1 ? "" : "s"} arrived while you slept${senderNote} (${parts.join(", ")}). They'll be delivered to your normal channels now.`;
|
|
3344
2643
|
} catch (err) {
|
|
3345
|
-
|
|
2644
|
+
slog2.error(`failed to build queued summary for ${name}`, logger_default.errorData(err));
|
|
3346
2645
|
return "Unable to check for queued messages \u2014 there may be messages waiting.";
|
|
3347
2646
|
}
|
|
3348
2647
|
}
|
|
@@ -3357,7 +2656,7 @@ var SleepManager = class {
|
|
|
3357
2656
|
} catch {
|
|
3358
2657
|
return;
|
|
3359
2658
|
}
|
|
3360
|
-
|
|
2659
|
+
slog2.warn(`orphan process found on port ${port} after sleep, killing`);
|
|
3361
2660
|
const execFileAsync = promisify(execFile);
|
|
3362
2661
|
try {
|
|
3363
2662
|
const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`, "-sTCP:LISTEN"]);
|
|
@@ -3368,7 +2667,7 @@ var SleepManager = class {
|
|
|
3368
2667
|
process.kill(pid, "SIGTERM");
|
|
3369
2668
|
} catch (err) {
|
|
3370
2669
|
if (err.code !== "ESRCH") {
|
|
3371
|
-
|
|
2670
|
+
slog2.warn(`failed to kill orphan pid ${pid}`, logger_default.errorData(err));
|
|
3372
2671
|
}
|
|
3373
2672
|
}
|
|
3374
2673
|
}
|
|
@@ -3400,7 +2699,7 @@ var SleepManager = class {
|
|
|
3400
2699
|
}
|
|
3401
2700
|
}
|
|
3402
2701
|
} catch (err) {
|
|
3403
|
-
|
|
2702
|
+
slog2.warn(`failed to kill orphan on port ${port} via /proc`, logger_default.errorData(err));
|
|
3404
2703
|
}
|
|
3405
2704
|
}
|
|
3406
2705
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
@@ -3410,7 +2709,7 @@ var SleepManager = class {
|
|
|
3410
2709
|
if (!state?.sleeping || !state.wokenByTrigger) return;
|
|
3411
2710
|
if (this.transitioning.has(event.mind)) return;
|
|
3412
2711
|
if (event.type === "mind_idle") {
|
|
3413
|
-
|
|
2712
|
+
slog2.info(`${event.mind} going back to sleep after trigger wake`);
|
|
3414
2713
|
state.wokenByTrigger = false;
|
|
3415
2714
|
this.transitioning.add(event.mind);
|
|
3416
2715
|
sleepMind(event.mind).then(() => this.archiveSessions(event.mind)).then(() => {
|
|
@@ -3419,27 +2718,27 @@ var SleepManager = class {
|
|
|
3419
2718
|
const sleepConfig = this.getSleepConfig(event.mind);
|
|
3420
2719
|
state.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
|
|
3421
2720
|
this.saveState();
|
|
3422
|
-
|
|
2721
|
+
slog2.info(`${event.mind} returned to sleep`);
|
|
3423
2722
|
}).catch((err) => {
|
|
3424
|
-
|
|
2723
|
+
slog2.error(`failed to return ${event.mind} to sleep`, logger_default.errorData(err));
|
|
3425
2724
|
}).finally(() => {
|
|
3426
2725
|
this.transitioning.delete(event.mind);
|
|
3427
2726
|
});
|
|
3428
2727
|
}
|
|
3429
2728
|
}
|
|
3430
2729
|
};
|
|
3431
|
-
var
|
|
2730
|
+
var instance6 = null;
|
|
3432
2731
|
function initSleepManager() {
|
|
3433
|
-
if (
|
|
3434
|
-
|
|
3435
|
-
return
|
|
2732
|
+
if (instance6) throw new Error("SleepManager already initialized");
|
|
2733
|
+
instance6 = new SleepManager();
|
|
2734
|
+
return instance6;
|
|
3436
2735
|
}
|
|
3437
2736
|
function getSleepManager() {
|
|
3438
|
-
if (!
|
|
3439
|
-
return
|
|
2737
|
+
if (!instance6) throw new Error("SleepManager not initialized \u2014 call initSleepManager() first");
|
|
2738
|
+
return instance6;
|
|
3440
2739
|
}
|
|
3441
2740
|
function getSleepManagerIfReady() {
|
|
3442
|
-
return
|
|
2741
|
+
return instance6;
|
|
3443
2742
|
}
|
|
3444
2743
|
|
|
3445
2744
|
export {
|
|
@@ -3459,36 +2758,14 @@ export {
|
|
|
3459
2758
|
deleteUser,
|
|
3460
2759
|
updateUserProfile,
|
|
3461
2760
|
migrateMindRoles,
|
|
3462
|
-
|
|
3463
|
-
|
|
2761
|
+
readVoluteConfig,
|
|
2762
|
+
writeVoluteConfig,
|
|
3464
2763
|
stopAllWatchers,
|
|
3465
2764
|
getCachedSites,
|
|
3466
2765
|
getCachedRecentPages,
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
initWebhook,
|
|
3471
|
-
subscribe2 as subscribe,
|
|
3472
|
-
publish2 as publish,
|
|
3473
|
-
createConversation,
|
|
3474
|
-
getConversation,
|
|
3475
|
-
getParticipants,
|
|
3476
|
-
isParticipant,
|
|
3477
|
-
listConversationsForUser,
|
|
3478
|
-
isParticipantOrOwner,
|
|
3479
|
-
deleteConversationForUser,
|
|
3480
|
-
addMessage,
|
|
3481
|
-
getMessages,
|
|
3482
|
-
getMessagesPaginated,
|
|
3483
|
-
listConversationsWithParticipants,
|
|
3484
|
-
findDMConversation,
|
|
3485
|
-
createChannel,
|
|
3486
|
-
getChannelByName,
|
|
3487
|
-
listChannels,
|
|
3488
|
-
joinChannel,
|
|
3489
|
-
leaveChannel,
|
|
3490
|
-
getUnreadCounts,
|
|
3491
|
-
markConversationRead,
|
|
2766
|
+
splitMessage,
|
|
2767
|
+
writeChannelEntry,
|
|
2768
|
+
resolveChannelId,
|
|
3492
2769
|
ensureSystemChannel,
|
|
3493
2770
|
joinSystemChannel,
|
|
3494
2771
|
announceToSystem,
|
|
@@ -3503,8 +2780,8 @@ export {
|
|
|
3503
2780
|
initSleepManager,
|
|
3504
2781
|
getSleepManager,
|
|
3505
2782
|
getSleepManagerIfReady,
|
|
3506
|
-
|
|
3507
|
-
publish3 as
|
|
2783
|
+
subscribe2 as subscribe,
|
|
2784
|
+
publish3 as publish,
|
|
3508
2785
|
getTypingMap,
|
|
3509
2786
|
publishTypingForChannels,
|
|
3510
2787
|
extractTextContent,
|
|
@@ -3512,6 +2789,5 @@ export {
|
|
|
3512
2789
|
getDeliveryManager,
|
|
3513
2790
|
recordInbound,
|
|
3514
2791
|
deliverMessage,
|
|
3515
|
-
initMailPoller
|
|
3516
|
-
getMailPoller
|
|
2792
|
+
initMailPoller
|
|
3517
2793
|
};
|