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.
Files changed (44) hide show
  1. package/README.md +15 -5
  2. package/dist/api.d.ts +192 -9
  3. package/dist/{chat-M4SX42JD.js → chat-KTPOR2JT.js} +1 -1
  4. package/dist/chunk-A6TUJJ3L.js +19 -0
  5. package/dist/{chunk-AAPXKR5V.js → chunk-CMILSHZD.js} +138 -285
  6. package/dist/{chunk-K5NAC55T.js → chunk-CQ7SNKNI.js} +1 -1
  7. package/dist/{chunk-POSXWWTA.js → chunk-EHZKEMMV.js} +4 -4
  8. package/dist/{chunk-IAYBDWVG.js → chunk-FLZGS4QH.js} +145 -0
  9. package/dist/chunk-THUUIU3E.js +232 -0
  10. package/dist/cli.js +11 -8
  11. package/dist/clock-DGCBVGYA.js +259 -0
  12. package/dist/{cloud-sync-HDL6PHZI.js → cloud-sync-KILFGV5Q.js} +6 -5
  13. package/dist/connectors/discord-bridge.js +1 -1
  14. package/dist/connectors/slack-bridge.js +1 -1
  15. package/dist/connectors/telegram-bridge.js +1 -1
  16. package/dist/{conversations-M2K4253F.js → conversations-P5BL7RMX.js} +7 -1
  17. package/dist/create-DFCAGEE5.js +70 -0
  18. package/dist/{daemon-restart-G4B2OYAB.js → daemon-restart-UHOMICXT.js} +1 -1
  19. package/dist/daemon.js +181 -66
  20. package/dist/{message-delivery-HV3S6HZV.js → message-delivery-Q7VUMIEI.js} +10 -7
  21. package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-WRHFI3YW.js} +1 -1
  22. package/dist/{mind-manager-S6ILZVX3.js → mind-manager-P66HQDNE.js} +1 -1
  23. package/dist/{package-CG4RWUGP.js → package-OFKXNKJF.js} +1 -1
  24. package/dist/pages-watcher-P7QECRE2.js +21 -0
  25. package/dist/skills/dreaming/references/INSTALL.md +3 -17
  26. package/dist/skills/volute-mind/SKILL.md +43 -16
  27. package/dist/{sleep-manager-WMVG2VCL.js → sleep-manager-G4B5GW5P.js} +6 -5
  28. package/dist/{up-GM2JOH2Y.js → up-W6VAK2XE.js} +1 -1
  29. package/dist/{version-notify-JDUF4HQJ.js → version-notify-WDHRO3XD.js} +8 -7
  30. package/dist/web-assets/assets/index-BmKDnWDB.css +1 -0
  31. package/dist/web-assets/assets/index-CLJMx-GA.js +71 -0
  32. package/dist/web-assets/index.html +2 -2
  33. package/package.json +1 -1
  34. package/templates/_base/src/lib/logger.ts +10 -49
  35. package/templates/_base/src/lib/router.ts +1 -9
  36. package/templates/claude/src/lib/stream-consumer.ts +1 -4
  37. package/templates/pi/src/lib/event-handler.ts +1 -14
  38. package/dist/chunk-T6HKBWXZ.js +0 -23
  39. package/dist/create-D7J73A6H.js +0 -45
  40. package/dist/schedule-QTJMFATP.js +0 -154
  41. package/dist/web-assets/assets/index-BZGvToHi.css +0 -1
  42. package/dist/web-assets/assets/index-Cz4TrpzB.js +0 -75
  43. /package/dist/{chunk-SGVNFZHW.js → chunk-DUAUMCEE.js} +0 -0
  44. /package/dist/{pages-KJDJX4TA.js → pages-EUJR52AH.js} +0 -0
package/dist/daemon.js CHANGED
@@ -5,19 +5,6 @@ import {
5
5
  removeSharedWorktree,
6
6
  sharedMerge
7
7
  } from "./chunk-P72MVS4R.js";
