volute 0.27.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.
Files changed (81) hide show
  1. package/README.md +20 -10
  2. package/dist/accept-666DIZX2.js +41 -0
  3. package/dist/api.d.ts +342 -143
  4. package/dist/{chat-MHJ3L6JQ.js → chat-KTPOR2JT.js} +18 -8
  5. package/dist/chunk-A6TUJJ3L.js +19 -0
  6. package/dist/{chunk-OQZH4PBB.js → chunk-CMILSHZD.js} +199 -277
  7. package/dist/{chunk-K5NAC55T.js → chunk-CQ7SNKNI.js} +1 -1
  8. package/dist/{chunk-PHSAT7YL.js → chunk-EHZKEMMV.js} +5 -5
  9. package/dist/{chunk-IAYBDWVG.js → chunk-FLZGS4QH.js} +145 -0
  10. package/dist/{chunk-USUXRNVD.js → chunk-J4IBNXGJ.js} +0 -2
  11. package/dist/chunk-MD4C26II.js +128 -0
  12. package/dist/{chunk-4WXYUOAK.js → chunk-NI5FFCCS.js} +8 -1
  13. package/dist/{chunk-JKOWNZ4P.js → chunk-P72MVS4R.js} +1 -40
  14. package/dist/chunk-THUUIU3E.js +232 -0
  15. package/dist/cli.js +21 -30
  16. package/dist/clock-DGCBVGYA.js +259 -0
  17. package/dist/{cloud-sync-T7M3ESC3.js → cloud-sync-KILFGV5Q.js} +7 -7
  18. package/dist/connectors/discord-bridge.js +1 -1
  19. package/dist/connectors/slack-bridge.js +1 -1
  20. package/dist/connectors/telegram-bridge.js +1 -1
  21. package/dist/{conversations-M2K4253F.js → conversations-P5BL7RMX.js} +7 -1
  22. package/dist/create-DFCAGEE5.js +70 -0
  23. package/dist/{daemon-restart-M2QTYMEG.js → daemon-restart-UHOMICXT.js} +1 -1
  24. package/dist/daemon.js +715 -661
  25. package/dist/files-M546TKVN.js +46 -0
  26. package/dist/{login-XX37I52P.js → login-BKP3AFWN.js} +7 -17
  27. package/dist/logout-IQK7FNEK.js +20 -0
  28. package/dist/{message-delivery-LDXLGERA.js → message-delivery-Q7VUMIEI.js} +11 -9
  29. package/dist/{mind-DI33C74K.js → mind-S5V6CK5W.js} +8 -13
  30. package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-WRHFI3YW.js} +1 -1
  31. package/dist/mind-list-UPJ75GPI.js +29 -0
  32. package/dist/{mind-manager-M6EMUW5I.js → mind-manager-P66HQDNE.js} +2 -2
  33. package/dist/mind-status-TK5AETEM.js +55 -0
  34. package/dist/{package-7WY6VKU3.js → package-OFKXNKJF.js} +1 -1
  35. package/dist/{pages-6EBS6CBR.js → pages-EUJR52AH.js} +5 -5
  36. package/dist/pages-watcher-P7QECRE2.js +21 -0
  37. package/dist/{publish-66UB2ZFY.js → publish-ZZB33WP4.js} +6 -17
  38. package/dist/{register-6B2CXTYM.js → register-CHREOMJ3.js} +5 -24
  39. package/dist/reject-LXIZFJ4Q.js +39 -0
  40. package/dist/{sandbox-TGBX22DS.js → sandbox-5BW5HPXM.js} +1 -1
  41. package/dist/{send-ZNCJDSRP.js → send-TAOEZ4NH.js} +64 -6
  42. package/dist/skills/dreaming/references/INSTALL.md +3 -17
  43. package/dist/skills/shared-files/SKILL.md +44 -0
  44. package/dist/skills/shared-files/scripts/merge.ts +72 -0
  45. package/dist/skills/shared-files/scripts/pull.ts +52 -0
  46. package/dist/skills/volute-mind/SKILL.md +48 -22
  47. package/dist/{sleep-manager-MWYHM5HV.js → sleep-manager-G4B5GW5P.js} +7 -7
  48. package/dist/{sprout-IJVVKSJ2.js → sprout-UNT7LKKE.js} +1 -1
  49. package/dist/{status-77YEPHMW.js → status-NQJYR4BG.js} +45 -1
  50. package/dist/{status-THLOBLWG.js → status-S7UUPNRW.js} +3 -13
  51. package/dist/systems-SMEFSHTA.js +60 -0
  52. package/dist/{up-NKSMXBWR.js → up-W6VAK2XE.js} +1 -1
  53. package/dist/{version-notify-5Z4MNR6M.js → version-notify-WDHRO3XD.js} +11 -11
  54. package/dist/web-assets/assets/index-BmKDnWDB.css +1 -0
  55. package/dist/web-assets/assets/index-CLJMx-GA.js +71 -0
  56. package/dist/web-assets/index.html +2 -2
  57. package/package.json +1 -1
  58. package/templates/_base/src/lib/logger.ts +10 -53
  59. package/templates/_base/src/lib/router.ts +1 -9
  60. package/templates/claude/src/lib/stream-consumer.ts +1 -4
  61. package/templates/pi/src/lib/event-handler.ts +1 -14
  62. package/dist/auth-D3OT2ARB.js +0 -37
  63. package/dist/chunk-KDGS53OS.js +0 -50
  64. package/dist/chunk-RWKVSSLY.js +0 -26
  65. package/dist/chunk-T6HKBWXZ.js +0 -23
  66. package/dist/create-D7J73A6H.js +0 -45
  67. package/dist/file-CR36YUPD.js +0 -204
  68. package/dist/log-ABYNVYJ3.js +0 -39
  69. package/dist/logout-W4KOOBIT.js +0 -18
  70. package/dist/logs-U35JR2KE.js +0 -77
  71. package/dist/merge-LNSMSAOF.js +0 -46
  72. package/dist/pull-XCHJTM5M.js +0 -39
  73. package/dist/schedule-QTJMFATP.js +0 -154
  74. package/dist/service-6LIN3F3K.js +0 -122
  75. package/dist/shared-ML5I4Q2A.js +0 -39
  76. package/dist/status-7GA4SM4Y.js +0 -35
  77. package/dist/web-assets/assets/index-CI5wgghI.css +0 -1
  78. package/dist/web-assets/assets/index-is5CvJWH.js +0 -75
  79. package/dist/{chunk-GIE6CSN5.js → chunk-DUAUMCEE.js} +0 -0
  80. package/dist/{history-XKRTAFS2.js → history-ALPTNB3I.js} +0 -0
  81. package/dist/{setup-JG4QAEBV.js → setup-RXYVGGT7.js} +3 -3
