volute 0.28.0 → 0.30.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 (134) hide show
  1. package/README.md +127 -18
  2. package/dist/{accept-666DIZX2.js → accept-E3PAH3QJ.js} +2 -2
  3. package/dist/{activity-events-BBIEA2F4.js → activity-events-BKBPPUBP.js} +2 -2
  4. package/dist/ai-service-VAJT5UBS.js +29 -0
  5. package/dist/api.d.ts +586 -529
  6. package/dist/{archive-UA4BDFXQ.js → archive-WWDBWYN2.js} +2 -2
  7. package/dist/{bridge-FQHZL3MC.js → bridge-RO37CUFM.js} +2 -2
  8. package/dist/{chat-M4SX42JD.js → chat-TCUNPFGO.js} +8 -8
  9. package/dist/{chunk-IAYBDWVG.js → chunk-2C2VXEBB.js} +147 -2
  10. package/dist/chunk-2NDZC3S7.js +1330 -0
  11. package/dist/{chunk-IKRVFPWU.js → chunk-7D47T4RB.js} +3 -2
  12. package/dist/chunk-A6TUJJ3L.js +19 -0
  13. package/dist/{chunk-AW7PFDVN.js → chunk-CVH6Y2YG.js} +1 -1
  14. package/dist/{chunk-XBLSAVJF.js → chunk-DTC6EH5I.js} +1 -1
  15. package/dist/chunk-EFP3PE6C.js +232 -0
  16. package/dist/{chunk-JGFVMROS.js → chunk-EFVHR7KH.js} +1 -1
  17. package/dist/{chunk-K5NAC55T.js → chunk-FSM45XD5.js} +2 -2
  18. package/dist/{chunk-LAC664WU.js → chunk-FXHXHI2A.js} +42 -24
  19. package/dist/{chunk-RKQEHRBB.js → chunk-G3GBKZGG.js} +1 -1
  20. package/dist/{chunk-H7OZRFJB.js → chunk-HHTXM4JT.js} +0 -49
  21. package/dist/{chunk-J4IBNXGJ.js → chunk-IKHDUZRH.js} +4 -3
  22. package/dist/{chunk-MD4C26II.js → chunk-JGFRDMR6.js} +1 -1
  23. package/dist/{chunk-POSXWWTA.js → chunk-LIRWLNAK.js} +26 -12
  24. package/dist/{chunk-NI5FFCCS.js → chunk-MDPCSXZ4.js} +35 -11
  25. package/dist/chunk-NSBFETWP.js +188 -0
  26. package/dist/{chunk-VIVMW2H2.js → chunk-P27RV5WM.js} +1 -1
  27. package/dist/{chunk-EHYDTZTF.js → chunk-P7VFDSSG.js} +2 -2
  28. package/dist/{chunk-AAPXKR5V.js → chunk-QVAQ5454.js} +181 -544
  29. package/dist/{chunk-HDN7MNGD.js → chunk-S5LR3XYJ.js} +1 -1
  30. package/dist/{chunk-2YP2TVDT.js → chunk-UPA6COHU.js} +5 -5
  31. package/dist/{chunk-AKPFNL7L.js → chunk-VGWJSNHS.js} +1 -1
  32. package/dist/{chunk-SGVNFZHW.js → chunk-W5OOPLNP.js} +3 -3
  33. package/dist/{chunk-2WPW7OT6.js → chunk-ZWKTUQEL.js} +1 -1
  34. package/dist/cli.js +25 -26
  35. package/dist/clock-G3ALCMLJ.js +263 -0
  36. package/dist/{cloud-sync-HDL6PHZI.js → cloud-sync-JV4LJOK3.js} +14 -12
  37. package/dist/connectors/discord-bridge.js +1 -1
  38. package/dist/connectors/slack-bridge.js +1 -1
  39. package/dist/connectors/telegram-bridge.js +1 -1
  40. package/dist/{conversations-M2K4253F.js → conversations-7KVQV7EZ.js} +9 -3
  41. package/dist/create-JTLS7GX3.js +70 -0
  42. package/dist/{create-QWV73WXD.js → create-VQSQHJQW.js} +1 -1
  43. package/dist/{daemon-client-I42FK2BF.js → daemon-client-BCTFGVCZ.js} +2 -2
  44. package/dist/{daemon-restart-G4B2OYAB.js → daemon-restart-4JGBHEJ4.js} +7 -7
  45. package/dist/daemon.js +1474 -1124
  46. package/dist/{db-IC4J52XQ.js → db-HMFPIRO2.js} +1 -1
  47. package/dist/{delete-4JYGD4VN.js → delete-JESHKE7F.js} +1 -1
  48. package/dist/down-NGBMGORS.js +14 -0
  49. package/dist/{env-YJMUMFIY.js → env-CLXXT7M2.js} +2 -2
  50. package/dist/{export-BOJQWBMA.js → export-EGA5M5PB.js} +3 -3
  51. package/dist/extension-WZ4SUPJB.js +174 -0
  52. package/dist/extensions-ECO4RPFQ.js +27 -0
  53. package/dist/{files-M546TKVN.js → files-4VEJDASH.js} +3 -3
  54. package/dist/{history-ALPTNB3I.js → history-EJMMLXDO.js} +17 -2
  55. package/dist/{import-SRTQXBGH.js → import-YCGPMBSI.js} +3 -3
  56. package/dist/{join-J4QU42DL.js → join-2GBJKZEN.js} +1 -1
  57. package/dist/{list-R73GENNL.js → list-Q6O7FGAN.js} +2 -2
  58. package/dist/{login-3QZNR2DF.js → login-RET5WESK.js} +2 -2
  59. package/dist/{login-BKP3AFWN.js → login-RL6AU2SM.js} +3 -3
  60. package/dist/{logout-T53VKCPU.js → logout-CGAGJN3L.js} +2 -2
  61. package/dist/{logout-IQK7FNEK.js → logout-JRPBEMMR.js} +3 -3
  62. package/dist/message-delivery-6YMVNOEC.js +28 -0
  63. package/dist/{migrate-registry-to-db-XC7T5B7P.js → migrate-registry-to-db-FK35IPEH.js} +1 -1
  64. package/dist/{mind-S5V6CK5W.js → mind-LUWRQUQ5.js} +17 -17
  65. package/dist/{mind-activity-tracker-EN6XNXPF.js → mind-activity-tracker-VYN2ZZ2M.js} +3 -3
  66. package/dist/{mind-list-UPJ75GPI.js → mind-list-V5WW5DUA.js} +2 -2
  67. package/dist/{mind-manager-S6ILZVX3.js → mind-manager-YFCOIAAX.js} +6 -6
  68. package/dist/{mind-sleep-BTSWQNAC.js → mind-sleep-R6PTNNW4.js} +2 -2
  69. package/dist/{mind-status-TK5AETEM.js → mind-status-I4ISFJ6I.js} +2 -2
  70. package/dist/{mind-wake-SBAKIDVP.js → mind-wake-67ZQEWAV.js} +2 -2
  71. package/dist/{package-CG4RWUGP.js → package-S2OAA5ZA.js} +11 -5
  72. package/dist/pages-watcher-Z3PKNROC.js +21 -0
  73. package/dist/{read-36UFXN3G.js → read-WQMPTSN2.js} +2 -2
  74. package/dist/{register-CHREOMJ3.js → register-NZDSTLP3.js} +3 -3
  75. package/dist/{registry-NDNOOYG4.js → registry-ODSALQQL.js} +1 -1
  76. package/dist/{reject-LXIZFJ4Q.js → reject-2HZOJEIJ.js} +2 -2
  77. package/dist/{restart-6ESL3NBO.js → restart-QHS3NT64.js} +2 -2
  78. package/dist/{sandbox-5BW5HPXM.js → sandbox-O5FUSF43.js} +3 -3
  79. package/dist/{seed-SSUCYYDF.js → seed-WUQMPLDM.js} +1 -1
  80. package/dist/{send-TAOEZ4NH.js → send-OAN3RYYY.js} +20 -6
  81. package/dist/{setup-JHL5ZEST.js → setup-QMDK5RZX.js} +2 -2
  82. package/dist/{setup-RXYVGGT7.js → setup-XJH3E7YM.js} +45 -14
  83. package/dist/{skill-AUAQTSP5.js → skill-FZIN4W4Q.js} +65 -3
  84. package/dist/skills/dreaming/references/INSTALL.md +3 -17
  85. package/dist/skills/volute-mind/SKILL.md +45 -27
  86. package/dist/sleep-manager-O7YQFCV5.js +30 -0
  87. package/dist/{split-TKJ5OT3P.js → split-EXYGGGQN.js} +1 -1
  88. package/dist/{sprout-UNT7LKKE.js → sprout-AXQ6H5DB.js} +8 -7
  89. package/dist/{start-EUJSS5R4.js → start-MTOVL6SY.js} +2 -2
  90. package/dist/{status-NQJYR4BG.js → status-ZRO37MWR.js} +5 -5
  91. package/dist/{stop-3XAITBBF.js → stop-OK5WEPVC.js} +2 -2
  92. package/dist/{systems-SMEFSHTA.js → systems-W3BBMSOZ.js} +5 -5
  93. package/dist/{tailscale-NY5MUMY3.js → tailscale-BM72RXCJ.js} +1 -1
  94. package/dist/{template-hash-BIMA4ILT.js → template-hash-3HOR4UAJ.js} +1 -1
  95. package/dist/up-BXUAIDXB.js +17 -0
  96. package/dist/{update-PTSH22AZ.js → update-PLPHMMZ2.js} +5 -5
  97. package/dist/{update-check-64FWC4Y2.js → update-check-CVCN7MF6.js} +2 -2
  98. package/dist/{upgrade-HA47CS4C.js → upgrade-I6NPCYUU.js} +1 -1
  99. package/dist/{version-notify-JDUF4HQJ.js → version-notify-2NTWVEHL.js} +18 -16
  100. package/dist/web-assets/assets/index--kREqKl9.js +72 -0
  101. package/dist/web-assets/assets/index-BXYTG0nJ.css +1 -0
  102. package/dist/web-assets/ext-theme.css +111 -0
  103. package/dist/web-assets/index.html +2 -2
  104. package/package.json +11 -5
  105. package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +1 -0
  106. package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +2 -0
  107. package/packages/extensions/notes/dist/ui/index.html +14 -0
  108. package/packages/extensions/notes/skills/notes/SKILL.md +62 -0
  109. package/packages/extensions/notes/skills/notes/scripts/notes.mjs +185 -0
  110. package/packages/extensions/pages/dist/ui/assets/index-D0HyS-xQ.css +1 -0
  111. package/packages/extensions/pages/dist/ui/assets/index-tLTROSk5.js +2 -0
  112. package/packages/extensions/pages/dist/ui/index.html +14 -0
  113. package/packages/extensions/pages/skills/pages/SKILL.md +58 -0
  114. package/templates/_base/home/VOLUTE.md +1 -1
  115. package/templates/_base/src/lib/logger.ts +10 -49
  116. package/templates/_base/src/lib/router.ts +1 -9
  117. package/templates/claude/src/lib/stream-consumer.ts +1 -4
  118. package/templates/pi/src/lib/event-handler.ts +1 -14
  119. package/dist/chunk-P72MVS4R.js +0 -188
  120. package/dist/chunk-T6HKBWXZ.js +0 -23
  121. package/dist/chunk-ZYGKG6VC.js +0 -22
  122. package/dist/create-D7J73A6H.js +0 -45
  123. package/dist/down-LVBXEULC.js +0 -14
  124. package/dist/message-delivery-HV3S6HZV.js +0 -24
  125. package/dist/notes-XCER3I7M.js +0 -220
  126. package/dist/pages-KJDJX4TA.js +0 -36
  127. package/dist/publish-ZZB33WP4.js +0 -86
  128. package/dist/schedule-QTJMFATP.js +0 -154
  129. package/dist/skills/notes/SKILL.md +0 -34
  130. package/dist/sleep-manager-WMVG2VCL.js +0 -28
  131. package/dist/status-S7UUPNRW.js +0 -38
  132. package/dist/up-GM2JOH2Y.js +0 -17
  133. package/dist/web-assets/assets/index-BZGvToHi.css +0 -1
  134. package/dist/web-assets/assets/index-Cz4TrpzB.js +0 -75
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ markIdle
4
+ } from "./chunk-FSM45XD5.js";
5
+ import {
6
+ clearJsonMap,
7
+ getMindManager,
8
+ getPrompt,
9
+ loadJsonMap,
10
+ saveJsonMap
11
+ } from "./chunk-LIRWLNAK.js";
2
12
  import {
3
13
  addMessage,
4
14
  createChannel,
@@ -6,28 +16,24 @@ import {
6
16
  getParticipants,
7
17
  joinChannel,
8
18
  publish as publish2
9
- } from "./chunk-IAYBDWVG.js";
19
+ } from "./chunk-2C2VXEBB.js";
10
20
  import {
11
- markIdle
12
- } from "./chunk-K5NAC55T.js";
21
+ getOrCreateMindUser,
22
+ notifyExtensionsMindStart,
23
+ notifyExtensionsMindStop,
24
+ readSystemsConfig,
25
+ syncMindProfile
26
+ } from "./chunk-2NDZC3S7.js";
13
27
  import {
14
- broadcast,
15
28
  publish,
16
29
  subscribe
17
- } from "./chunk-VIVMW2H2.js";
18
- import {
19
- clearJsonMap,
20
- getMindManager,
21
- getPrompt,
22
- loadJsonMap,
23
- saveJsonMap
24
- } from "./chunk-POSXWWTA.js";
30
+ } from "./chunk-P27RV5WM.js";
25
31
  import {
26
32
  logger_default
27
33
  } from "./chunk-YUIHSKR6.js";
