volute 0.28.0 → 0.29.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 +15 -5
- package/dist/api.d.ts +192 -9
- package/dist/{chat-M4SX42JD.js → chat-KTPOR2JT.js} +1 -1
- package/dist/chunk-A6TUJJ3L.js +19 -0
- package/dist/{chunk-AAPXKR5V.js → chunk-CMILSHZD.js} +138 -285
- package/dist/{chunk-K5NAC55T.js → chunk-CQ7SNKNI.js} +1 -1
- package/dist/{chunk-POSXWWTA.js → chunk-EHZKEMMV.js} +4 -4
- package/dist/{chunk-IAYBDWVG.js → chunk-FLZGS4QH.js} +145 -0
- package/dist/chunk-THUUIU3E.js +232 -0
- package/dist/cli.js +11 -8
- package/dist/clock-DGCBVGYA.js +259 -0
- package/dist/{cloud-sync-HDL6PHZI.js → cloud-sync-KILFGV5Q.js} +6 -5
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-M2K4253F.js → conversations-P5BL7RMX.js} +7 -1
- package/dist/create-DFCAGEE5.js +70 -0
- package/dist/{daemon-restart-G4B2OYAB.js → daemon-restart-UHOMICXT.js} +1 -1
- package/dist/daemon.js +181 -66
- package/dist/{message-delivery-HV3S6HZV.js → message-delivery-Q7VUMIEI.js} +10 -7
- package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-WRHFI3YW.js} +1 -1
- package/dist/{mind-manager-S6ILZVX3.js → mind-manager-P66HQDNE.js} +1 -1
- package/dist/{package-CG4RWUGP.js → package-OFKXNKJF.js} +1 -1
- package/dist/pages-watcher-P7QECRE2.js +21 -0
- package/dist/skills/dreaming/references/INSTALL.md +3 -17
- package/dist/skills/volute-mind/SKILL.md +43 -16
- package/dist/{sleep-manager-WMVG2VCL.js → sleep-manager-G4B5GW5P.js} +6 -5
- package/dist/{up-GM2JOH2Y.js → up-W6VAK2XE.js} +1 -1
- package/dist/{version-notify-JDUF4HQJ.js → version-notify-WDHRO3XD.js} +8 -7
- package/dist/web-assets/assets/index-BmKDnWDB.css +1 -0
- package/dist/web-assets/assets/index-CLJMx-GA.js +71 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/templates/_base/src/lib/logger.ts +10 -49
- package/templates/_base/src/lib/router.ts +1 -9
- package/templates/claude/src/lib/stream-consumer.ts +1 -4
- package/templates/pi/src/lib/event-handler.ts +1 -14
- package/dist/chunk-T6HKBWXZ.js +0 -23
- package/dist/create-D7J73A6H.js +0 -45
- package/dist/schedule-QTJMFATP.js +0 -154
- package/dist/web-assets/assets/index-BZGvToHi.css +0 -1
- package/dist/web-assets/assets/index-Cz4TrpzB.js +0 -75
- /package/dist/{chunk-SGVNFZHW.js → chunk-DUAUMCEE.js} +0 -0
- /package/dist/{pages-KJDJX4TA.js → pages-EUJR52AH.js} +0 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
startWatcher,
|
|
4
|
+
stopWatcher
|
|
5
|
+
} from "./chunk-THUUIU3E.js";
|
|
2
6
|
import {
|
|
3
7
|
addMessage,
|
|
4
8
|
createChannel,
|
|
@@ -6,10 +10,10 @@ import {
|
|
|
6
10
|
getParticipants,
|
|
7
11
|
joinChannel,
|
|
8
12
|
publish as publish2
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FLZGS4QH.js";
|
|
10
14
|
import {
|
|
11
15
|
markIdle
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CQ7SNKNI.js";
|
|
13
17
|
import {
|
|
14
18
|
broadcast,
|
|
15
19
|
publish,
|
|
@@ -21,7 +25,7 @@ import {
|
|
|
21
25
|
getPrompt,
|
|
22
26
|
loadJsonMap,
|
|
23
27
|
saveJsonMap
|
|
24
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-EHZKEMMV.js";
|
|
25
29
|
import {
|
|
26
30
|
logger_default
|
|
27
31
|
} from "./chunk-YUIHSKR6.js";
|
|
@@ -46,15 +50,15 @@ import {
|
|
|
46
50
|
// src/lib/daemon/sleep-manager.ts
|
|
47
51
|
import { execFile, spawn as spawnChild } from "child_process";
|
|
48
52
|
import {
|
|
49
|
-
existsSync as
|
|
53
|
+
existsSync as existsSync5,
|
|
50
54
|
mkdirSync as mkdirSync5,
|
|
51
|
-
readdirSync
|
|
55
|
+
readdirSync,
|
|
52
56
|
readFileSync as readFileSync6,
|
|
53
57
|
readlinkSync,
|
|
54
58
|
renameSync as renameSync2,
|
|
55
59
|
writeFileSync as writeFileSync5
|
|
56
60
|
} from "fs";
|
|
57
|
-
import { resolve as
|
|
61
|
+
import { resolve as resolve8 } from "path";
|
|
58
62
|
import { promisify } from "util";
|
|
59
63
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
60
64
|
import { and as and3, eq as eq3, inArray as inArray2 } from "drizzle-orm";
|
|
@@ -225,214 +229,9 @@ async function migrateMindRoles() {
|
|
|
225
229
|
await db.update(users).set({ role: "user" }).where(and(eq(users.user_type, "mind"), inArray(users.role, ["mind", "agent"])));
|
|
226
230
|
}
|
|
227
231
|
|
|
228
|
-
// src/lib/pages-watcher.ts
|
|
229
|
-
import { existsSync as existsSync2, readdirSync, statSync, watch } from "fs";
|
|
230
|
-
import { join, resolve as resolve2 } from "path";
|
|
231
|
-
var watchers = /* @__PURE__ */ new Map();
|
|
232
|
-
var homeWatchers = /* @__PURE__ */ new Map();
|
|
233
|
-
var debounceTimers = /* @__PURE__ */ new Map();
|
|
234
|
-
var sitesCache = null;
|
|
235
|
-
var recentPagesCache = null;
|
|
236
|
-
function startPagesWatcher(mindName, pagesDir) {
|
|
237
|
-
try {
|
|
238
|
-
const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
|
|
239
|
-
if (!filename || !filename.endsWith(".html")) return;
|
|
240
|
-
const key = `${mindName}:${filename}`;
|
|
241
|
-
const existing = debounceTimers.get(key);
|
|
242
|
-
if (existing) clearTimeout(existing);
|
|
243
|
-
debounceTimers.set(
|
|
244
|
-
key,
|
|
245
|
-
setTimeout(() => {
|
|
246
|
-
debounceTimers.delete(key);
|
|
247
|
-
invalidateCache();
|
|
248
|
-
publish({
|
|
249
|
-
type: "page_updated",
|
|
250
|
-
mind: mindName,
|
|
251
|
-
summary: `${mindName} updated ${filename}`,
|
|
252
|
-
metadata: { file: filename }
|
|
253
|
-
}).catch(
|
|
254
|
-
(err) => logger_default.error("failed to publish page_updated activity", logger_default.errorData(err))
|
|
255
|
-
);
|
|
256
|
-
}, 100)
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
watchers.set(mindName, watcher);
|
|
260
|
-
} catch (err) {
|
|
261
|
-
logger_default.warn(`failed to start pages watcher for ${mindName}`, logger_default.errorData(err));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function startWatcher(mindName) {
|
|
265
|
-
if (watchers.has(mindName)) return;
|
|
266
|
-
const pagesDir = resolve2(mindDir(mindName), "home", "public", "pages");
|
|
267
|
-
if (existsSync2(pagesDir)) {
|
|
268
|
-
startPagesWatcher(mindName, pagesDir);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
if (homeWatchers.has(mindName)) return;
|
|
272
|
-
const publicDir = resolve2(mindDir(mindName), "home", "public");
|
|
273
|
-
if (!existsSync2(publicDir)) return;
|
|
274
|
-
try {
|
|
275
|
-
const hw = watch(publicDir, (_eventType, filename) => {
|
|
276
|
-
if (filename !== "pages") return;
|
|
277
|
-
if (!existsSync2(pagesDir)) return;
|
|
278
|
-
hw.close();
|
|
279
|
-
homeWatchers.delete(mindName);
|
|
280
|
-
invalidateCache();
|
|
281
|
-
startPagesWatcher(mindName, pagesDir);
|
|
282
|
-
});
|
|
283
|
-
homeWatchers.set(mindName, hw);
|
|
284
|
-
} catch (err) {
|
|
285
|
-
logger_default.warn(`failed to start home watcher for ${mindName}`, logger_default.errorData(err));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
function stopWatcher(mindName) {
|
|
289
|
-
const watcher = watchers.get(mindName);
|
|
290
|
-
if (watcher) {
|
|
291
|
-
watcher.close();
|
|
292
|
-
watchers.delete(mindName);
|
|
293
|
-
}
|
|
294
|
-
const hw = homeWatchers.get(mindName);
|
|
295
|
-
if (hw) {
|
|
296
|
-
hw.close();
|
|
297
|
-
homeWatchers.delete(mindName);
|
|
298
|
-
}
|
|
299
|
-
for (const [key, timer] of debounceTimers) {
|
|
300
|
-
if (key.startsWith(`${mindName}:`)) {
|
|
301
|
-
clearTimeout(timer);
|
|
302
|
-
debounceTimers.delete(key);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function stopAllWatchers() {
|
|
307
|
-
for (const [, watcher] of watchers) {
|
|
308
|
-
watcher.close();
|
|
309
|
-
}
|
|
310
|
-
watchers.clear();
|
|
311
|
-
for (const [, hw] of homeWatchers) {
|
|
312
|
-
hw.close();
|
|
313
|
-
}
|
|
314
|
-
homeWatchers.clear();
|
|
315
|
-
for (const [, timer] of debounceTimers) {
|
|
316
|
-
clearTimeout(timer);
|
|
317
|
-
}
|
|
318
|
-
debounceTimers.clear();
|
|
319
|
-
invalidateCache();
|
|
320
|
-
}
|
|
321
|
-
function invalidateCache() {
|
|
322
|
-
sitesCache = null;
|
|
323
|
-
recentPagesCache = null;
|
|
324
|
-
}
|
|
325
|
-
function scanPagesDir(dir, urlPrefix) {
|
|
326
|
-
const pages = [];
|
|
327
|
-
let items;
|
|
328
|
-
try {
|
|
329
|
-
items = readdirSync(dir);
|
|
330
|
-
} catch {
|
|
331
|
-
return pages;
|
|
332
|
-
}
|
|
333
|
-
for (const item of items) {
|
|
334
|
-
if (item.startsWith(".")) continue;
|
|
335
|
-
const fullPath = resolve2(dir, item);
|
|
336
|
-
try {
|
|
337
|
-
const s = statSync(fullPath);
|
|
338
|
-
if (s.isFile() && item.endsWith(".html")) {
|
|
339
|
-
pages.push({
|
|
340
|
-
file: item,
|
|
341
|
-
modified: s.mtime.toISOString(),
|
|
342
|
-
url: `${urlPrefix}/${item}`
|
|
343
|
-
});
|
|
344
|
-
} else if (s.isDirectory()) {
|
|
345
|
-
const indexPath = resolve2(fullPath, "index.html");
|
|
346
|
-
if (existsSync2(indexPath)) {
|
|
347
|
-
const indexStat = statSync(indexPath);
|
|
348
|
-
pages.push({
|
|
349
|
-
file: join(item, "index.html"),
|
|
350
|
-
modified: indexStat.mtime.toISOString(),
|
|
351
|
-
url: `${urlPrefix}/${item}/`
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
} catch {
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
359
|
-
return pages;
|
|
360
|
-
}
|
|
361
|
-
async function buildSites() {
|
|
362
|
-
const sites = [];
|
|
363
|
-
const systemPagesDir = resolve2(voluteHome(), "shared", "pages");
|
|
364
|
-
if (existsSync2(systemPagesDir)) {
|
|
365
|
-
const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
|
|
366
|
-
if (systemPages.length > 0) {
|
|
367
|
-
sites.push({ name: "_system", label: "System", pages: systemPages });
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const entries = await readRegistry();
|
|
371
|
-
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
372
|
-
const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
|
|
373
|
-
if (!existsSync2(pagesDir)) continue;
|
|
374
|
-
const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
|
|
375
|
-
if (mindPages.length > 0) {
|
|
376
|
-
sites.push({ name: entry.name, label: entry.name, pages: mindPages });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return sites;
|
|
380
|
-
}
|
|
381
|
-
async function buildRecentPages() {
|
|
382
|
-
const entries = await readRegistry();
|
|
383
|
-
const pages = [];
|
|
384
|
-
for (const entry of entries) {
|
|
385
|
-
const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
|
|
386
|
-
if (!existsSync2(pagesDir)) continue;
|
|
387
|
-
let items;
|
|
388
|
-
try {
|
|
389
|
-
items = readdirSync(pagesDir);
|
|
390
|
-
} catch {
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
for (const item of items) {
|
|
394
|
-
if (item.startsWith(".")) continue;
|
|
395
|
-
const fullPath = resolve2(pagesDir, item);
|
|
396
|
-
try {
|
|
397
|
-
const s = statSync(fullPath);
|
|
398
|
-
if (s.isFile() && item.endsWith(".html")) {
|
|
399
|
-
pages.push({
|
|
400
|
-
mind: entry.name,
|
|
401
|
-
file: item,
|
|
402
|
-
modified: s.mtime.toISOString(),
|
|
403
|
-
url: `/pages/${entry.name}/${item}`
|
|
404
|
-
});
|
|
405
|
-
} else if (s.isDirectory()) {
|
|
406
|
-
const indexPath = resolve2(fullPath, "index.html");
|
|
407
|
-
if (existsSync2(indexPath)) {
|
|
408
|
-
const indexStat = statSync(indexPath);
|
|
409
|
-
pages.push({
|
|
410
|
-
mind: entry.name,
|
|
411
|
-
file: join(item, "index.html"),
|
|
412
|
-
modified: indexStat.mtime.toISOString(),
|
|
413
|
-
url: `/pages/${entry.name}/${item}/`
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
} catch {
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
422
|
-
return pages.slice(0, 10);
|
|
423
|
-
}
|
|
424
|
-
async function getCachedSites() {
|
|
425
|
-
if (!sitesCache) sitesCache = await buildSites();
|
|
426
|
-
return sitesCache;
|
|
427
|
-
}
|
|
428
|
-
async function getCachedRecentPages() {
|
|
429
|
-
if (!recentPagesCache) recentPagesCache = await buildRecentPages();
|
|
430
|
-
return recentPagesCache;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
232
|
// src/connectors/sdk.ts
|
|
434
|
-
import { existsSync as
|
|
435
|
-
import { join
|
|
233
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
234
|
+
import { join, resolve as resolve2 } from "path";
|
|
436
235
|
function splitMessage(text, maxLength) {
|
|
437
236
|
const chunks = [];
|
|
438
237
|
while (text.length > maxLength) {
|
|
@@ -445,8 +244,8 @@ function splitMessage(text, maxLength) {
|
|
|
445
244
|
return chunks;
|
|
446
245
|
}
|
|
447
246
|
function readChannelMap(mindName) {
|
|
448
|
-
const filePath =
|
|
449
|
-
if (!
|
|
247
|
+
const filePath = join(stateDir(mindName), "channels.json");
|
|
248
|
+
if (!existsSync2(filePath)) return {};
|
|
450
249
|
try {
|
|
451
250
|
return JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
452
251
|
} catch (err) {
|
|
@@ -457,7 +256,7 @@ function readChannelMap(mindName) {
|
|
|
457
256
|
function writeChannelEntry(mindName, slug, entry) {
|
|
458
257
|
const dir = stateDir(mindName);
|
|
459
258
|
mkdirSync2(dir, { recursive: true });
|
|
460
|
-
const filePath =
|
|
259
|
+
const filePath = join(dir, "channels.json");
|
|
461
260
|
const map = readChannelMap(mindName);
|
|
462
261
|
map[slug] = entry;
|
|
463
262
|
writeFileSync2(filePath, JSON.stringify(map, null, 2) + "\n");
|
|
@@ -501,7 +300,7 @@ function publish3(mind, event) {
|
|
|
501
300
|
|
|
502
301
|
// src/lib/delivery/delivery-manager.ts
|
|
503
302
|
import { readFile, realpath } from "fs/promises";
|
|
504
|
-
import { extname, resolve as
|
|
303
|
+
import { extname, resolve as resolve4 } from "path";
|
|
505
304
|
import { and as and2, eq as eq2, sql } from "drizzle-orm";
|
|
506
305
|
|
|
507
306
|
// src/lib/typing.ts
|
|
@@ -595,8 +394,8 @@ function publishTypingForChannels(channels, map) {
|
|
|
595
394
|
}
|
|
596
395
|
|
|
597
396
|
// src/lib/delivery/delivery-router.ts
|
|
598
|
-
import { readFileSync as readFileSync3, statSync
|
|
599
|
-
import { resolve as
|
|
397
|
+
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
398
|
+
import { resolve as resolve3 } from "path";
|
|
600
399
|
function extractTextContent(content) {
|
|
601
400
|
if (typeof content === "string") return content;
|
|
602
401
|
if (Array.isArray(content)) {
|
|
@@ -609,7 +408,7 @@ var statCheckCache = /* @__PURE__ */ new Map();
|
|
|
609
408
|
var STAT_TTL_MS = 5e3;
|
|
610
409
|
var dlog = logger_default.child("delivery-router");
|
|
611
410
|
function configPath(mindName) {
|
|
612
|
-
return
|
|
411
|
+
return resolve3(mindDir(mindName), "home/.config/routes.json");
|
|
613
412
|
}
|
|
614
413
|
function getRoutingConfig(mindName) {
|
|
615
414
|
const path = configPath(mindName);
|
|
@@ -621,7 +420,7 @@ function getRoutingConfig(mindName) {
|
|
|
621
420
|
}
|
|
622
421
|
let mtime;
|
|
623
422
|
try {
|
|
624
|
-
mtime =
|
|
423
|
+
mtime = statSync(path).mtimeMs;
|
|
625
424
|
} catch {
|
|
626
425
|
configCache.delete(mindName);
|
|
627
426
|
statCheckCache.delete(mindName);
|
|
@@ -1294,8 +1093,8 @@ var DeliveryManager = class {
|
|
|
1294
1093
|
const dir = mindDir(p.username);
|
|
1295
1094
|
const config = readVoluteConfig(dir);
|
|
1296
1095
|
if (!config?.profile?.avatar) continue;
|
|
1297
|
-
filePath =
|
|
1298
|
-
const homeDir =
|
|
1096
|
+
filePath = resolve4(dir, "home", config.profile.avatar);
|
|
1097
|
+
const homeDir = resolve4(dir, "home");
|
|
1299
1098
|
if (!filePath.startsWith(`${homeDir}/`)) {
|
|
1300
1099
|
dlog2.warn(`avatar path for ${p.username} escapes home directory, skipping`);
|
|
1301
1100
|
continue;
|
|
@@ -1314,7 +1113,7 @@ var DeliveryManager = class {
|
|
|
1314
1113
|
throw err;
|
|
1315
1114
|
}
|
|
1316
1115
|
} else {
|
|
1317
|
-
filePath =
|
|
1116
|
+
filePath = resolve4(voluteHome(), "avatars", p.avatar);
|
|
1318
1117
|
}
|
|
1319
1118
|
const ext = extname(filePath).toLowerCase();
|
|
1320
1119
|
const mimeMap = {
|
|
@@ -1410,6 +1209,12 @@ async function recordInbound(mind, channel, sender, content) {
|
|
|
1410
1209
|
content: content ?? void 0
|
|
1411
1210
|
});
|
|
1412
1211
|
}
|
|
1212
|
+
function resolveSleepAction(sleepBehavior, wokenByTrigger, wakeTriggerMatches) {
|
|
1213
|
+
if (sleepBehavior === "skip") return "skip";
|
|
1214
|
+
if (sleepBehavior === "trigger-wake" && !wokenByTrigger) return "queue-and-wake";
|
|
1215
|
+
if (!sleepBehavior && wakeTriggerMatches) return "queue-and-wake";
|
|
1216
|
+
return "queue";
|
|
1217
|
+
}
|
|
1413
1218
|
async function deliverMessage(mindName, payload) {
|
|
1414
1219
|
try {
|
|
1415
1220
|
const baseName = await getBaseName(mindName);
|
|
@@ -1422,11 +1227,21 @@ async function deliverMessage(mindName, payload) {
|
|
|
1422
1227
|
await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
|
|
1423
1228
|
const sleepManager = getSleepManagerIfReady();
|
|
1424
1229
|
if (sleepManager?.isSleeping(baseName)) {
|
|
1425
|
-
|
|
1426
|
-
|
|
1230
|
+
const sleepState = sleepManager.getState(baseName);
|
|
1231
|
+
const action = resolveSleepAction(
|
|
1232
|
+
payload.whileSleeping,
|
|
1233
|
+
sleepState.wokenByTrigger,
|
|
1234
|
+
sleepManager.checkWakeTrigger(baseName, payload)
|
|
1235
|
+
);
|
|
1236
|
+
if (action === "skip") {
|
|
1237
|
+
dlog3.info(
|
|
1238
|
+
`skipped delivery to ${baseName} (sleeping, whileSleeping=skip, channel=${payload.channel})`
|
|
1239
|
+
);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
await sleepManager.queueSleepMessage(baseName, payload);
|
|
1243
|
+
if (action === "queue-and-wake") {
|
|
1427
1244
|
sleepManager.initiateWake(baseName, { trigger: { channel: payload.channel } }).catch((err) => dlog3.warn(`failed to trigger-wake ${baseName}`, logger_default.errorData(err)));
|
|
1428
|
-
} else {
|
|
1429
|
-
await sleepManager.queueSleepMessage(baseName, payload);
|
|
1430
1245
|
}
|
|
1431
1246
|
return;
|
|
1432
1247
|
}
|
|
@@ -1472,7 +1287,7 @@ async function announceToSystem(text) {
|
|
|
1472
1287
|
platformId: channelId,
|
|
1473
1288
|
platform: "volute",
|
|
1474
1289
|
name: SYSTEM_CHANNEL_NAME,
|
|
1475
|
-
type: "
|
|
1290
|
+
type: "channel"
|
|
1476
1291
|
});
|
|
1477
1292
|
} catch (err) {
|
|
1478
1293
|
logger_default.warn(`failed to write channel entry for ${mind.username}`, logger_default.errorData(err));
|
|
@@ -1493,27 +1308,27 @@ async function announceToSystem(text) {
|
|
|
1493
1308
|
|
|
1494
1309
|
// src/lib/systems-config.ts
|
|
1495
1310
|
import {
|
|
1496
|
-
existsSync as
|
|
1311
|
+
existsSync as existsSync3,
|
|
1497
1312
|
mkdirSync as mkdirSync3,
|
|
1498
1313
|
readFileSync as readFileSync4,
|
|
1499
1314
|
renameSync,
|
|
1500
1315
|
unlinkSync,
|
|
1501
1316
|
writeFileSync as writeFileSync3
|
|
1502
1317
|
} from "fs";
|
|
1503
|
-
import { resolve as
|
|
1318
|
+
import { resolve as resolve5 } from "path";
|
|
1504
1319
|
var DEFAULT_API_URL = "https://volute.systems";
|
|
1505
1320
|
function configPath2() {
|
|
1506
|
-
return
|
|
1321
|
+
return resolve5(voluteSystemDir(), "systems.json");
|
|
1507
1322
|
}
|
|
1508
1323
|
function migrateIfNeeded() {
|
|
1509
1324
|
const target = configPath2();
|
|
1510
|
-
if (
|
|
1325
|
+
if (existsSync3(target)) return;
|
|
1511
1326
|
const oldPaths = [
|
|
1512
|
-
|
|
1513
|
-
|
|
1327
|
+
resolve5(voluteUserHome(), "systems.json"),
|
|
1328
|
+
resolve5(voluteHome(), "systems.json")
|
|
1514
1329
|
];
|
|
1515
1330
|
for (const old of oldPaths) {
|
|
1516
|
-
if (old !== target &&
|
|
1331
|
+
if (old !== target && existsSync3(old)) {
|
|
1517
1332
|
try {
|
|
1518
1333
|
mkdirSync3(voluteSystemDir(), { recursive: true });
|
|
1519
1334
|
renameSync(old, target);
|
|
@@ -1526,7 +1341,7 @@ function migrateIfNeeded() {
|
|
|
1526
1341
|
function readSystemsConfig() {
|
|
1527
1342
|
migrateIfNeeded();
|
|
1528
1343
|
const path = configPath2();
|
|
1529
|
-
if (!
|
|
1344
|
+
if (!existsSync3(path)) return null;
|
|
1530
1345
|
const raw = readFileSync4(path, "utf-8");
|
|
1531
1346
|
let data;
|
|
1532
1347
|
try {
|
|
@@ -1793,7 +1608,7 @@ async function ensureMailAddress(mindName) {
|
|
|
1793
1608
|
}
|
|
1794
1609
|
|
|
1795
1610
|
// src/lib/daemon/scheduler.ts
|
|
1796
|
-
import { resolve as
|
|
1611
|
+
import { resolve as resolve6 } from "path";
|
|
1797
1612
|
import { CronExpressionParser } from "cron-parser";
|
|
1798
1613
|
var slog = logger_default.child("scheduler");
|
|
1799
1614
|
var Scheduler = class {
|
|
@@ -1802,7 +1617,7 @@ var Scheduler = class {
|
|
|
1802
1617
|
lastFired = /* @__PURE__ */ new Map();
|
|
1803
1618
|
// "mind:scheduleId" → epoch minute
|
|
1804
1619
|
get statePath() {
|
|
1805
|
-
return
|
|
1620
|
+
return resolve6(voluteSystemDir(), "scheduler-state.json");
|
|
1806
1621
|
}
|
|
1807
1622
|
start() {
|
|
1808
1623
|
this.loadState();
|
|
@@ -1853,6 +1668,15 @@ var Scheduler = class {
|
|
|
1853
1668
|
shouldFire(schedule, epochMinute, mind, cronCache) {
|
|
1854
1669
|
const key = `${mind}:${schedule.id}`;
|
|
1855
1670
|
if (this.lastFired.get(key) === epochMinute) return false;
|
|
1671
|
+
if (schedule.fireAt) {
|
|
1672
|
+
const fireTime = Math.floor(new Date(schedule.fireAt).getTime() / 6e4);
|
|
1673
|
+
if (epochMinute >= fireTime) {
|
|
1674
|
+
this.lastFired.set(key, epochMinute);
|
|
1675
|
+
return true;
|
|
1676
|
+
}
|
|
1677
|
+
return false;
|
|
1678
|
+
}
|
|
1679
|
+
if (!schedule.cron) return false;
|
|
1856
1680
|
let prevMinute = cronCache.get(schedule.cron);
|
|
1857
1681
|
if (prevMinute === void 0) {
|
|
1858
1682
|
try {
|
|
@@ -1872,22 +1696,10 @@ var Scheduler = class {
|
|
|
1872
1696
|
return false;
|
|
1873
1697
|
}
|
|
1874
1698
|
async fire(mindName, schedule) {
|
|
1875
|
-
const sleepManager = getSleepManagerIfReady();
|
|
1876
|
-
const sleepState = sleepManager?.getState(mindName);
|
|
1877
|
-
if (sleepState?.sleeping) {
|
|
1878
|
-
if (schedule.skipWhenSleeping) {
|
|
1879
|
-
slog.info(`skipped "${schedule.id}" for ${mindName} (sleeping)`);
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1882
|
-
if (sleepState.wokenByTrigger) {
|
|
1883
|
-
slog.info(`skipped "${schedule.id}" for ${mindName} (trigger-woken)`);
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
1699
|
try {
|
|
1888
1700
|
let text;
|
|
1889
1701
|
if (schedule.script) {
|
|
1890
|
-
const homeDir =
|
|
1702
|
+
const homeDir = resolve6(mindDir(mindName), "home");
|
|
1891
1703
|
try {
|
|
1892
1704
|
const output = await this.runScript(schedule.script, homeDir, mindName);
|
|
1893
1705
|
if (!output.trim()) {
|
|
@@ -1907,16 +1719,46 @@ ${stderr}` : ""}`;
|
|
|
1907
1719
|
slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
|
|
1908
1720
|
return;
|
|
1909
1721
|
}
|
|
1722
|
+
const whileSleeping = schedule.whileSleeping ?? (schedule.skipWhenSleeping ? "skip" : void 0);
|
|
1910
1723
|
await this.deliver(mindName, {
|
|
1911
1724
|
content: [{ type: "text", text }],
|
|
1912
1725
|
channel: schedule.channel ?? "system:scheduler",
|
|
1913
|
-
sender: schedule.id
|
|
1726
|
+
sender: schedule.id,
|
|
1727
|
+
whileSleeping
|
|
1914
1728
|
});
|
|
1915
1729
|
slog.info(`fired "${schedule.id}" for ${mindName}`);
|
|
1730
|
+
if (schedule.fireAt) {
|
|
1731
|
+
this.removeSchedule(mindName, schedule.id);
|
|
1732
|
+
}
|
|
1916
1733
|
} catch (err) {
|
|
1917
1734
|
slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
|
|
1918
1735
|
}
|
|
1919
1736
|
}
|
|
1737
|
+
removeSchedule(mindName, scheduleId) {
|
|
1738
|
+
const memSchedules = this.schedules.get(mindName);
|
|
1739
|
+
if (memSchedules) {
|
|
1740
|
+
const filtered = memSchedules.filter((s) => s.id !== scheduleId);
|
|
1741
|
+
if (filtered.length > 0) {
|
|
1742
|
+
this.schedules.set(mindName, filtered);
|
|
1743
|
+
} else {
|
|
1744
|
+
this.schedules.delete(mindName);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
const dir = mindDir(mindName);
|
|
1749
|
+
const config = readVoluteConfig(dir);
|
|
1750
|
+
if (!config?.schedules) return;
|
|
1751
|
+
config.schedules = config.schedules.filter((s) => s.id !== scheduleId);
|
|
1752
|
+
if (config.schedules.length === 0) config.schedules = void 0;
|
|
1753
|
+
writeVoluteConfig(dir, config);
|
|
1754
|
+
slog.info(`removed one-time schedule "${scheduleId}" for ${mindName}`);
|
|
1755
|
+
} catch (err) {
|
|
1756
|
+
slog.error(
|
|
1757
|
+
`failed to persist removal of schedule "${scheduleId}" for ${mindName} (removed from memory)`,
|
|
1758
|
+
logger_default.errorData(err)
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1920
1762
|
runScript(script, cwd, mindName) {
|
|
1921
1763
|
return exec("bash", ["-c", script], { cwd, mindName });
|
|
1922
1764
|
}
|
|
@@ -1936,8 +1778,8 @@ function getScheduler() {
|
|
|
1936
1778
|
}
|
|
1937
1779
|
|
|
1938
1780
|
// src/lib/daemon/token-budget.ts
|
|
1939
|
-
import { existsSync as
|
|
1940
|
-
import { resolve as
|
|
1781
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1782
|
+
import { resolve as resolve7 } from "path";
|
|
1941
1783
|
var tlog = logger_default.child("token-budget");
|
|
1942
1784
|
var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
|
|
1943
1785
|
var MAX_QUEUE_SIZE = 100;
|
|
@@ -2055,7 +1897,7 @@ var TokenBudget = class {
|
|
|
2055
1897
|
this.dirty.clear();
|
|
2056
1898
|
}
|
|
2057
1899
|
budgetStatePath(mind) {
|
|
2058
|
-
return
|
|
1900
|
+
return resolve7(stateDir(mind), "budget.json");
|
|
2059
1901
|
}
|
|
2060
1902
|
saveBudgetState(mind, state) {
|
|
2061
1903
|
try {
|
|
@@ -2076,7 +1918,7 @@ var TokenBudget = class {
|
|
|
2076
1918
|
loadBudgetState(mind) {
|
|
2077
1919
|
try {
|
|
2078
1920
|
const path = this.budgetStatePath(mind);
|
|
2079
|
-
if (!
|
|
1921
|
+
if (!existsSync4(path)) return null;
|
|
2080
1922
|
const data = JSON.parse(readFileSync5(path, "utf-8"));
|
|
2081
1923
|
if (typeof data.periodStart !== "number" || typeof data.tokensUsed !== "number") return null;
|
|
2082
1924
|
return {
|
|
@@ -2146,6 +1988,7 @@ async function startMindFull(name) {
|
|
|
2146
1988
|
if (!entry || entry.stage === "seed") return;
|
|
2147
1989
|
const dir = mindDir(baseName);
|
|
2148
1990
|
getScheduler().loadSchedules(baseName);
|
|
1991
|
+
getSleepManagerIfReady()?.loadSleepConfig(baseName);
|
|
2149
1992
|
ensureMailAddress(baseName).catch(
|
|
2150
1993
|
(err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
|
|
2151
1994
|
);
|
|
@@ -2238,8 +2081,9 @@ var SleepManager = class {
|
|
|
2238
2081
|
interval = null;
|
|
2239
2082
|
unsubActivity = null;
|
|
2240
2083
|
transitioning = /* @__PURE__ */ new Set();
|
|
2084
|
+
sleepConfigs = /* @__PURE__ */ new Map();
|
|
2241
2085
|
get statePath() {
|
|
2242
|
-
return
|
|
2086
|
+
return resolve8(voluteSystemDir(), "sleep-state.json");
|
|
2243
2087
|
}
|
|
2244
2088
|
start() {
|
|
2245
2089
|
this.loadState();
|
|
@@ -2255,7 +2099,7 @@ var SleepManager = class {
|
|
|
2255
2099
|
// --- State persistence ---
|
|
2256
2100
|
loadState() {
|
|
2257
2101
|
try {
|
|
2258
|
-
if (
|
|
2102
|
+
if (existsSync5(this.statePath)) {
|
|
2259
2103
|
const data = JSON.parse(readFileSync6(this.statePath, "utf-8"));
|
|
2260
2104
|
for (const [name, state] of Object.entries(data)) {
|
|
2261
2105
|
state.triggerWakeHistory ??= [];
|
|
@@ -2299,9 +2143,21 @@ var SleepManager = class {
|
|
|
2299
2143
|
slog2.info(`${name} trigger-wake converted to full wake`);
|
|
2300
2144
|
}
|
|
2301
2145
|
getSleepConfig(name) {
|
|
2146
|
+
if (this.sleepConfigs.has(name)) {
|
|
2147
|
+
return this.sleepConfigs.get(name) ?? null;
|
|
2148
|
+
}
|
|
2149
|
+
const config = this.loadSleepConfig(name);
|
|
2150
|
+
return config;
|
|
2151
|
+
}
|
|
2152
|
+
loadSleepConfig(name) {
|
|
2302
2153
|
const dir = mindDir(name);
|
|
2303
2154
|
const config = readVoluteConfig(dir);
|
|
2304
|
-
|
|
2155
|
+
const sleepConfig = config?.sleep ?? null;
|
|
2156
|
+
this.sleepConfigs.set(name, sleepConfig);
|
|
2157
|
+
return sleepConfig;
|
|
2158
|
+
}
|
|
2159
|
+
invalidateSleepConfig(name) {
|
|
2160
|
+
this.sleepConfigs.delete(name);
|
|
2305
2161
|
}
|
|
2306
2162
|
/**
|
|
2307
2163
|
* Put a mind to sleep. Sends pre-sleep message, waits for completion,
|
|
@@ -2321,8 +2177,7 @@ var SleepManager = class {
|
|
|
2321
2177
|
if (!entry) return;
|
|
2322
2178
|
const sleepConfig = this.getSleepConfig(name);
|
|
2323
2179
|
const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
|
|
2324
|
-
const
|
|
2325
|
-
const preSleepMsg = await getPrompt("pre_sleep", { wakeTime, queuedInfo });
|
|
2180
|
+
const preSleepMsg = await getPrompt("pre_sleep", { wakeTime });
|
|
2326
2181
|
try {
|
|
2327
2182
|
const db = await getDb();
|
|
2328
2183
|
await db.insert(mindHistory).values({
|
|
@@ -2492,7 +2347,7 @@ var SleepManager = class {
|
|
|
2492
2347
|
const db = await getDb();
|
|
2493
2348
|
const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
|
|
2494
2349
|
if (rows.length === 0) return 0;
|
|
2495
|
-
const { deliverMessage: deliverMessage2 } = await import("./message-delivery-
|
|
2350
|
+
const { deliverMessage: deliverMessage2 } = await import("./message-delivery-Q7VUMIEI.js");
|
|
2496
2351
|
const delivered = [];
|
|
2497
2352
|
for (const row of rows) {
|
|
2498
2353
|
try {
|
|
@@ -2592,17 +2447,17 @@ var SleepManager = class {
|
|
|
2592
2447
|
}
|
|
2593
2448
|
}
|
|
2594
2449
|
async waitForIdle(name, timeoutMs) {
|
|
2595
|
-
return new Promise((
|
|
2450
|
+
return new Promise((resolve9) => {
|
|
2596
2451
|
const timeout = setTimeout(() => {
|
|
2597
2452
|
unsub();
|
|
2598
|
-
|
|
2453
|
+
resolve9();
|
|
2599
2454
|
}, timeoutMs);
|
|
2600
2455
|
const unsub = subscribe((event) => {
|
|
2601
2456
|
if (event.mind !== name) return;
|
|
2602
2457
|
if (event.type === "mind_done" || event.type === "mind_idle") {
|
|
2603
2458
|
clearTimeout(timeout);
|
|
2604
2459
|
unsub();
|
|
2605
|
-
|
|
2460
|
+
resolve9();
|
|
2606
2461
|
}
|
|
2607
2462
|
});
|
|
2608
2463
|
});
|
|
@@ -2610,15 +2465,15 @@ var SleepManager = class {
|
|
|
2610
2465
|
async archiveSessions(name) {
|
|
2611
2466
|
const dir = mindDir(name);
|
|
2612
2467
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
2613
|
-
const sessionsDir =
|
|
2614
|
-
if (
|
|
2615
|
-
const archiveDir =
|
|
2468
|
+
const sessionsDir = resolve8(dir, ".mind", "sessions");
|
|
2469
|
+
if (existsSync5(sessionsDir)) {
|
|
2470
|
+
const archiveDir = resolve8(sessionsDir, "archive");
|
|
2616
2471
|
mkdirSync5(archiveDir, { recursive: true });
|
|
2617
|
-
for (const file of
|
|
2472
|
+
for (const file of readdirSync(sessionsDir)) {
|
|
2618
2473
|
if (file === "archive" || !file.endsWith(".json")) continue;
|
|
2619
|
-
const src =
|
|
2474
|
+
const src = resolve8(sessionsDir, file);
|
|
2620
2475
|
const base = file.replace(/\.json$/, "");
|
|
2621
|
-
const dest =
|
|
2476
|
+
const dest = resolve8(archiveDir, `${base}-${timestamp}.json`);
|
|
2622
2477
|
try {
|
|
2623
2478
|
renameSync2(src, dest);
|
|
2624
2479
|
} catch (err) {
|
|
@@ -2626,14 +2481,14 @@ var SleepManager = class {
|
|
|
2626
2481
|
}
|
|
2627
2482
|
}
|
|
2628
2483
|
}
|
|
2629
|
-
const piSessionsDir =
|
|
2630
|
-
if (
|
|
2631
|
-
const archiveDir =
|
|
2484
|
+
const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
|
|
2485
|
+
if (existsSync5(piSessionsDir)) {
|
|
2486
|
+
const archiveDir = resolve8(piSessionsDir, "archive");
|
|
2632
2487
|
mkdirSync5(archiveDir, { recursive: true });
|
|
2633
|
-
for (const entry of
|
|
2488
|
+
for (const entry of readdirSync(piSessionsDir, { withFileTypes: true })) {
|
|
2634
2489
|
if (entry.name === "archive" || !entry.isDirectory()) continue;
|
|
2635
|
-
const src =
|
|
2636
|
-
const dest =
|
|
2490
|
+
const src = resolve8(piSessionsDir, entry.name);
|
|
2491
|
+
const dest = resolve8(archiveDir, `${entry.name}-${timestamp}`);
|
|
2637
2492
|
try {
|
|
2638
2493
|
renameSync2(src, dest);
|
|
2639
2494
|
} catch (err) {
|
|
@@ -2643,8 +2498,8 @@ var SleepManager = class {
|
|
|
2643
2498
|
}
|
|
2644
2499
|
}
|
|
2645
2500
|
async runWakeContextScript(name, sleepingSince, duration) {
|
|
2646
|
-
const scriptPath =
|
|
2647
|
-
if (!
|
|
2501
|
+
const scriptPath = resolve8(mindDir(name), "home", ".config", "hooks", "wake-context.sh");
|
|
2502
|
+
if (!existsSync5(scriptPath)) return "";
|
|
2648
2503
|
const input = JSON.stringify({
|
|
2649
2504
|
sleepingSince,
|
|
2650
2505
|
duration,
|
|
@@ -2748,9 +2603,9 @@ var SleepManager = class {
|
|
|
2748
2603
|
if (fields[3] !== "0A") continue;
|
|
2749
2604
|
const inode = parseInt(fields[9], 10);
|
|
2750
2605
|
if (!inode) continue;
|
|
2751
|
-
for (const pidDir of
|
|
2606
|
+
for (const pidDir of readdirSync("/proc").filter((f) => /^\d+$/.test(f))) {
|
|
2752
2607
|
try {
|
|
2753
|
-
const fds =
|
|
2608
|
+
const fds = readdirSync(`/proc/${pidDir}/fd`);
|
|
2754
2609
|
for (const fd of fds) {
|
|
2755
2610
|
try {
|
|
2756
2611
|
const link = readlinkSync(`/proc/${pidDir}/fd/${fd}`);
|
|
@@ -2826,9 +2681,6 @@ export {
|
|
|
2826
2681
|
migrateMindRoles,
|
|
2827
2682
|
readVoluteConfig,
|
|
2828
2683
|
writeVoluteConfig,
|
|
2829
|
-
stopAllWatchers,
|
|
2830
|
-
getCachedSites,
|
|
2831
|
-
getCachedRecentPages,
|
|
2832
2684
|
splitMessage,
|
|
2833
2685
|
writeChannelEntry,
|
|
2834
2686
|
resolveChannelId,
|
|
@@ -2854,6 +2706,7 @@ export {
|
|
|
2854
2706
|
initDeliveryManager,
|
|
2855
2707
|
getDeliveryManager,
|
|
2856
2708
|
recordInbound,
|
|
2709
|
+
resolveSleepAction,
|
|
2857
2710
|
deliverMessage,
|
|
2858
2711
|
readSystemsConfig,
|
|
2859
2712
|
writeSystemsConfig,
|