8
- import {
9
- checkForUpdate,
10
- checkForUpdateCached,
11
- getCurrentVersion
12
- } from "./chunk-HDN7MNGD.js";
13
- import {
14
- applyInitFiles,
15
- composeTemplate,
16
- computeTemplateHash,
17
- copyTemplateToDir,
18
- findTemplatesRoot,
19
- listFiles
20
- } from "./chunk-AKPFNL7L.js";
21
8
  import {
22
9
  announceToSystem,
23
10
  approveUser,
@@ -30,11 +17,10 @@ import {
30
17
  deliverMessage,
31
18
  ensureSystemChannel,
32
19
  extractTextContent,
33
- getCachedRecentPages,
34
- getCachedSites,
35
20
  getDeliveryManager,
36
21
  getOrCreateMindUser,
37
22
  getScheduler,
23
+ getSleepManagerIfReady,
38
24
  getTokenBudget,
39
25
  getTypingMap,
40
26
  getUser,
@@ -58,7 +44,6 @@ import {
58
44
  setUserRole,
59
45
  splitMessage,
60
46
  startMindFull,
61
- stopAllWatchers,
62
47
  stopMindFull,
63
48
  subscribe as subscribe3,
64
49
  updateUserProfile,
@@ -66,7 +51,12 @@ import {
66
51
  writeChannelEntry,
67
52
  writeSystemsConfig,
68
53
  writeVoluteConfig
69
- } from "./chunk-AAPXKR5V.js";
54
+ } from "./chunk-CMILSHZD.js";
55
+ import {
56
+ getCachedRecentPages,
57
+ getCachedSites,
58
+ stopAllWatchers
59
+ } from "./chunk-THUUIU3E.js";
70
60
  import {
71
61
  addMessage,
72
62
  createChannel,
@@ -81,22 +71,37 @@ import {
81
71
  getParticipants,
82
72
  getUnreadCounts,
83
73
  initWebhook,
74
+ isConversationForMind,
84
75
  isParticipant,
85
76
  isParticipantOrOwner,
86
77
  joinChannel,
87
78
  leaveChannel,
88
79
  listChannels,
80
+ listConversationsForMind,
89
81
  listConversationsForUser,
90
82
  listConversationsWithParticipants,
91
83
  markConversationRead,
92
84
  publish,
93
85
  subscribe as subscribe2
94
- } from "./chunk-IAYBDWVG.js";
86
+ } from "./chunk-FLZGS4QH.js";
87
+ import {
88
+ checkForUpdate,
89
+ checkForUpdateCached,
90
+ getCurrentVersion
91
+ } from "./chunk-HDN7MNGD.js";
92
+ import {
93
+ applyInitFiles,
94
+ composeTemplate,
95
+ computeTemplateHash,
96
+ copyTemplateToDir,
97
+ findTemplatesRoot,
98
+ listFiles
99
+ } from "./chunk-AKPFNL7L.js";
95
100
  import {
96
101
  getActiveMinds,
97
102
  onMindEvent,
98
103
  stopAll
99
- } from "./chunk-K5NAC55T.js";
104
+ } from "./chunk-CQ7SNKNI.js";
100
105
  import {
101
106
  broadcast,
102
107
  subscribe
@@ -113,7 +118,7 @@ import {
113
118
  initMindManager,
114
119
  resolveMindToken,
115
120
  substitute
116
- } from "./chunk-POSXWWTA.js";
121
+ } from "./chunk-EHZKEMMV.js";
117
122
  import "./chunk-J4IBNXGJ.js";
118
123
  import {
119
124
  SEED_SKILLS,
@@ -185,7 +190,7 @@ import "./chunk-D424ZQGI.js";
185
190
  import {
186
191
  buildVoluteSlug,
187
192
  slugify
188
- } from "./chunk-T6HKBWXZ.js";
193
+ } from "./chunk-A6TUJJ3L.js";
189
194
  import {
190
195
  activity,
191
196
  addMind,
@@ -1409,7 +1414,7 @@ var app3 = new Hono3().post("/:platform/inbound", zValidator2("json", inboundSch
1409
1414
  }
1410
1415
  const participants = await getParticipants(channel.id);
1411
1416
  if (!participants.some((p) => p.userId === puppet.id)) {
1412
- const { addParticipant } = await import("./conversations-M2K4253F.js");
1417
+ const { addParticipant } = await import("./conversations-P5BL7RMX.js");
1413
1418
  await addParticipant(channel.id, puppet.id);
1414
1419
  }
1415
1420
  const contentBlocks = body.content;
@@ -1502,10 +1507,10 @@ async function fanOutToBridgedMinds(opts) {
1502
1507
  const participants = await getParticipants(opts.conversationId);
1503
1508
  const mindParticipants = participants.filter((p) => p.userType === "mind");
1504
1509
  const participantNames = participants.map((p) => p.username);
1505
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
1506
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
1510
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
1511
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
1507
1512
  const manager = getMindManager2();
1508
- const sm = getSleepManagerIfReady();
1513
+ const sm = getSleepManagerIfReady2();
1509
1514
  const targetMinds = mindParticipants.filter((ap) => {
1510
1515
  return (manager.isRunning(ap.username) || sm?.isSleeping(ap.username)) && ap.username !== opts.senderName;
1511
1516
  }).map((ap) => ap.username);
@@ -1520,7 +1525,7 @@ async function fanOutToBridgedMinds(opts) {
1520
1525
  writeChannelEntry(mindName, channel, {
1521
1526
  platformId: opts.conversationId,
1522
1527
  platform: "volute",
1523
- type: opts.isDM ? "dm" : "group"
1528
+ type: opts.isDM ? "dm" : "channel"
1524
1529
  });
1525
1530
  } catch (err) {
1526
1531
  logger_default.warn(`failed to write channel entry for ${mindName}`, logger_default.errorData(err));
@@ -1648,7 +1653,7 @@ async function listConversations(env) {
1648
1653
  id: slug,
1649
1654
  platformId: dm.id,
1650
1655
  name: recipients.join(", ") || "DM",
1651
- type: dm.type === 1 ? "dm" : "group"
1656
+ type: dm.type === 1 ? "dm" : "channel"
1652
1657
  });
1653
1658
  }
1654
1659
  return results;
@@ -1818,7 +1823,6 @@ async function listConversations2(env) {
1818
1823
  return data.channels.map((ch) => {
1819
1824
  let type = "channel";
1820
1825
  if (ch.is_im) type = "dm";
1821
- else if (ch.is_mpim) type = "group";
1822
1826
  let slug;
1823
1827
  let name;
1824
1828
  if (ch.is_im && ch.user) {
@@ -1895,7 +1899,7 @@ async function createConversation3(env, participants, name) {
1895
1899
  platformId,
1896
1900
  platform: "slack",
1897
1901
  name: participants.join(", "),
1898
- type: participants.length === 1 ? "dm" : "group"
1902
+ type: participants.length === 1 ? "dm" : "channel"
1899
1903
  });
1900
1904
  }
1901
1905
  return slug;
@@ -2111,7 +2115,7 @@ async function listConversations4(env) {
2111
2115
  convType: conv.type,
2112
2116
  convName: conv.name
2113
2117
  });
2114
- const convType = conv.type === "channel" ? "channel" : participants.length === 2 ? "dm" : "group";
2118
+ const convType = conv.type === "channel" ? "channel" : "dm";
2115
2119
  results.push({
2116
2120
  id: slug,
2117
2121
  platformId: conv.id,
@@ -3193,8 +3197,8 @@ async function getMindStatus(name, port) {
3193
3197
  const manager = getMindManager();
3194
3198
  let status = "stopped";
3195
3199
  try {
3196
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
3197
- if (getSleepManagerIfReady()?.isSleeping(name)) {
3200
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
3201
+ if (getSleepManagerIfReady2()?.isSleeping(name)) {
3198
3202
  status = "sleeping";
3199
3203
  }
3200
3204
  } catch {
@@ -3925,8 +3929,8 @@ ${user.trimEnd()}
3925
3929
  const manager = getMindManager();
3926
3930
  try {
3927
3931
  if (context?.type === "reload") {
3928
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
3929
- const sleepState = getSleepManagerIfReady()?.getState(name);
3932
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
3933
+ const sleepState = getSleepManagerIfReady2()?.getState(name);
3930
3934
  if (sleepState?.sleeping) {
3931
3935
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
3932
3936
  return c.json({ ok: true, deferred: true, port: targetPort });
@@ -4020,16 +4024,16 @@ ${user.trimEnd()}
4020
4024
  const name = c.req.param("name");
4021
4025
  const entry = await findMind(name);
4022
4026
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4023
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4024
- const sm = getSleepManagerIfReady();
4027
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4028
+ const sm = getSleepManagerIfReady2();
4025
4029
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4026
4030
  return c.json(sm.getState(name));
4027
4031
  }).post("/:name/sleep", requireSelf(), async (c) => {
4028
4032
  const name = c.req.param("name");
4029
4033
  const entry = await findMind(name);
4030
4034
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4031
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4032
- const sm = getSleepManagerIfReady();
4035
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4036
+ const sm = getSleepManagerIfReady2();
4033
4037
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4034
4038
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
4035
4039
  const body = await c.req.json().catch(() => ({}));
@@ -4048,8 +4052,8 @@ ${user.trimEnd()}
4048
4052
  const name = c.req.param("name");
4049
4053
  const entry = await findMind(name);
4050
4054
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4051
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4052
- const sm = getSleepManagerIfReady();
4055
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4056
+ const sm = getSleepManagerIfReady2();
4053
4057
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4054
4058
  const sleepState = sm.getState(name);
4055
4059
  if (!sleepState.sleeping) return c.json({ error: "Mind is not sleeping" }, 409);
@@ -4063,8 +4067,8 @@ ${user.trimEnd()}
4063
4067
  const name = c.req.param("name");
4064
4068
  const entry = await findMind(name);
4065
4069
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4066
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4067
- const sm = getSleepManagerIfReady();
4070
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4071
+ const sm = getSleepManagerIfReady2();
4068
4072
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4069
4073
  const flushed = await sm.flushQueuedMessages(name);
4070
4074
  return c.json({ ok: true, flushed });
@@ -4332,8 +4336,8 @@ ${user.trimEnd()}
4332
4336
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4333
4337
  const baseName = entry.parent ?? name;
4334
4338
  try {
4335
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
4336
- const sm = getSleepManagerIfReady();
4339
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
4340
+ const sm = getSleepManagerIfReady2();
4337
4341
  if (sm?.isSleeping(baseName)) {
4338
4342
  const body2 = await c.req.text();
4339
4343
  let parsed2 = null;
@@ -4464,6 +4468,34 @@ ${user.trimEnd()}
4464
4468
  logger_default.error(`delivery failed for ${name}`, logger_default.errorData(err));
4465
4469
  });
4466
4470
  return c.json({ ok: true });
4471
+ }).get("/:name/conversations", async (c) => {
4472
+ const name = c.req.param("name");
4473
+ const entry = await findMind(name);
4474
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4475
+ const convs = await listConversationsForMind(name);
4476
+ return c.json(convs);
4477
+ }).get("/:name/conversations/:convId/messages", async (c) => {
4478
+ const name = c.req.param("name");
4479
+ const convId = c.req.param("convId");
4480
+ const entry = await findMind(name);
4481
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
4482
+ const belongs = await isConversationForMind(name, convId);
4483
+ if (!belongs) {
4484
+ return c.json({ error: "Conversation not found" }, 404);
4485
+ }
4486
+ const beforeStr = c.req.query("before");
4487
+ const limitStr = c.req.query("limit");
4488
+ if (!beforeStr && !limitStr) {
4489
+ const msgs = await getMessages(convId);
4490
+ return c.json({ items: msgs, hasMore: false });
4491
+ }
4492
+ const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
4493
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4494
+ if (before !== void 0 && isNaN(before) || limit !== void 0 && isNaN(limit)) {
4495
+ return c.json({ error: "Invalid pagination parameters" }, 400);
4496
+ }
4497
+ const result = await getMessagesPaginated(convId, { before, limit });
4498
+ return c.json({ items: result.messages, hasMore: result.hasMore });
4467
4499
  }).get("/:name/budget", async (c) => {
4468
4500
  const name = c.req.param("name");
4469
4501
  const baseName = await getBaseName(name);
@@ -5256,13 +5288,45 @@ function writeSchedules(name, schedules) {
5256
5288
  config.schedules = schedules.length > 0 ? schedules : void 0;
5257
5289
  writeVoluteConfig(dir, config);
5258
5290
  getScheduler().loadSchedules(name);
5291
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
5259
5292
  fireWebhook({
5260
5293
  event: "schedule_changed",
5261
5294
  mind: name,
5262
5295
  data: { schedules }
5263
5296
  });
5264
5297
  }
5265
- var app16 = new Hono16().get("/:name/schedules", async (c) => {
5298
+ var app16 = new Hono16().get("/:name/clock/status", async (c) => {
5299
+ const name = c.req.param("name");
5300
+ if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5301
+ const sleepManager = getSleepManagerIfReady();
5302
+ const sleepState = sleepManager?.getState(name) ?? null;
5303
+ const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
5304
+ const schedules = readSchedules(name);
5305
+ const now = /* @__PURE__ */ new Date();
5306
+ const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
5307
+ const upcoming = [];
5308
+ for (const s of schedules) {
5309
+ if (!s.enabled) continue;
5310
+ if (s.fireAt) {
5311
+ const fireDate = new Date(s.fireAt);
5312
+ if (fireDate >= now && fireDate <= in24h) {
5313
+ upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
5314
+ }
5315
+ } else if (s.cron) {
5316
+ try {
5317
+ const interval = CronExpressionParser.parse(s.cron);
5318
+ const next = interval.next().toDate();
5319
+ if (next <= in24h) {
5320
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
5321
+ }
5322
+ } catch {
5323
+ slog.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
5324
+ }
5325
+ }
5326
+ }
5327
+ upcoming.sort((a, b) => a.at.localeCompare(b.at));
5328
+ return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming });
5329
+ }).get("/:name/schedules", async (c) => {
5266
5330
  const name = c.req.param("name");
5267
5331
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5268
5332
  return c.json(readSchedules(name));
@@ -5273,8 +5337,11 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5273
5337
  if (entry.stage === "seed")
5274
5338
  return c.json({ error: "Seed minds cannot use schedules \u2014 sprout first" }, 403);
5275
5339
  const body = await c.req.json();
5276
- if (!body.cron) {
5277
- return c.json({ error: "cron is required" }, 400);
5340
+ if (!body.cron && !body.fireAt) {
5341
+ return c.json({ error: "cron or fireAt is required" }, 400);
5342
+ }
5343
+ if (body.cron && body.fireAt) {
5344
+ return c.json({ error: "cron and fireAt are mutually exclusive" }, 400);
5278
5345
  }
5279
5346
  if (!body.message && !body.script) {
5280
5347
  return c.json({ error: "message or script is required" }, 400);
@@ -5282,20 +5349,36 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5282
5349
  if (body.message && body.script) {
5283
5350
  return c.json({ error: "message and script are mutually exclusive" }, 400);
5284
5351
  }
5285
- try {
5286
- CronExpressionParser.parse(body.cron);
5287
- } catch {
5288
- return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5352
+ if (body.cron) {
5353
+ try {
5354
+ CronExpressionParser.parse(body.cron);
5355
+ } catch {
5356
+ return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5357
+ }
5358
+ }
5359
+ if (body.fireAt && Number.isNaN(new Date(body.fireAt).getTime())) {
5360
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5361
+ }
5362
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5363
+ return c.json(
5364
+ {
5365
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5366
+ },
5367
+ 400
5368
+ );
5289
5369
  }
5290
5370
  const schedules = readSchedules(name);
5291
5371
  const id = body.id || `schedule-${Date.now()}`;
5292
5372
  if (schedules.some((s) => s.id === id)) {
5293
5373
  return c.json({ error: `Schedule "${id}" already exists` }, 409);
5294
5374
  }
5295
- const schedule = { id, cron: body.cron, enabled: body.enabled ?? true };
5375
+ const schedule = { id, enabled: body.enabled ?? true };
5376
+ if (body.cron) schedule.cron = body.cron;
5377
+ if (body.fireAt) schedule.fireAt = body.fireAt;
5296
5378
  if (body.message) schedule.message = body.message;
5297
5379
  if (body.script) schedule.script = body.script;
5298
5380
  if (body.channel) schedule.channel = body.channel;
5381
+ if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
5299
5382
  schedules.push(schedule);
5300
5383
  writeSchedules(name, schedules);
5301
5384
  return c.json({ ok: true, id }, 201);
@@ -5317,6 +5400,14 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5317
5400
  return c.json({ error: `Invalid cron expression: ${body.cron}` }, 400);
5318
5401
  }
5319
5402
  schedules[idx].cron = body.cron;
5403
+ delete schedules[idx].fireAt;
5404
+ }
5405
+ if (body.fireAt !== void 0) {
5406
+ if (Number.isNaN(new Date(body.fireAt).getTime())) {
5407
+ return c.json({ error: `Invalid fireAt date: ${body.fireAt}` }, 400);
5408
+ }
5409
+ schedules[idx].fireAt = body.fireAt;
5410
+ delete schedules[idx].cron;
5320
5411
  }
5321
5412
  if (body.message !== void 0) {
5322
5413
  schedules[idx].message = body.message;
@@ -5326,8 +5417,18 @@ var app16 = new Hono16().get("/:name/schedules", async (c) => {
5326
5417
  schedules[idx].script = body.script;
5327
5418
  delete schedules[idx].message;
5328
5419
  }
5420
+ if (body.whileSleeping && !["skip", "queue", "trigger-wake"].includes(body.whileSleeping)) {
5421
+ return c.json(
5422
+ {
5423
+ error: `Invalid whileSleeping value: ${body.whileSleeping} (must be skip, queue, or trigger-wake)`
5424
+ },
5425
+ 400
5426
+ );
5427
+ }
5329
5428
  if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
5330
5429
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
5430
+ if (body.whileSleeping !== void 0)
5431
+ schedules[idx].whileSleeping = body.whileSleeping || void 0;
5331
5432
  writeSchedules(name, schedules);
5332
5433
  return c.json({ ok: true });
5333
5434
  }).delete("/:name/schedules/:id", requireSelf(), async (c) => {
@@ -5674,11 +5775,11 @@ async function fanOutToMinds(opts) {
5674
5775
  const mindParticipants = participants.filter((p) => p.userType === "mind");
5675
5776
  const participantNames = participants.map((p) => p.username);
5676
5777
  const isDM = opts.isDM ?? participants.length === 2;
5677
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
5678
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
5679
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
5778
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
5779
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
5780
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
5680
5781
  const manager = getMindManager2();
5681
- const sm = getSleepManagerIfReady();
5782
+ const sm = getSleepManagerIfReady2();
5682
5783
  const targetMinds = mindParticipants.map((ap) => {
5683
5784
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
5684
5785
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -5797,7 +5898,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5797
5898
  senderName,
5798
5899
  convTitle,
5799
5900
  isDM,
5800
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
5901
+ channelEntryType: isDM ? "dm" : "channel",
5801
5902
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
5802
5903
  targetName: (username) => username === baseName ? name : username
5803
5904
  });
@@ -5854,7 +5955,7 @@ var app22 = new Hono22().use("*", authMiddleware).post("/minds/:name/chat", zVal
5854
5955
  senderName,
5855
5956
  convTitle: conv.title,
5856
5957
  isDM,
5857
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
5958
+ channelEntryType: isDM ? "dm" : "channel",
5858
5959
  slugExtra: { convType: conv.type, convName: conv.name }
5859
5960
  });
5860
5961
  return c.json({ ok: true, conversationId: body.conversationId });
@@ -6608,11 +6709,11 @@ async function fanOutToMinds2(opts) {
6608
6709
  const mindParticipants = participants.filter((p) => p.userType === "mind");
6609
6710
  const participantNames = participants.map((p) => p.username);
6610
6711
  const isDM = opts.isDM ?? participants.length === 2;
6611
- const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "group");
6612
- const { getMindManager: getMindManager2 } = await import("./mind-manager-S6ILZVX3.js");
6613
- const { getSleepManagerIfReady } = await import("./sleep-manager-WMVG2VCL.js");
6712
+ const channelEntryType = opts.channelEntryType ?? (isDM ? "dm" : "channel");
6713
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-P66HQDNE.js");
6714
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-G4B5GW5P.js");
6614
6715
  const manager = getMindManager2();
6615
- const sm = getSleepManagerIfReady();
6716
+ const sm = getSleepManagerIfReady2();
6616
6717
  const targetMinds = mindParticipants.map((ap) => {
6617
6718
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
6618
6719
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
@@ -6771,7 +6872,7 @@ var app27 = new Hono27().post("/:name/chat", zValidator12("json", chatSchema), a
6771
6872
  senderName,
6772
6873
  convTitle,
6773
6874
  isDM,
6774
- channelEntryType: conv?.type === "channel" ? "group" : isDM ? "dm" : "group",
6875
+ channelEntryType: isDM ? "dm" : "channel",
6775
6876
  slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
6776
6877
  targetName: (username) => username === baseName ? name : username
6777
6878
  });
@@ -6879,7 +6980,7 @@ var unifiedChatApp = new Hono27().post(
6879
6980
  senderName,
6880
6981
  convTitle: conv.title,
6881
6982
  isDM,
6882
- channelEntryType: conv.type === "channel" ? "group" : isDM ? "dm" : "group",
6983
+ channelEntryType: isDM ? "dm" : "channel",
6883
6984
  slugExtra: { convType: conv.type, convName: conv.name }
6884
6985
  });
6885
6986
  return c.json({ ok: true, conversationId: body.conversationId });
@@ -6949,6 +7050,9 @@ var app28 = new Hono28().get("/:name/conversations", async (c) => {
6949
7050
  if (!u) return c.json({ error: `User ${id} not found` }, 400);
6950
7051
  }
6951
7052
  const participantIds = [...participantSet];
7053
+ if (participantIds.length > 2) {
7054
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7055
+ }
6952
7056
  if (participantIds.length === 2) {
6953
7057
  const existingId = await findDMConversation(name, participantIds);
6954
7058
  if (existingId) {
@@ -7037,6 +7141,9 @@ var app29 = new Hono29().use("*", authMiddleware).get("/", async (c) => {
7037
7141
  if (!firstMindName) {
7038
7142
  return c.json({ error: "At least one mind participant is required" }, 400);
7039
7143
  }
7144
+ if (participantIds.size > 2) {
7145
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7146
+ }
7040
7147
  const conv = await createConversation(firstMindName, "volute", {
7041
7148
  userId: user.id !== 0 ? user.id : void 0,
7042
7149
  title: body.title,
@@ -7282,6 +7389,12 @@ async function startDaemon(opts) {
7282
7389
  await (await import("./db-IC4J52XQ.js")).getDb();
7283
7390
  const { migrateRegistryToDb } = await import("./migrate-registry-to-db-XC7T5B7P.js");
7284
7391
  migrateRegistryToDb();
7392
+ try {
7393
+ const { migrateGroupDMsToChannels } = await import("./conversations-P5BL7RMX.js");
7394
+ await migrateGroupDMsToChannels();
7395
+ } catch (err) {
7396
+ logger_default.error("failed to migrate group DMs to channels", logger_default.errorData(err));
7397
+ }
7285
7398
  const { initSandbox } = await import("./sandbox-5BW5HPXM.js");
7286
7399
  await initSandbox();
7287
7400
  try {
@@ -7294,6 +7407,8 @@ async function startDaemon(opts) {
7294
7407
  } catch (err) {
7295
7408
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
7296
7409
  }
7410
+ const { startSystemWatcher } = await import("./pages-watcher-P7QECRE2.js");
7411
+ startSystemWatcher();
7297
7412
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
7298
7413
  let tls;
7299
7414
  if (opts.tailscale) {
@@ -7378,7 +7493,7 @@ async function startDaemon(opts) {
7378
7493
  bridgeManager.startBridges(daemonPort).catch((err) => {
7379
7494
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
7380
7495
  });
7381
- import("./cloud-sync-HDL6PHZI.js").then(
7496
+ import("./cloud-sync-KILFGV5Q.js").then(
7382
7497
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
7383
7498
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
7384
7499
  })
@@ -7386,7 +7501,7 @@ async function startDaemon(opts) {
7386
7501
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
7387
7502
  });
7388
7503
  try {
7389
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-JDUF4HQJ.js");
7504
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-WDHRO3XD.js");
7390
7505
  backfillTemplateHashes();
7391
7506
  notifyVersionUpdate().catch((err) => {
7392
7507
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -2,23 +2,26 @@
2
2
  import {
3
3
  deliverMessage,
4
4
  extractTextContent,
5
- recordInbound
6
- } from "./chunk-AAPXKR5V.js";
7
- import "./chunk-IAYBDWVG.js";
8
- import "./chunk-K5NAC55T.js";
5
+ recordInbound,
6
+ resolveSleepAction
7
+ } from "./chunk-CMILSHZD.js";
8
+ import "./chunk-THUUIU3E.js";
9
+ import "./chunk-FLZGS4QH.js";
10
+ import "./chunk-CQ7SNKNI.js";
9
11
  import "./chunk-VIVMW2H2.js";
10
- import "./chunk-POSXWWTA.js";
12
+ import "./chunk-EHZKEMMV.js";
11
13
  import "./chunk-J4IBNXGJ.js";
12
14
  import "./chunk-2WPW7OT6.js";
13
15
  import "./chunk-YUIHSKR6.js";
14
16
  import "./chunk-AW7PFDVN.js";
15
17
  import "./chunk-RKQEHRBB.js";
16
18
  import "./chunk-IKRVFPWU.js";
17
- import "./chunk-T6HKBWXZ.js";
19
+ import "./chunk-A6TUJJ3L.js";
18
20
  import "./chunk-H7OZRFJB.js";
19
21
  import "./chunk-K3NQKI34.js";
20
22
  export {
21
23
  deliverMessage,
22
24
  extractTextContent,
23
- recordInbound
25
+ recordInbound,
26
+ resolveSleepAction
24
27
  };
@@ -4,7 +4,7 @@ import {
4
4
  markIdle,
5
5
  onMindEvent,
6
6
  stopAll
7
- } from "./chunk-K5NAC55T.js";
7
+ } from "./chunk-CQ7SNKNI.js";
8
8
  import "./chunk-VIVMW2H2.js";
9
9
  import "./chunk-YUIHSKR6.js";
10
10
  import "./chunk-H7OZRFJB.js";
@@ -3,7 +3,7 @@ import {
3
3
  MindManager,
4
4
  getMindManager,
5
5
  initMindManager
6
- } from "./chunk-POSXWWTA.js";
6
+ } from "./chunk-EHZKEMMV.js";
7
7
  import "./chunk-J4IBNXGJ.js";
8
8
  import "./chunk-2WPW7OT6.js";
9
9
  import "./chunk-YUIHSKR6.js";
@@ -4,7 +4,7 @@ import "./chunk-K3NQKI34.js";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "volute",
7
- version: "0.28.0",
7
+ version: "0.29.0",
8
8
  description: "CLI for creating and managing self-modifying AI minds powered by the Claude Agent SDK",
9
9
  type: "module",
10
10
  license: "MIT",
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getCachedRecentPages,
4
+ getCachedSites,
5
+ startSystemWatcher,
6
+ startWatcher,
7
+ stopAllWatchers,
8
+ stopWatcher
9
+ } from "./chunk-THUUIU3E.js";
10
+ import "./chunk-VIVMW2H2.js";
11
+ import "./chunk-YUIHSKR6.js";
12
+ import "./chunk-H7OZRFJB.js";
13
+ import "./chunk-K3NQKI34.js";
14
+ export {
15
+ getCachedRecentPages,
16
+ getCachedSites,
17
+ startSystemWatcher,
18
+ startWatcher,
19
+ stopAllWatchers,
20
+ stopWatcher
21
+ };
@@ -34,23 +34,9 @@ Add to `.config/volute.json` under `schedules`:
34
34
  Or via CLI:
35
35
 
36
36
  ```bash
37
- volute schedule add --mind <name> --id dream --cron "0 3 * * *" --message "it's 3am. you are dreaming...."
37
+ volute clock add --mind <name> --id dream --cron "0 3 * * *" --channel system:dream --while-sleeping trigger-wake --message "it's 3am. you are dreaming...."
38
38
  ```
39
39
 
40
- ## 3. Sleep integration (optional)
40
+ ## 3. Sleep integration
41
41
 
42
- If your mind uses the sleep system, add `system:dream` to wake triggers so the dream schedule wakes the mind briefly:
43
-
44
- In `.config/volute.json`, add to the `sleep` section:
45
-
46
- ```json
47
- {
48
- "sleep": {
49
- "wakeTriggers": {
50
- "channels": ["system:dream"]
51
- }
52
- }
53
- }
54
- ```
55
-
56
- The mind will wake for the dream, then return to sleep when done.
42
+ The `--while-sleeping trigger-wake` flag on the schedule tells the clock system to briefly wake the mind for the dream, then return to sleep when done. No additional wake trigger configuration is needed.