28
34
  import {
29
35
  exec
30
- } from "./chunk-AW7PFDVN.js";
36
+ } from "./chunk-CVH6Y2YG.js";
31
37
  import {
32
38
  deliveryQueue,
33
39
  findMind,
@@ -37,27 +43,25 @@ import {
37
43
  mindHistory,
38
44
  readRegistry,
39
45
  stateDir,
40
- users,
41
46
  voluteHome,
42
- voluteSystemDir,
43
- voluteUserHome
44
- } from "./chunk-H7OZRFJB.js";
47
+ voluteSystemDir
48
+ } from "./chunk-HHTXM4JT.js";
45
49
 
46
50
  // src/lib/daemon/sleep-manager.ts
47
51
  import { execFile, spawn as spawnChild } from "child_process";
48
52
  import {
49
- existsSync as existsSync6,
50
- mkdirSync as mkdirSync5,
51
- readdirSync as readdirSync2,
52
- readFileSync as readFileSync6,
53
+ existsSync as existsSync4,
54
+ mkdirSync as mkdirSync4,
55
+ readdirSync,
56
+ readFileSync as readFileSync5,
53
57
  readlinkSync,
54
- renameSync as renameSync2,
55
- writeFileSync as writeFileSync5
58
+ renameSync,
59
+ writeFileSync as writeFileSync4
56
60
  } from "fs";