@@ -1,17 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- readSystemsConfig
4
- } from "./chunk-KDGS53OS.js";
5
- import {
6
- markIdle
7
- } from "./chunk-K5NAC55T.js";
8
- import {
9
- clearJsonMap,
10
- getMindManager,
11
- getPrompt,
12
- loadJsonMap,
13
- saveJsonMap
14
- } from "./chunk-PHSAT7YL.js";
3
+ startWatcher,
4
+ stopWatcher
5
+ } from "./chunk-THUUIU3E.js";
15
6
  import {
16
7
  addMessage,
17
8
  createChannel,
@@ -19,12 +10,22 @@ import {
19
10
  getParticipants,
20
11
  joinChannel,
21
12
  publish as publish2
22
- } from "./chunk-IAYBDWVG.js";
13
+ } from "./chunk-FLZGS4QH.js";
14
+ import {
15
+ markIdle
16
+ } from "./chunk-CQ7SNKNI.js";
23
17
  import {
24
18
  broadcast,
25
19
  publish,
26
20
  subscribe
27
21
  } from "./chunk-VIVMW2H2.js";
22
+ import {
23
+ clearJsonMap,
24
+ getMindManager,
25
+ getPrompt,
26
+ loadJsonMap,
27
+ saveJsonMap
28
+ } from "./chunk-EHZKEMMV.js";
28
29
  import {
29
30
  logger_default
30
31
  } from "./chunk-YUIHSKR6.js";
@@ -42,19 +43,20 @@ import {
42
43
  stateDir,
43
44
  users,
44
45
  voluteHome,
45
- voluteSystemDir
46
+ voluteSystemDir,
47
+ voluteUserHome
46
48
  } from "./chunk-H7OZRFJB.js";
47
49
 
48
50
  // src/lib/daemon/sleep-manager.ts
49
51
  import { execFile, spawn as spawnChild } from "child_process";
50
52
  import {
51
53
  existsSync as existsSync5,
52
- mkdirSync as mkdirSync4,
53
- readdirSync as readdirSync2,
54
- readFileSync as readFileSync5,
54
+ mkdirSync as mkdirSync5,
55
+ readdirSync,
56
+ readFileSync as readFileSync6,
55
57
  readlinkSync,
56
- renameSync,
57
- writeFileSync as writeFileSync4
58
+ renameSync as renameSync2,
59
+ writeFileSync as writeFileSync5
58
60
  } from "fs";
59
61
  import { resolve as resolve8 } from "path";
60
62
  import { promisify } from "util";