57
- import { resolve as resolve9 } from "path";
61
+ import { resolve as resolve7 } from "path";
58
62
  import { promisify } from "util";
59
63
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
60
- import { and as and3, eq as eq3, inArray as inArray2 } from "drizzle-orm";
64
+ import { and as and2, eq as eq2, inArray } from "drizzle-orm";
61
65
 
62
66
  // src/lib/volute-config.ts
63
67
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -95,344 +99,9 @@ function writeVoluteConfig(mindDir2, config) {
95
99
  `);
96
100
  }
97
101
 
98
- // src/lib/auth.ts
99
- import { compareSync, hashSync } from "bcryptjs";
100
- import { and, count, eq, inArray } from "drizzle-orm";
101
- var userSelectFields = {
102
- id: users.id,
103
- username: users.username,
104
- role: users.role,
105
- user_type: users.user_type,
106
- display_name: users.display_name,
107
- description: users.description,
108
- avatar: users.avatar,
109
- created_at: users.created_at
110
- };
111
- async function createUser(username, password) {
112
- const db = await getDb();
113
- const hash = hashSync(password, 10);
114
- const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.user_type, "brain"));
115
- const role = value === 0 ? "admin" : "pending";
116
- const [result] = await db.insert(users).values({ username, password_hash: hash, role }).returning(userSelectFields);
117
- return result;
118
- }
119
- async function verifyUser(username, password) {
120
- const db = await getDb();
121
- const row = await db.select().from(users).where(eq(users.username, username)).get();
122
- if (!row) return null;
123
- if (row.user_type === "mind") return null;
124
- if (!compareSync(password, row.password_hash)) return null;
125
- const { password_hash: _, ...user } = row;
126
- return user;
127
- }
128
- async function getUser(id) {
129
- const db = await getDb();
130
- const row = await db.select(userSelectFields).from(users).where(eq(users.id, id)).get();
131
- return row ?? null;
132
- }
133
- async function getUserByUsername(username) {
134
- const db = await getDb();
135
- const row = await db.select(userSelectFields).from(users).where(eq(users.username, username)).get();
136
- return row ?? null;
137
- }
138
- async function listUsers() {
139
- const db = await getDb();
140
- return db.select(userSelectFields).from(users).orderBy(users.created_at).all();
141
- }
142
- async function listPendingUsers() {
143
- const db = await getDb();
144
- return db.select(userSelectFields).from(users).where(eq(users.role, "pending")).orderBy(users.created_at).all();
145
- }
146
- async function listUsersByType(userType) {
147
- const db = await getDb();
148
- return db.select(userSelectFields).from(users).where(eq(users.user_type, userType)).orderBy(users.created_at).all();
149
- }
150
- async function getOrCreateMindUser(mindName) {
151
- const db = await getDb();
152
- const existing = await db.select(userSelectFields).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
153
- if (existing) return existing;
154
- try {
155
- const [result] = await db.insert(users).values({
156
- username: mindName,
157
- password_hash: "!mind",
158
- role: "user",
159
- user_type: "mind"
160
- }).returning(userSelectFields);
161
- return result;
162
- } catch (err) {
163
- if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
164
- const retried = await db.select(userSelectFields).from(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind"))).get();
165
- if (retried) return retried;
166
- }
167
- throw err;
168
- }
169
- }
170
- async function deleteMindUser(mindName) {
171
- const db = await getDb();
172
- await db.delete(users).where(and(eq(users.username, mindName), eq(users.user_type, "mind")));
173
- }
174
- async function changePassword(userId, currentPassword, newPassword) {
175
- const db = await getDb();
176
- const row = await db.select().from(users).where(eq(users.id, userId)).get();
177
- if (!row) return false;
178
- if (!compareSync(currentPassword, row.password_hash)) return false;
179
- const hash = hashSync(newPassword, 10);
180
- await db.update(users).set({ password_hash: hash }).where(eq(users.id, userId));
181
- return true;
182
- }
183
- async function approveUser(id) {
184
- const db = await getDb();
185
- await db.update(users).set({ role: "user" }).where(and(eq(users.id, id), eq(users.role, "pending")));
186
- }
187
- async function countAdmins() {
188
- const db = await getDb();
189
- const [{ value }] = await db.select({ value: count() }).from(users).where(eq(users.role, "admin"));
190
- return value;
191
- }
192
- async function setUserRole(id, role) {
193
- const db = await getDb();
194
- const target = await db.select({ id: users.id }).from(users).where(eq(users.id, id)).get();
195
- if (!target) throw new Error("User not found");
196
- await db.update(users).set({ role }).where(eq(users.id, id));
197
- }
198
- async function deleteUser(id) {
199
- const db = await getDb();
200
- const target = await db.select({ id: users.id }).from(users).where(and(eq(users.id, id), eq(users.user_type, "brain"))).get();
201
- if (!target) throw new Error("User not found");
202
- await db.delete(users).where(and(eq(users.id, id), eq(users.user_type, "brain")));
203
- }
204
- async function updateUserProfile(userId, profile) {
205
- const db = await getDb();
206
- const target = await db.select({ id: users.id }).from(users).where(eq(users.id, userId)).get();
207
- if (!target) throw new Error("User not found");
208
- await db.update(users).set(profile).where(eq(users.id, userId));
209
- }
210
- async function syncMindProfile(mindName, config) {
211
- const user = await getOrCreateMindUser(mindName);
212
- const newProfile = {
213
- display_name: config.displayName ?? null,
214
- description: config.description ?? null,
215
- avatar: config.avatar ?? null
216
- };
217
- const changed = user.display_name !== newProfile.display_name || user.description !== newProfile.description || user.avatar !== newProfile.avatar;
218
- if (!changed) return;
219
- const db = await getDb();
220
- await db.update(users).set(newProfile).where(eq(users.id, user.id));
221
- broadcast({ type: "profile_updated", mind: mindName, summary: `${mindName} profile updated` });
222
- }
223
- async function migrateMindRoles() {
224
- const db = await getDb();
225
- await db.update(users).set({ role: "user" }).where(and(eq(users.user_type, "mind"), inArray(users.role, ["mind", "agent"])));
226
- }
227
-
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
102
  // src/connectors/sdk.ts
434
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
435
- import { join as join2, resolve as resolve3 } from "path";
103
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
104
+ import { join, resolve as resolve2 } from "path";
436
105
  function splitMessage(text, maxLength) {
437
106
  const chunks = [];
438
107
  while (text.length > maxLength) {
@@ -445,8 +114,8 @@ function splitMessage(text, maxLength) {
445
114
  return chunks;
446
115
  }
447
116
  function readChannelMap(mindName) {
448
- const filePath = join2(stateDir(mindName), "channels.json");
449
- if (!existsSync3(filePath)) return {};
117
+ const filePath = join(stateDir(mindName), "channels.json");
118
+ if (!existsSync2(filePath)) return {};
450
119
  try {
451
120
  return JSON.parse(readFileSync2(filePath, "utf-8"));
452
121
  } catch (err) {
@@ -457,10 +126,11 @@ function readChannelMap(mindName) {
457
126
  function writeChannelEntry(mindName, slug, entry) {
458
127
  const dir = stateDir(mindName);
459
128
  mkdirSync2(dir, { recursive: true });
460
- const filePath = join2(dir, "channels.json");
129
+ const filePath = join(dir, "channels.json");
461
130
  const map = readChannelMap(mindName);
462
131
  map[slug] = entry;
463
- writeFileSync2(filePath, JSON.stringify(map, null, 2) + "\n");
132
+ writeFileSync2(filePath, `${JSON.stringify(map, null, 2)}
133
+ `);
464
134
  }
465
135
  function resolveChannelId(mindName, slug) {
466
136
  const map = readChannelMap(mindName);
@@ -501,8 +171,8 @@ function publish3(mind, event) {
501
171
 
502
172
  // src/lib/delivery/delivery-manager.ts
503
173
  import { readFile, realpath } from "fs/promises";
504
- import { extname, resolve as resolve5 } from "path";
505
- import { and as and2, eq as eq2, sql } from "drizzle-orm";
174
+ import { extname, resolve as resolve4 } from "path";
175
+ import { and, eq, sql } from "drizzle-orm";
506
176
 
507
177
  // src/lib/typing.ts
508
178
  var DEFAULT_TTL_MS = 1e4;
@@ -595,8 +265,8 @@ function publishTypingForChannels(channels, map) {
595
265
  }
596
266
 
597
267
  // src/lib/delivery/delivery-router.ts
598
- import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
599
- import { resolve as resolve4 } from "path";
268
+ import { readFileSync as readFileSync3, statSync } from "fs";
269
+ import { resolve as resolve3 } from "path";
600
270
  function extractTextContent(content) {
601
271
  if (typeof content === "string") return content;
602
272
  if (Array.isArray(content)) {
@@ -609,7 +279,7 @@ var statCheckCache = /* @__PURE__ */ new Map();
609
279
  var STAT_TTL_MS = 5e3;
610
280
  var dlog = logger_default.child("delivery-router");
611
281
  function configPath(mindName) {
612
- return resolve4(mindDir(mindName), "home/.config/routes.json");
282
+ return resolve3(mindDir(mindName), "home/.config/routes.json");
613
283
  }
614
284
  function getRoutingConfig(mindName) {
615
285
  const path = configPath(mindName);
@@ -621,7 +291,7 @@ function getRoutingConfig(mindName) {
621
291
  }
622
292
  let mtime;
623
293
  try {
624
- mtime = statSync2(path).mtimeMs;
294
+ mtime = statSync(path).mtimeMs;
625
295
  } catch {
626
296
  configCache.delete(mindName);
627
297
  statCheckCache.delete(mindName);
@@ -868,7 +538,7 @@ var DeliveryManager = class {
868
538
  async restoreFromDb() {
869
539
  try {
870
540
  const db = await getDb();
871
- const rows = await db.select().from(deliveryQueue).where(eq2(deliveryQueue.status, "pending"));
541
+ const rows = await db.select().from(deliveryQueue).where(eq(deliveryQueue.status, "pending"));
872
542
  for (const row of rows) {
873
543
  let payload;
874
544
  try {
@@ -886,7 +556,7 @@ var DeliveryManager = class {
886
556
  this.addToBatchBuffer(row.mind, row.session, payload, sessionConfig);
887
557
  } else {
888
558
  try {
889
- await db.delete(deliveryQueue).where(eq2(deliveryQueue.id, row.id));
559
+ await db.delete(deliveryQueue).where(eq(deliveryQueue.id, row.id));
890
560
  } catch (err) {
891
561
  dlog2.warn(`failed to delete queue row ${row.id} for ${row.mind}`, logger_default.errorData(err));
892
562
  }
@@ -907,7 +577,7 @@ var DeliveryManager = class {
907
577
  */
908
578
  async getPending(mindName) {
909
579
  const db = await getDb();
910
- const rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, mindName), eq2(deliveryQueue.status, "gated")));
580
+ const rows = await db.select().from(deliveryQueue).where(and(eq(deliveryQueue.mind, mindName), eq(deliveryQueue.status, "gated")));
911
581
  const byChannel = /* @__PURE__ */ new Map();
912
582
  for (const row of rows) {
913
583
  const ch = row.channel ?? "unknown";
@@ -1068,10 +738,10 @@ var DeliveryManager = class {
1068
738
  try {
1069
739
  const db = await getDb();
1070
740
  await db.delete(deliveryQueue).where(
1071
- and2(
1072
- eq2(deliveryQueue.mind, baseName),
1073
- eq2(deliveryQueue.session, session),
1074
- eq2(deliveryQueue.status, "pending")
741
+ and(
742
+ eq(deliveryQueue.mind, baseName),
743
+ eq(deliveryQueue.session, session),
744
+ eq(deliveryQueue.status, "pending")
1075
745
  )
1076
746
  );
1077
747
  } catch (err) {
@@ -1197,14 +867,14 @@ var DeliveryManager = class {
1197
867
  await this.persistToQueue(baseName, session, payload, "gated");
1198
868
  try {
1199
869
  const db = await getDb();
1200
- const count2 = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
1201
- and2(
1202
- eq2(deliveryQueue.mind, baseName),
1203
- eq2(deliveryQueue.channel, payload.channel),
1204
- eq2(deliveryQueue.status, "gated")
870
+ const count = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
871
+ and(
872
+ eq(deliveryQueue.mind, baseName),
873
+ eq(deliveryQueue.channel, payload.channel),
874
+ eq(deliveryQueue.status, "gated")
1205
875
  )
1206
876
  );
1207
- if ((count2[0]?.count ?? 0) <= 1) {
877
+ if ((count[0]?.count ?? 0) <= 1) {
1208
878
  await this.sendInviteNotification(mindName, payload);
1209
879
  }
1210
880
  } catch (err) {
@@ -1294,8 +964,8 @@ var DeliveryManager = class {
1294
964
  const dir = mindDir(p.username);
1295
965
  const config = readVoluteConfig(dir);
1296
966
  if (!config?.profile?.avatar) continue;
1297
- filePath = resolve5(dir, "home", config.profile.avatar);
1298
- const homeDir = resolve5(dir, "home");
967
+ filePath = resolve4(dir, "home", config.profile.avatar);
968
+ const homeDir = resolve4(dir, "home");
1299
969
  if (!filePath.startsWith(`${homeDir}/`)) {
1300
970
  dlog2.warn(`avatar path for ${p.username} escapes home directory, skipping`);
1301
971
  continue;
@@ -1314,7 +984,7 @@ var DeliveryManager = class {
1314
984
  throw err;
1315
985
  }
1316
986
  } else {
1317
- filePath = resolve5(voluteHome(), "avatars", p.avatar);
987
+ filePath = resolve4(voluteHome(), "avatars", p.avatar);
1318
988
  }
1319
989
  const ext = extname(filePath).toLowerCase();
1320
990
  const mimeMap = {
@@ -1410,6 +1080,12 @@ async function recordInbound(mind, channel, sender, content) {
1410
1080
  content: content ?? void 0
1411
1081
  });
1412
1082
  }
1083
+ function resolveSleepAction(sleepBehavior, wokenByTrigger, wakeTriggerMatches) {
1084
+ if (sleepBehavior === "skip") return "skip";
1085
+ if (sleepBehavior === "trigger-wake" && !wokenByTrigger) return "queue-and-wake";
1086
+ if (!sleepBehavior && wakeTriggerMatches) return "queue-and-wake";
1087
+ return "queue";
1088
+ }
1413
1089
  async function deliverMessage(mindName, payload) {
1414
1090
  try {
1415
1091
  const baseName = await getBaseName(mindName);
@@ -1422,11 +1098,21 @@ async function deliverMessage(mindName, payload) {
1422
1098
  await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
1423
1099
  const sleepManager = getSleepManagerIfReady();
1424
1100
  if (sleepManager?.isSleeping(baseName)) {
1425
- if (sleepManager.checkWakeTrigger(baseName, payload)) {
1426
- await sleepManager.queueSleepMessage(baseName, payload);
1101
+ const sleepState = sleepManager.getState(baseName);
1102
+ const action = resolveSleepAction(
1103
+ payload.whileSleeping,
1104
+ sleepState.wokenByTrigger,
1105
+ sleepManager.checkWakeTrigger(baseName, payload)
1106
+ );
1107
+ if (action === "skip") {
1108
+ dlog3.info(
1109
+ `skipped delivery to ${baseName} (sleeping, whileSleeping=skip, channel=${payload.channel})`
1110
+ );
1111
+ return;
1112
+ }
1113
+ await sleepManager.queueSleepMessage(baseName, payload);
1114
+ if (action === "queue-and-wake") {
1427
1115
  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
1116
  }
1431
1117
  return;
1432
1118
  }
@@ -1472,7 +1158,7 @@ async function announceToSystem(text) {
1472
1158
  platformId: channelId,
1473
1159
  platform: "volute",
1474
1160
  name: SYSTEM_CHANNEL_NAME,
1475
- type: "group"
1161
+ type: "channel"
1476
1162
  });
1477
1163
  } catch (err) {
1478
1164
  logger_default.warn(`failed to write channel entry for ${mind.username}`, logger_default.errorData(err));
@@ -1491,74 +1177,6 @@ async function announceToSystem(text) {
1491
1177
  }
1492
1178
  }
1493
1179
 
1494
- // src/lib/systems-config.ts
1495
- import {
1496
- existsSync as existsSync4,
1497
- mkdirSync as mkdirSync3,
1498
- readFileSync as readFileSync4,
1499
- renameSync,
1500
- unlinkSync,
1501
- writeFileSync as writeFileSync3
1502
- } from "fs";
1503
- import { resolve as resolve6 } from "path";
1504
- var DEFAULT_API_URL = "https://volute.systems";
1505
- function configPath2() {
1506
- return resolve6(voluteSystemDir(), "systems.json");
1507
- }
1508
- function migrateIfNeeded() {
1509
- const target = configPath2();
1510
- if (existsSync4(target)) return;
1511
- const oldPaths = [
1512
- resolve6(voluteUserHome(), "systems.json"),
1513
- resolve6(voluteHome(), "systems.json")
1514
- ];
1515
- for (const old of oldPaths) {
1516
- if (old !== target && existsSync4(old)) {
1517
- try {
1518
- mkdirSync3(voluteSystemDir(), { recursive: true });
1519
- renameSync(old, target);
1520
- } catch {
1521
- }
1522
- return;
1523
- }
1524
- }
1525
- }
1526
- function readSystemsConfig() {
1527
- migrateIfNeeded();
1528
- const path = configPath2();
1529
- if (!existsSync4(path)) return null;
1530
- const raw = readFileSync4(path, "utf-8");
1531
- let data;
1532
- try {
1533
- data = JSON.parse(raw);
1534
- } catch {
1535
- console.error(
1536
- `Warning: ${path} contains invalid JSON. Run "volute systems logout" and re-login.`
1537
- );
1538
- return null;
1539
- }
1540
- if (!data.apiKey || !data.system) return null;
1541
- return {
1542
- apiKey: data.apiKey,
1543
- system: data.system,
1544
- apiUrl: data.apiUrl || DEFAULT_API_URL
1545
- };
1546
- }
1547
- function writeSystemsConfig(config) {
1548
- mkdirSync3(voluteSystemDir(), { recursive: true });
1549
- writeFileSync3(configPath2(), `${JSON.stringify(config, null, 2)}
1550
- `, { mode: 384 });
1551
- }
1552
- function deleteSystemsConfig() {
1553
- try {
1554
- unlinkSync(configPath2());
1555
- return true;
1556
- } catch (err) {
1557
- if (err.code === "ENOENT") return false;
1558
- throw err;
1559
- }
1560
- }
1561
-
1562
1180
  // src/lib/daemon/mail-poller.ts
1563
1181
  var mlog = logger_default.child("mail");
1564
1182
  function formatEmailContent(email) {
@@ -1793,7 +1411,7 @@ async function ensureMailAddress(mindName) {
1793
1411
  }
1794
1412
 
1795
1413
  // src/lib/daemon/scheduler.ts
1796
- import { resolve as resolve7 } from "path";
1414
+ import { resolve as resolve5 } from "path";
1797
1415
  import { CronExpressionParser } from "cron-parser";
1798
1416
  var slog = logger_default.child("scheduler");
1799
1417
  var Scheduler = class {
@@ -1802,7 +1420,7 @@ var Scheduler = class {
1802
1420
  lastFired = /* @__PURE__ */ new Map();
1803
1421
  // "mind:scheduleId" → epoch minute
1804
1422
  get statePath() {
1805
- return resolve7(voluteSystemDir(), "scheduler-state.json");
1423
+ return resolve5(voluteSystemDir(), "scheduler-state.json");
1806
1424
  }
1807
1425
  start() {
1808
1426
  this.loadState();
@@ -1853,6 +1471,15 @@ var Scheduler = class {
1853
1471
  shouldFire(schedule, epochMinute, mind, cronCache) {
1854
1472
  const key = `${mind}:${schedule.id}`;
1855
1473
  if (this.lastFired.get(key) === epochMinute) return false;
1474
+ if (schedule.fireAt) {
1475
+ const fireTime = Math.floor(new Date(schedule.fireAt).getTime() / 6e4);
1476
+ if (epochMinute >= fireTime) {
1477
+ this.lastFired.set(key, epochMinute);
1478
+ return true;
1479
+ }
1480
+ return false;
1481
+ }
1482
+ if (!schedule.cron) return false;
1856
1483
  let prevMinute = cronCache.get(schedule.cron);
1857
1484
  if (prevMinute === void 0) {
1858
1485
  try {
@@ -1872,22 +1499,10 @@ var Scheduler = class {
1872
1499
  return false;
1873
1500
  }
1874
1501
  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
1502
  try {
1888
1503
  let text;
1889
1504
  if (schedule.script) {
1890
- const homeDir = resolve7(mindDir(mindName), "home");
1505
+ const homeDir = resolve5(mindDir(mindName), "home");
1891
1506
  try {
1892
1507
  const output = await this.runScript(schedule.script, homeDir, mindName);
1893
1508
  if (!output.trim()) {
@@ -1907,16 +1522,46 @@ ${stderr}` : ""}`;
1907
1522
  slog.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
1908
1523
  return;
1909
1524
  }
1525
+ const whileSleeping = schedule.whileSleeping ?? (schedule.skipWhenSleeping ? "skip" : void 0);
1910
1526
  await this.deliver(mindName, {
1911
1527
  content: [{ type: "text", text }],
1912
1528
  channel: schedule.channel ?? "system:scheduler",
1913
- sender: schedule.id
1529
+ sender: schedule.id,
1530
+ whileSleeping
1914
1531
  });
1915
1532
  slog.info(`fired "${schedule.id}" for ${mindName}`);
1533
+ if (schedule.fireAt) {
1534
+ this.removeSchedule(mindName, schedule.id);
1535
+ }
1916
1536
  } catch (err) {
1917
1537
  slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
1918
1538
  }
1919
1539
  }
1540
+ removeSchedule(mindName, scheduleId) {
1541
+ const memSchedules = this.schedules.get(mindName);
1542
+ if (memSchedules) {
1543
+ const filtered = memSchedules.filter((s) => s.id !== scheduleId);
1544
+ if (filtered.length > 0) {
1545
+ this.schedules.set(mindName, filtered);
1546
+ } else {
1547
+ this.schedules.delete(mindName);
1548
+ }
1549
+ }
1550
+ try {
1551
+ const dir = mindDir(mindName);
1552
+ const config = readVoluteConfig(dir);
1553
+ if (!config?.schedules) return;
1554
+ config.schedules = config.schedules.filter((s) => s.id !== scheduleId);
1555
+ if (config.schedules.length === 0) config.schedules = void 0;
1556
+ writeVoluteConfig(dir, config);
1557
+ slog.info(`removed one-time schedule "${scheduleId}" for ${mindName}`);
1558
+ } catch (err) {
1559
+ slog.error(
1560
+ `failed to persist removal of schedule "${scheduleId}" for ${mindName} (removed from memory)`,
1561
+ logger_default.errorData(err)
1562
+ );
1563
+ }
1564
+ }
1920
1565
  runScript(script, cwd, mindName) {
1921
1566
  return exec("bash", ["-c", script], { cwd, mindName });
1922
1567
  }
@@ -1936,8 +1581,8 @@ function getScheduler() {
1936
1581
  }
1937
1582
 
1938
1583
  // src/lib/daemon/token-budget.ts
1939
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1940
- import { resolve as resolve8 } from "path";
1584
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1585
+ import { resolve as resolve6 } from "path";
1941
1586
  var tlog = logger_default.child("token-budget");
1942
1587
  var DEFAULT_BUDGET_PERIOD_MINUTES = 60;
1943
1588
  var MAX_QUEUE_SIZE = 100;
@@ -2055,19 +1700,19 @@ var TokenBudget = class {
2055
1700
  this.dirty.clear();
2056
1701
  }
2057
1702
  budgetStatePath(mind) {
2058
- return resolve8(stateDir(mind), "budget.json");
1703
+ return resolve6(stateDir(mind), "budget.json");
2059
1704
  }
2060
1705
  saveBudgetState(mind, state) {
2061
1706
  try {
2062
1707
  const dir = stateDir(mind);
2063
- mkdirSync4(dir, { recursive: true });
1708
+ mkdirSync3(dir, { recursive: true });
2064
1709
  const data = {
2065
1710
  periodStart: state.periodStart,
2066
1711
  tokensUsed: state.tokensUsed,
2067
1712
  warningInjected: state.warningInjected,
2068
1713
  queue: state.queue
2069
1714
  };
2070
- writeFileSync4(this.budgetStatePath(mind), `${JSON.stringify(data)}
1715
+ writeFileSync3(this.budgetStatePath(mind), `${JSON.stringify(data)}
2071
1716
  `);