@@ -227,214 +229,9 @@ async function migrateMindRoles() {
227
229
  await db.update(users).set({ role: "user" }).where(and(eq(users.user_type, "mind"), inArray(users.role, ["mind", "agent"])));
228
230
  }
229
231
 
230
- // src/lib/pages-watcher.ts
231
- import { existsSync as existsSync2, readdirSync, statSync, watch } from "fs";
232
- import { join, resolve as resolve2 } from "path";
233
- var watchers = /* @__PURE__ */ new Map();
234
- var homeWatchers = /* @__PURE__ */ new Map();
235
- var debounceTimers = /* @__PURE__ */ new Map();
236
- var sitesCache = null;
237
- var recentPagesCache = null;
238
- function startPagesWatcher(mindName, pagesDir) {
239
- try {
240
- const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
241
- if (!filename || !filename.endsWith(".html")) return;
242
- const key = `${mindName}:${filename}`;
243
- const existing = debounceTimers.get(key);
244
- if (existing) clearTimeout(existing);
245
- debounceTimers.set(
246
- key,
247
- setTimeout(() => {
248
- debounceTimers.delete(key);
249
- invalidateCache();
250
- publish({
251
- type: "page_updated",
252
- mind: mindName,
253
- summary: `${mindName} updated ${filename}`,
254
- metadata: { file: filename }
255
- }).catch(
256
- (err) => logger_default.error("failed to publish page_updated activity", logger_default.errorData(err))
257
- );
258
- }, 100)
259
- );
260
- });
261
- watchers.set(mindName, watcher);
262
- } catch (err) {
263
- logger_default.warn(`failed to start pages watcher for ${mindName}`, logger_default.errorData(err));
264
- }
265
- }
266
- function startWatcher(mindName) {
267
- if (watchers.has(mindName)) return;
268
- const pagesDir = resolve2(mindDir(mindName), "home", "public", "pages");
269
- if (existsSync2(pagesDir)) {
270
- startPagesWatcher(mindName, pagesDir);
271
- return;
272
- }
273
- if (homeWatchers.has(mindName)) return;
274
- const publicDir = resolve2(mindDir(mindName), "home", "public");
275
- if (!existsSync2(publicDir)) return;
276
- try {
277
- const hw = watch(publicDir, (_eventType, filename) => {
278
- if (filename !== "pages") return;
279
- if (!existsSync2(pagesDir)) return;
280
- hw.close();
281
- homeWatchers.delete(mindName);
282
- invalidateCache();
283
- startPagesWatcher(mindName, pagesDir);
284
- });
285
- homeWatchers.set(mindName, hw);
286
- } catch (err) {
287
- logger_default.warn(`failed to start home watcher for ${mindName}`, logger_default.errorData(err));
288
- }
289
- }
290
- function stopWatcher(mindName) {
291
- const watcher = watchers.get(mindName);
292
- if (watcher) {
293
- watcher.close();
294
- watchers.delete(mindName);
295
- }
296
- const hw = homeWatchers.get(mindName);
297
- if (hw) {
298
- hw.close();
299
- homeWatchers.delete(mindName);
300
- }
301
- for (const [key, timer] of debounceTimers) {
302
- if (key.startsWith(`${mindName}:`)) {
303
- clearTimeout(timer);
304
- debounceTimers.delete(key);
305
- }
306
- }
307
- }
308
- function stopAllWatchers() {
309
- for (const [, watcher] of watchers) {
310
- watcher.close();
311
- }
312
- watchers.clear();
313
- for (const [, hw] of homeWatchers) {
314
- hw.close();
315
- }
316
- homeWatchers.clear();
317
- for (const [, timer] of debounceTimers) {
318
- clearTimeout(timer);
319
- }
320
- debounceTimers.clear();
321
- invalidateCache();
322
- }
323
- function invalidateCache() {
324
- sitesCache = null;
325
- recentPagesCache = null;
326
- }
327
- function scanPagesDir(dir, urlPrefix) {
328
- const pages = [];
329
- let items;
330
- try {
331
- items = readdirSync(dir);
332
- } catch {
333
- return pages;
334
- }
335
- for (const item of items) {
336
- if (item.startsWith(".")) continue;
337
- const fullPath = resolve2(dir, item);
338
- try {
339
- const s = statSync(fullPath);
340
- if (s.isFile() && item.endsWith(".html")) {
341
- pages.push({
342
- file: item,
343
- modified: s.mtime.toISOString(),
344
- url: `${urlPrefix}/${item}`
345
- });
346
- } else if (s.isDirectory()) {
347
- const indexPath = resolve2(fullPath, "index.html");
348
- if (existsSync2(indexPath)) {
349
- const indexStat = statSync(indexPath);
350
- pages.push({
351
- file: join(item, "index.html"),
352
- modified: indexStat.mtime.toISOString(),
353
- url: `${urlPrefix}/${item}/`
354
- });
355
- }
356
- }
357
- } catch {
358
- }
359
- }
360
- pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
361
- return pages;
362
- }
363
- async function buildSites() {
364
- const sites = [];
365
- const systemPagesDir = resolve2(voluteHome(), "shared", "pages");
366
- if (existsSync2(systemPagesDir)) {
367
- const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
368
- if (systemPages.length > 0) {
369
- sites.push({ name: "_system", label: "System", pages: systemPages });
370
- }
371
- }
372
- const entries = await readRegistry();
373
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
374
- const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
375
- if (!existsSync2(pagesDir)) continue;
376
- const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
377
- if (mindPages.length > 0) {
378
- sites.push({ name: entry.name, label: entry.name, pages: mindPages });
379
- }
380
- }
381
- return sites;
382
- }
383
- async function buildRecentPages() {
384
- const entries = await readRegistry();
385
- const pages = [];
386
- for (const entry of entries) {
387
- const pagesDir = resolve2(mindDir(entry.name), "home", "public", "pages");
388
- if (!existsSync2(pagesDir)) continue;
389
- let items;
390
- try {
391
- items = readdirSync(pagesDir);
392
- } catch {
393
- continue;
394
- }
395
- for (const item of items) {
396
- if (item.startsWith(".")) continue;
397
- const fullPath = resolve2(pagesDir, item);
398
- try {
399
- const s = statSync(fullPath);
400
- if (s.isFile() && item.endsWith(".html")) {
401
- pages.push({
402
- mind: entry.name,
403
- file: item,
404
- modified: s.mtime.toISOString(),
405
- url: `/pages/${entry.name}/${item}`
406
- });
407
- } else if (s.isDirectory()) {
408
- const indexPath = resolve2(fullPath, "index.html");
409
- if (existsSync2(indexPath)) {
410
- const indexStat = statSync(indexPath);
411
- pages.push({
412
- mind: entry.name,
413
- file: join(item, "index.html"),
414
- modified: indexStat.mtime.toISOString(),
415
- url: `/pages/${entry.name}/${item}/`
416
- });
417
- }
418
- }
419
- } catch {
420
- }
421
- }
422
- }
423
- pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
424
- return pages.slice(0, 10);
425
- }
426
- async function getCachedSites() {
427
- if (!sitesCache) sitesCache = await buildSites();
428
- return sitesCache;
429
- }
430
- async function getCachedRecentPages() {
431
- if (!recentPagesCache) recentPagesCache = await buildRecentPages();
432
- return recentPagesCache;
433
- }
434
-
435
232
  // 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";