2072
1717
  } catch (err) {
2073
1718
  tlog.warn(`failed to save budget state for ${mind}`, logger_default.errorData(err));
@@ -2076,8 +1721,8 @@ var TokenBudget = class {
2076
1721
  loadBudgetState(mind) {
2077
1722
  try {
2078
1723
  const path = this.budgetStatePath(mind);
2079
- if (!existsSync5(path)) return null;
2080
- const data = JSON.parse(readFileSync5(path, "utf-8"));
1724
+ if (!existsSync3(path)) return null;
1725
+ const data = JSON.parse(readFileSync4(path, "utf-8"));
2081
1726
  if (typeof data.periodStart !== "number" || typeof data.tokensUsed !== "number") return null;
2082
1727
  return {
2083
1728
  periodStart: data.periodStart,
@@ -2146,6 +1791,7 @@ async function startMindFull(name) {
2146
1791
  if (!entry || entry.stage === "seed") return;
2147
1792
  const dir = mindDir(baseName);
2148
1793
  getScheduler().loadSchedules(baseName);
1794
+ getSleepManagerIfReady()?.loadSleepConfig(baseName);
2149
1795
  ensureMailAddress(baseName).catch(
2150
1796
  (err) => logger_default.error(`failed to ensure mail address for ${baseName}`, logger_default.errorData(err))
2151
1797
  );
@@ -2165,7 +1811,7 @@ async function startMindFull(name) {
2165
1811
  config.tokenBudgetPeriodMinutes ?? DEFAULT_BUDGET_PERIOD_MINUTES
2166
1812
  );
2167
1813
  }
2168
- startWatcher(baseName);
1814
+ notifyExtensionsMindStart(baseName);
2169
1815
  }
2170
1816
  async function sleepMind(name) {
2171
1817
  markIdle(name);
@@ -2188,7 +1834,7 @@ async function stopMindFull(name) {
2188
1834
  const baseName = await getBaseName(name);
2189
1835
  const isBase = baseName === name;
2190
1836
  if (isBase) {
2191
- stopWatcher(baseName);
1837
+ notifyExtensionsMindStop(baseName);
2192
1838
  markIdle(baseName);
2193
1839
  getScheduler().unloadSchedules(baseName);
2194
1840
  getTokenBudget().removeBudget(baseName);
@@ -2238,8 +1884,9 @@ var SleepManager = class {
2238
1884
  interval = null;
2239
1885
  unsubActivity = null;
2240
1886
  transitioning = /* @__PURE__ */ new Set();
1887
+ sleepConfigs = /* @__PURE__ */ new Map();
2241
1888
  get statePath() {
2242
- return resolve9(voluteSystemDir(), "sleep-state.json");
1889
+ return resolve7(voluteSystemDir(), "sleep-state.json");
2243
1890
  }
2244
1891
  start() {
2245
1892
  this.loadState();
@@ -2255,8 +1902,8 @@ var SleepManager = class {
2255
1902
  // --- State persistence ---
2256
1903
  loadState() {
2257
1904
  try {
2258
- if (existsSync6(this.statePath)) {
2259
- const data = JSON.parse(readFileSync6(this.statePath, "utf-8"));
1905
+ if (existsSync4(this.statePath)) {
1906
+ const data = JSON.parse(readFileSync5(this.statePath, "utf-8"));
2260
1907
  for (const [name, state] of Object.entries(data)) {
2261
1908
  state.triggerWakeHistory ??= [];
2262
1909
  this.states.set(name, state);
@@ -2272,7 +1919,7 @@ var SleepManager = class {
2272
1919
  if (state.sleeping) data[name] = state;
2273
1920
  }
2274
1921
  try {
2275
- writeFileSync5(this.statePath, `${JSON.stringify(data, null, 2)}
1922
+ writeFileSync4(this.statePath, `${JSON.stringify(data, null, 2)}
2276
1923
  `);
2277
1924
  } catch (err) {
2278
1925
  slog2.error("failed to save sleep state", logger_default.errorData(err));
@@ -2299,9 +1946,21 @@ var SleepManager = class {
2299
1946
  slog2.info(`${name} trigger-wake converted to full wake`);
2300
1947
  }
2301
1948
  getSleepConfig(name) {
1949
+ if (this.sleepConfigs.has(name)) {
1950
+ return this.sleepConfigs.get(name) ?? null;
1951
+ }
1952
+ const config = this.loadSleepConfig(name);
1953
+ return config;
1954
+ }
1955
+ loadSleepConfig(name) {
2302
1956
  const dir = mindDir(name);
2303
1957
  const config = readVoluteConfig(dir);
2304
- return config?.sleep ?? null;
1958
+ const sleepConfig = config?.sleep ?? null;
1959
+ this.sleepConfigs.set(name, sleepConfig);
1960
+ return sleepConfig;
1961
+ }
1962
+ invalidateSleepConfig(name) {
1963
+ this.sleepConfigs.delete(name);
2305
1964
  }
2306
1965
  /**
2307
1966
  * Put a mind to sleep. Sends pre-sleep message, waits for completion,
@@ -2321,8 +1980,7 @@ var SleepManager = class {
2321
1980
  if (!entry) return;
2322
1981
  const sleepConfig = this.getSleepConfig(name);
2323
1982
  const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
2324
- const queuedInfo = "";
2325
- const preSleepMsg = await getPrompt("pre_sleep", { wakeTime, queuedInfo });
1983
+ const preSleepMsg = await getPrompt("pre_sleep", { wakeTime });
2326
1984
  try {
2327
1985
  const db = await getDb();
2328
1986
  await db.insert(mindHistory).values({
@@ -2490,9 +2148,9 @@ var SleepManager = class {
2490
2148
  async flushQueuedMessages(name) {
2491
2149
  try {
2492
2150
  const db = await getDb();
2493
- const rows = await db.select().from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
2151
+ const rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
2494
2152
  if (rows.length === 0) return 0;
2495
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-HV3S6HZV.js");
2153
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-6YMVNOEC.js");
2496
2154
  const delivered = [];
2497
2155
  for (const row of rows) {
2498
2156
  try {
@@ -2503,7 +2161,7 @@ var SleepManager = class {
2503
2161
  }
2504
2162
  }
2505
2163
  if (delivered.length > 0) {
2506
- await db.delete(deliveryQueue).where(inArray2(deliveryQueue.id, delivered));
2164
+ await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, delivered));
2507
2165
  }
2508
2166
  const state = this.states.get(name);
2509
2167
  if (state) {
@@ -2592,17 +2250,17 @@ var SleepManager = class {
2592
2250
  }
2593
2251
  }
2594
2252
  async waitForIdle(name, timeoutMs) {
2595
- return new Promise((resolve10) => {
2253
+ return new Promise((resolve8) => {
2596
2254
  const timeout = setTimeout(() => {
2597
2255
  unsub();
2598
- resolve10();
2256
+ resolve8();
2599
2257
  }, timeoutMs);
2600
2258
  const unsub = subscribe((event) => {
2601
2259
  if (event.mind !== name) return;
2602
2260
  if (event.type === "mind_done" || event.type === "mind_idle") {
2603
2261
  clearTimeout(timeout);
2604
2262
  unsub();
2605
- resolve10();
2263
+ resolve8();
2606
2264
  }
2607
2265
  });
2608
2266
  });
@@ -2610,32 +2268,32 @@ var SleepManager = class {
2610
2268
  async archiveSessions(name) {
2611
2269
  const dir = mindDir(name);
2612
2270
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
2613
- const sessionsDir = resolve9(dir, ".mind", "sessions");
2614
- if (existsSync6(sessionsDir)) {
2615
- const archiveDir = resolve9(sessionsDir, "archive");
2616
- mkdirSync5(archiveDir, { recursive: true });
2617
- for (const file of readdirSync2(sessionsDir)) {
2271
+ const sessionsDir = resolve7(dir, ".mind", "sessions");
2272
+ if (existsSync4(sessionsDir)) {
2273
+ const archiveDir = resolve7(sessionsDir, "archive");
2274
+ mkdirSync4(archiveDir, { recursive: true });
2275
+ for (const file of readdirSync(sessionsDir)) {
2618
2276
  if (file === "archive" || !file.endsWith(".json")) continue;
2619
- const src = resolve9(sessionsDir, file);
2277
+ const src = resolve7(sessionsDir, file);
2620
2278
  const base = file.replace(/\.json$/, "");
2621
- const dest = resolve9(archiveDir, `${base}-${timestamp}.json`);
2279
+ const dest = resolve7(archiveDir, `${base}-${timestamp}.json`);
2622
2280
  try {
2623
- renameSync2(src, dest);
2281
+ renameSync(src, dest);
2624
2282
  } catch (err) {
2625
2283
  slog2.warn(`failed to archive session ${file} for ${name}`, logger_default.errorData(err));
2626
2284
  }
2627
2285
  }
2628
2286
  }
2629
- const piSessionsDir = resolve9(dir, ".mind", "pi-sessions");
2630
- if (existsSync6(piSessionsDir)) {
2631
- const archiveDir = resolve9(piSessionsDir, "archive");
2632
- mkdirSync5(archiveDir, { recursive: true });
2633
- for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
2287
+ const piSessionsDir = resolve7(dir, ".mind", "pi-sessions");
2288
+ if (existsSync4(piSessionsDir)) {
2289
+ const archiveDir = resolve7(piSessionsDir, "archive");
2290
+ mkdirSync4(archiveDir, { recursive: true });
2291
+ for (const entry of readdirSync(piSessionsDir, { withFileTypes: true })) {
2634
2292
  if (entry.name === "archive" || !entry.isDirectory()) continue;
2635
- const src = resolve9(piSessionsDir, entry.name);
2636
- const dest = resolve9(archiveDir, `${entry.name}-${timestamp}`);
2293
+ const src = resolve7(piSessionsDir, entry.name);
2294
+ const dest = resolve7(archiveDir, `${entry.name}-${timestamp}`);
2637
2295
  try {
2638
- renameSync2(src, dest);
2296
+ renameSync(src, dest);
2639
2297
  } catch (err) {
2640
2298
  slog2.warn(`failed to archive pi-session ${entry.name} for ${name}`, logger_default.errorData(err));
2641
2299
  }
@@ -2643,8 +2301,8 @@ var SleepManager = class {
2643
2301
  }
2644
2302
  }
2645
2303
  async runWakeContextScript(name, sleepingSince, duration) {
2646
- const scriptPath = resolve9(mindDir(name), "home", ".config", "hooks", "wake-context.sh");
2647
- if (!existsSync6(scriptPath)) return "";
2304
+ const scriptPath = resolve7(mindDir(name), "home", ".config", "hooks", "wake-context.sh");
2305
+ if (!existsSync4(scriptPath)) return "";
2648
2306
  const input = JSON.stringify({
2649
2307
  sleepingSince,
2650
2308
  duration,
@@ -2694,7 +2352,7 @@ var SleepManager = class {
2694
2352
  async buildQueuedSummary(name) {
2695
2353
  try {
2696
2354
  const db = await getDb();
2697
- const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and3(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
2355
+ const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
2698
2356
  if (rows.length === 0) return "No messages arrived while you slept.";
2699
2357
  const channelCounts = /* @__PURE__ */ new Map();
2700
2358
  const senders = /* @__PURE__ */ new Set();
@@ -2703,7 +2361,7 @@ var SleepManager = class {
2703
2361
  channelCounts.set(ch, (channelCounts.get(ch) ?? 0) + 1);
2704
2362
  if (row.sender) senders.add(row.sender);
2705
2363
  }
2706
- const parts = [...channelCounts.entries()].map(([ch, count2]) => `${count2} on ${ch}`);
2364
+ const parts = [...channelCounts.entries()].map(([ch, count]) => `${count} on ${ch}`);
2707
2365
  const senderNote = senders.size > 0 ? ` from ${[...senders].join(", ")}` : "";
2708
2366
  return `${rows.length} message${rows.length === 1 ? "" : "s"} arrived while you slept${senderNote} (${parts.join(", ")}). They'll be delivered to your normal channels now.`;
2709
2367
  } catch (err) {
@@ -2741,16 +2399,16 @@ var SleepManager = class {
2741
2399
  } catch {
2742
2400
  try {
2743
2401
  const portHex = port.toString(16).toUpperCase().padStart(4, "0");
2744
- const tcp6 = readFileSync6("/proc/net/tcp6", "utf-8");
2402
+ const tcp6 = readFileSync5("/proc/net/tcp6", "utf-8");
2745
2403
  for (const line of tcp6.split("\n")) {
2746
2404
  if (!line.includes(`:${portHex} `)) continue;
2747
2405
  const fields = line.trim().split(/\s+/);
2748
2406
  if (fields[3] !== "0A") continue;
2749
2407
  const inode = parseInt(fields[9], 10);
2750
2408
  if (!inode) continue;
2751
- for (const pidDir of readdirSync2("/proc").filter((f) => /^\d+$/.test(f))) {
2409
+ for (const pidDir of readdirSync("/proc").filter((f) => /^\d+$/.test(f))) {
2752
2410
  try {
2753
- const fds = readdirSync2(`/proc/${pidDir}/fd`);
2411
+ const fds = readdirSync(`/proc/${pidDir}/fd`);
2754
2412
  for (const fd of fds) {
2755
2413
  try {
2756
2414
  const link = readlinkSync(`/proc/${pidDir}/fd/${fd}`);
@@ -2808,27 +2466,8 @@ function getSleepManagerIfReady() {
2808
2466
  }
2809
2467
 
2810
2468
  export {
2811
- createUser,
2812
- verifyUser,
2813
- getUser,
2814
- getUserByUsername,
2815
- listUsers,
2816
- listPendingUsers,
2817
- listUsersByType,
2818
- getOrCreateMindUser,
2819
- deleteMindUser,
2820
- changePassword,
2821
- approveUser,
2822
- countAdmins,
2823
- setUserRole,
2824
- deleteUser,
2825
- updateUserProfile,
2826
- migrateMindRoles,
2827
2469
  readVoluteConfig,
2828
2470
  writeVoluteConfig,
2829
- stopAllWatchers,
2830
- getCachedSites,
2831
- getCachedRecentPages,
2832
2471
  splitMessage,
2833
2472
  writeChannelEntry,
2834
2473
  resolveChannelId,
@@ -2854,9 +2493,7 @@ export {
2854
2493
  initDeliveryManager,
2855
2494
  getDeliveryManager,
2856
2495
  recordInbound,
2496
+ resolveSleepAction,
2857
2497
  deliverMessage,
2858
- readSystemsConfig,
2859
- writeSystemsConfig,
2860
- deleteSystemsConfig,
2861
2498
  initMailPoller
2862
2499
  };