233
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
234
+ import { join, resolve as resolve2 } from "path";
438
235
  function splitMessage(text, maxLength) {
439
236
  const chunks = [];
440
237
  while (text.length > maxLength) {
@@ -447,8 +244,8 @@ function splitMessage(text, maxLength) {
447
244
  return chunks;
448
245
  }
449
246
  function readChannelMap(mindName) {
450
- const filePath = join2(stateDir(mindName), "channels.json");
451
- if (!existsSync3(filePath)) return {};
247
+ const filePath = join(stateDir(mindName), "channels.json");
248
+ if (!existsSync2(filePath)) return {};
452
249
  try {
453
250
  return JSON.parse(readFileSync2(filePath, "utf-8"));
454
251
  } catch (err) {
@@ -459,7 +256,7 @@ function readChannelMap(mindName) {
459
256
  function writeChannelEntry(mindName, slug, entry) {
460
257
  const dir = stateDir(mindName);
461
258
  mkdirSync2(dir, { recursive: true });
462
- const filePath = join2(dir, "channels.json");
259
+ const filePath = join(dir, "channels.json");
463
260
  const map = readChannelMap(mindName);
464
261
  map[slug] = entry;
465
262
  writeFileSync2(filePath, JSON.stringify(map, null, 2) + "\n");
@@ -503,7 +300,7 @@ function publish3(mind, event) {
503
300
 
504
301
  // src/lib/delivery/delivery-manager.ts
505
302
  import { readFile, realpath } from "fs/promises";
506
- import { extname, resolve as resolve5 } from "path";
303
+ import { extname, resolve as resolve4 } from "path";
507
304
  import { and as and2, eq as eq2, sql } from "drizzle-orm";
508
305
 
509
306
  // src/lib/typing.ts
@@ -597,8 +394,8 @@ function publishTypingForChannels(channels, map) {
597
394
  }
598
395
 
599
396
  // src/lib/delivery/delivery-router.ts
600
- import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
601
- import { resolve as resolve4 } from "path";
397
+ import { readFileSync as readFileSync3, statSync } from "fs";
398
+ import { resolve as resolve3 } from "path";
602
399
  function extractTextContent(content) {
603
400
  if (typeof content === "string") return content;
604
401
  if (Array.isArray(content)) {
@@ -611,7 +408,7 @@ var statCheckCache = /* @__PURE__ */ new Map();
611
408
  var STAT_TTL_MS = 5e3;
612
409
  var dlog = logger_default.child("delivery-router");
613
410
  function configPath(mindName) {
614
- return resolve4(mindDir(mindName), "home/.config/routes.json");
411
+ return resolve3(mindDir(mindName), "home/.config/routes.json");
615
412
  }
616
413
  function getRoutingConfig(mindName) {
617
414
  const path = configPath(mindName);
@@ -623,7 +420,7 @@ function getRoutingConfig(mindName) {
623
420
  }
624
421
  let mtime;
625
422
  try {
626
- mtime = statSync2(path).mtimeMs;
423
+ mtime = statSync(path).mtimeMs;
627
424
  } catch {
628
425
  configCache.delete(mindName);
629
426
  statCheckCache.delete(mindName);
@@ -1296,8 +1093,8 @@ var DeliveryManager = class {
1296
1093
  const dir = mindDir(p.username);
1297
1094
  const config = readVoluteConfig(dir);
1298
1095
  if (!config?.profile?.avatar) continue;
1299
- filePath = resolve5(dir, "home", config.profile.avatar);
1300
- const homeDir = resolve5(dir, "home");
1096
+ filePath = resolve4(dir, "home", config.profile.avatar);
1097
+ const homeDir = resolve4(dir, "home");
1301
1098
  if (!filePath.startsWith(`${homeDir}/`)) {
1302
1099
  dlog2.warn(`avatar path for ${p.username} escapes home directory, skipping`);
1303
1100
  continue;
@@ -1316,7 +1113,7 @@ var DeliveryManager = class {
1316
1113
  throw err;
1317
1114
  }
1318
1115
  } else {
1319
- filePath = resolve5(voluteHome(), "avatars", p.avatar);
1116
+ filePath = resolve4(voluteHome(), "avatars", p.avatar);
1320
1117
  }
1321
1118
  const ext = extname(filePath).toLowerCase();
1322
1119
  const mimeMap = {
@@ -1412,6 +1209,12 @@ async function recordInbound(mind, channel, sender, content) {
1412
1209
  content: content ?? void 0
1413
1210
  });
1414
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
+ }
1415
1218
  async function deliverMessage(mindName, payload) {
1416
1219
  try {
1417
1220
  const baseName = await getBaseName(mindName);
@@ -1424,11 +1227,21 @@ async function deliverMessage(mindName, payload) {
1424
1227
  await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
1425
1228
  const sleepManager = getSleepManagerIfReady();
1426
1229
  if (sleepManager?.isSleeping(baseName)) {
1427
- if (sleepManager.checkWakeTrigger(baseName, payload)) {
1428
- await sleepManager.queueSleepMessage(baseName, payload);
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") {
1429
1244
  sleepManager.initiateWake(baseName, { trigger: { channel: payload.channel } }).catch((err) => dlog3.warn(`failed to trigger-wake ${baseName}`, logger_default.errorData(err)));
1430
- } else {
1431
- await sleepManager.queueSleepMessage(baseName, payload);
1432
1245
  }
1433
1246
  return;
1434
1247
  }
@@ -1474,7 +1287,7 @@ async function announceToSystem(text) {
1474
1287
  platformId: channelId,
1475
1288
  platform: "volute",
1476
1289
  name: SYSTEM_CHANNEL_NAME,
1477
- type: "group"
1290
+ type: "channel"
1478
1291
  });
1479
1292
  } catch (err) {
1480
1293
  logger_default.warn(`failed to write channel entry for ${mind.username}`, logger_default.errorData(err));
@@ -1493,6 +1306,74 @@ async function announceToSystem(text) {
1493
1306
  }
1494
1307
  }
1495
1308
 
1309
+ // src/lib/systems-config.ts
1310
+ import {
1311
+ existsSync as existsSync3,
1312
+ mkdirSync as mkdirSync3,
1313
+ readFileSync as readFileSync4,
1314
+ renameSync,
1315
+ unlinkSync,
1316
+ writeFileSync as writeFileSync3
1317
+ } from "fs";
1318
+ import { resolve as resolve5 } from "path";
1319
+ var DEFAULT_API_URL = "https://volute.systems";
1320
+ function configPath2() {
1321
+ return resolve5(voluteSystemDir(), "systems.json");
1322
+ }
1323
+ function migrateIfNeeded() {
1324
+ const target = configPath2();
1325
+ if (existsSync3(target)) return;
1326
+ const oldPaths = [
1327
+ resolve5(voluteUserHome(), "systems.json"),
1328
+ resolve5(voluteHome(), "systems.json")
1329
+ ];
1330
+ for (const old of oldPaths) {
1331
+ if (old !== target && existsSync3(old)) {
1332
+ try {
1333
+ mkdirSync3(voluteSystemDir(), { recursive: true });
1334
+ renameSync(old, target);
1335
+ } catch {
1336
+ }
1337
+ return;
1338
+ }
1339
+ }
1340
+ }
1341
+ function readSystemsConfig() {
1342
+ migrateIfNeeded();
1343
+ const path = configPath2();
1344
+ if (!existsSync3(path)) return null;
1345
+ const raw = readFileSync4(path, "utf-8");
1346
+ let data;
1347
+ try {
1348
+ data = JSON.parse(raw);
1349
+ } catch {
1350
+ console.error(
1351
+ `Warning: ${path} contains invalid JSON. Run "volute systems logout" and re-login.`
1352
+ );
1353
+ return null;
1354
+ }
1355
+ if (!data.apiKey || !data.system) return null;
1356
+ return {
1357
+ apiKey: data.apiKey,
1358
+ system: data.system,
1359
+ apiUrl: data.apiUrl || DEFAULT_API_URL
1360
+ };
1361
+ }
1362
+ function writeSystemsConfig(config) {
1363
+ mkdirSync3(voluteSystemDir(), { recursive: true });
1364
+ writeFileSync3(configPath2(), `${JSON.stringify(config, null, 2)}
1365
+ `, { mode: 384 });
1366
+ }
1367
+ function deleteSystemsConfig() {
1368
+ try {
1369
+ unlinkSync(configPath2());
1370
+ return true;
1371
+ } catch (err) {
1372
+ if (err.code === "ENOENT") return false;
1373
+ throw err;
1374
+ }
1375
+ }
1376
+
1496
1377
  // src/lib/daemon/mail-poller.ts
1497
1378
  var mlog = logger_default.child("mail");
1498
1379
  function formatEmailContent(email) {
@@ -1787,6 +1668,15 @@ var Scheduler = class {
1787
1668
  shouldFire(schedule, epochMinute, mind, cronCache) {
1788
1669
  const key = `${mind}:${schedule.id}`;
1789
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;
1790
1680
  let prevMinute = cronCache.get(schedule.cron);
1791
1681
  if (prevMinute === void 0) {
1792
1682
  try {
@@ -1806,18 +1696,6 @@ var Scheduler = class {
1806
1696
  return false;
1807
1697
  }
1808
1698
  async fire(mindName, schedule) {
1809
- const sleepManager = getSleepManagerIfReady();
1810
- const sleepState = sleepManager?.getState(mindName);
1811
- if (sleepState?.sleeping) {
1812
- if (schedule.skipWhenSleeping) {
1813
- slog.info(`skipped "${schedule.id}" for ${mindName} (sleeping)`);
1814
- return;
1815
- }
1816
- if (sleepState.wokenByTrigger) {
1817
- slog.info(`skipped "${schedule.id}" for ${mindName} (trigger-woken)`);
1818
- return;
1819
- }
1820
- }
1821
1699
  try {
1822
1700
  let text;
1823
1701
  if (schedule.script) {
@@ -1841,16 +1719,46 @@ ${stderr}` : ""}`;
1841
1719
  slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
1842
1720
  return;
1843
1721
  }
1722
+ const whileSleeping = schedule.whileSleeping ?? (schedule.skipWhenSleeping ? "skip" : void 0);
1844
1723
  await this.deliver(mindName, {
1845
1724
  content: [{ type: "text", text }],
1846
1725
  channel: schedule.channel ?? "system:scheduler",
1847
- sender: schedule.id
1726
+ sender: schedule.id,
1727
+ whileSleeping
1848
1728
  });
1849
1729
  slog.info(`fired "${schedule.id}" for ${mindName}`);
1730
+ if (schedule.fireAt) {
1731
+ this.removeSchedule(mindName, schedule.id);
1732
+ }
1850
1733
  } catch (err) {
1851
1734
  slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
1852
1735
  }
1853
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
+ }
1854
1762
  runScript(script, cwd, mindName) {
1855
1763
  return exec("bash", ["-c", script], { cwd, mindName });
1856
1764
  }
@@ -1870,7 +1778,7 @@ function getScheduler() {
1870
1778
  }
1871
1779
 
1872
1780
  // src/lib/daemon/token-budget.ts
1873
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1781
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1874
1782
  import { resolve as resolve7 } from "path";
1875
1783
  var tlog = logger_default.child("token-budget");
1876
1784
  var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
@@ -1994,14 +1902,14 @@ var TokenBudget = class {
1994
1902
  saveBudgetState(mind, state) {
1995
1903
  try {
1996
1904
  const dir = stateDir(mind);
1997
- mkdirSync3(dir, { recursive: true });
1905
+ mkdirSync4(dir, { recursive: true });
1998
1906
  const data = {
1999
1907
  periodStart: state.periodStart,
2000
1908
  tokensUsed: state.tokensUsed,
2001
1909
  warningInjected: state.warningInjected,
2002
1910
  queue: state.queue
2003
1911
  };
2004
- writeFileSync3(this.budgetStatePath(mind), `${JSON.stringify(data)}
1912
+ writeFileSync4(this.budgetStatePath(mind), `${JSON.stringify(data)}
2005
1913
  `);
2006
1914
  } catch (err) {
2007
1915
  tlog.warn(`failed to save budget state for ${mind}`, logger_default.errorData(err));
@@ -2011,7 +1919,7 @@ var TokenBudget = class {
2011
1919
  try {
2012
1920
  const path = this.budgetStatePath(mind);
2013
1921
  if (!existsSync4(path)) return null;
2014
- const data = JSON.parse(readFileSync4(path, "utf-8"));
1922
+ const data = JSON.parse(readFileSync5(path, "utf-8"));
2015
1923
  if (typeof data.periodStart !== "number" || typeof data.tokensUsed !== "number") return null;
2016
1924
  return {
2017
1925
  periodStart: data.periodStart,
@@ -2080,6 +1988,7 @@ async function startMindFull(name) {
2080
1988
  if (!entry || entry.stage === "seed") return;
2081
1989
  const dir = mindDir(baseName);
2082
1990
  getScheduler().loadSchedules(baseName);
1991
+ getSleepManagerIfReady()?.loadSleepConfig(baseName);
2083
1992
  ensureMailAddress(baseName).catch(
2084
1993
  (err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
2085
1994
  );
@@ -2172,6 +2081,7 @@ var SleepManager = class {
2172
2081
  interval = null;
2173
2082
  unsubActivity = null;
2174
2083
  transitioning = /* @__PURE__ */ new Set();
2084
+ sleepConfigs = /* @__PURE__ */ new Map();
2175
2085
  get statePath() {
2176
2086
  return resolve8(voluteSystemDir(), "sleep-state.json");
2177
2087
  }
@@ -2190,7 +2100,7 @@ var SleepManager = class {
2190
2100
  loadState() {
2191
2101
  try {
2192
2102
  if (existsSync5(this.statePath)) {
2193
- const data = JSON.parse(readFileSync5(this.statePath, "utf-8"));
2103
+ const data = JSON.parse(readFileSync6(this.statePath, "utf-8"));
2194
2104
  for (const [name, state] of Object.entries(data)) {
2195
2105
  state.triggerWakeHistory ??= [];
2196
2106
  this.states.set(name, state);
@@ -2206,7 +2116,7 @@ var SleepManager = class {
2206
2116
  if (state.sleeping) data[name] = state;
2207
2117
  }
2208
2118
  try {
2209
- writeFileSync4(this.statePath, `${JSON.stringify(data, null, 2)}
2119
+ writeFileSync5(this.statePath, `${JSON.stringify(data, null, 2)}
2210
2120
  `);
2211
2121
  } catch (err) {
2212
2122
  slog2.error("failed to save sleep state", logger_default.errorData(err));
@@ -2233,9 +2143,21 @@ var SleepManager = class {
2233
2143
  slog2.info(`${name} trigger-wake converted to full wake`);
2234
2144
  }
2235
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) {
2236
2153
  const dir = mindDir(name);
2237
2154
  const config = readVoluteConfig(dir);
2238
- return config?.sleep ?? null;
2155
+ const sleepConfig = config?.sleep ?? null;
2156
+ this.sleepConfigs.set(name, sleepConfig);
2157
+ return sleepConfig;
2158
+ }
2159
+ invalidateSleepConfig(name) {
2160
+ this.sleepConfigs.delete(name);
2239
2161
  }
2240
2162
  /**
2241
2163
  * Put a mind to sleep. Sends pre-sleep message, waits for completion,
@@ -2255,8 +2177,7 @@ var SleepManager = class {
2255
2177
  if (!entry) return;
2256
2178
  const sleepConfig = this.getSleepConfig(name);
2257
2179
  const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
2258
- const queuedInfo = "";
2259
- const preSleepMsg = await getPrompt("pre_sleep", { wakeTime, queuedInfo });
2180
+ const preSleepMsg = await getPrompt("pre_sleep", { wakeTime });
2260
2181
  try {
2261
2182
  const db = await getDb();
2262
2183
  await db.insert(mindHistory).values({
@@ -2426,7 +2347,7 @@ var SleepManager = class {
2426
2347
  const db = await getDb();
2427
2348
  const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
2428
2349
  if (rows.length === 0) return 0;
2429
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-LDXLGERA.js");
2350
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-Q7VUMIEI.js");
2430
2351
  const delivered = [];
2431
2352
  for (const row of rows) {
2432
2353
  try {
@@ -2547,14 +2468,14 @@ var SleepManager = class {
2547
2468
  const sessionsDir = resolve8(dir, ".mind", "sessions");
2548
2469
  if (existsSync5(sessionsDir)) {
2549
2470
  const archiveDir = resolve8(sessionsDir, "archive");
2550
- mkdirSync4(archiveDir, { recursive: true });
2551
- for (const file of readdirSync2(sessionsDir)) {
2471
+ mkdirSync5(archiveDir, { recursive: true });
2472
+ for (const file of readdirSync(sessionsDir)) {
2552
2473
  if (file === "archive" || !file.endsWith(".json")) continue;
2553
2474
  const src = resolve8(sessionsDir, file);
2554
2475
  const base = file.replace(/\.json$/, "");
2555
2476
  const dest = resolve8(archiveDir, `${base}-${timestamp}.json`);
2556
2477
  try {
2557
- renameSync(src, dest);
2478
+ renameSync2(src, dest);
2558
2479
  } catch (err) {
2559
2480
  slog2.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
2560
2481
  }
@@ -2563,13 +2484,13 @@ var SleepManager = class {
2563
2484
  const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
2564
2485
  if (existsSync5(piSessionsDir)) {
2565
2486
  const archiveDir = resolve8(piSessionsDir, "archive");
2566
- mkdirSync4(archiveDir, { recursive: true });
2567
- for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
2487
+ mkdirSync5(archiveDir, { recursive: true });
2488
+ for (const entry of readdirSync(piSessionsDir, { withFileTypes: true })) {
2568
2489
  if (entry.name === "archive" || !entry.isDirectory()) continue;
2569
2490
  const src = resolve8(piSessionsDir, entry.name);
2570
2491
  const dest = resolve8(archiveDir, `${entry.name}-${timestamp}`);
2571
2492
  try {
2572
- renameSync(src, dest);
2493
+ renameSync2(src, dest);
2573
2494
  } catch (err) {
2574
2495
  slog2.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
2575
2496
  }
@@ -2675,16 +2596,16 @@ var SleepManager = class {
2675
2596
  } catch {
2676
2597
  try {
2677
2598
  const portHex = port.toString(16).toUpperCase().padStart(4, "0");
2678
- const tcp6 = readFileSync5("/proc/net/tcp6", "utf-8");
2599
+ const tcp6 = readFileSync6("/proc/net/tcp6", "utf-8");
2679
2600
  for (const line of tcp6.split("\n")) {
2680
2601
  if (!line.includes(`:${portHex} `)) continue;
2681
2602
  const fields = line.trim().split(/\s+/);
2682
2603
  if (fields[3] !== "0A") continue;
2683
2604
  const inode = parseInt(fields[9], 10);
2684
2605
  if (!inode) continue;
2685
- for (const pidDir of readdirSync2("/proc").filter((f) => /^\d+$/.test(f))) {
2606
+ for (const pidDir of readdirSync("/proc").filter((f) => /^\d+$/.test(f))) {
2686
2607
  try {
2687
- const fds = readdirSync2(`/proc/${pidDir}/fd`);
2608
+ const fds = readdirSync(`/proc/${pidDir}/fd`);
2688
2609
  for (const fd of fds) {
2689
2610
  try {
2690
2611
  const link = readlinkSync(`/proc/${pidDir}/fd/${fd}`);
@@ -2760,9 +2681,6 @@ export {
2760
2681
  migrateMindRoles,
2761
2682
  readVoluteConfig,
2762
2683
  writeVoluteConfig,
2763
- stopAllWatchers,
2764
- getCachedSites,
2765
- getCachedRecentPages,
2766
2684
  splitMessage,
2767
2685
  writeChannelEntry,
2768
2686
  resolveChannelId,
@@ -2788,6 +2706,10 @@ export {
2788
2706
  initDeliveryManager,
2789
2707
  getDeliveryManager,
2790
2708
  recordInbound,
2709
+ resolveSleepAction,
2791
2710
  deliverMessage,
2711
+ readSystemsConfig,
2712
+ writeSystemsConfig,
2713
+ deleteSystemsConfig,
2792
2714
  initMailPoller
2793
2715
  };