volute 0.49.0 → 0.51.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 (224) hide show
  1. package/README.md +2 -2
  2. package/dist/{accept-KYNC5X3U.js → accept-5NK5XJB5.js} +2 -1
  3. package/dist/{activity-events-GJJAI2WT.js → activity-events-LQII6PZS.js} +3 -3
  4. package/dist/{ai-service-A7FS7EC3.js → ai-service-YW5SWCSB.js} +9 -4
  5. package/dist/{archive-7E3YE6VF.js → archive-FOHGXHPT.js} +3 -3
  6. package/dist/{auth-ABYLOEZ7.js → auth-X6EMNEGB.js} +10 -6
  7. package/dist/{avatar-image-6ATV2IOZ.js → avatar-image-52WGXLHV.js} +3 -3
  8. package/dist/{backup-XYAMT4CU.js → backup-YVGV2ZNM.js} +11 -12
  9. package/dist/{bridge-3FNSWLNQ.js → bridge-YK7NTRU5.js} +2 -1
  10. package/dist/{channels-SGQZHNQJ.js → channels-FSAHOQWI.js} +2 -1
  11. package/dist/{chat-OGXAEXMV.js → chat-VAZ25KVT.js} +9 -9
  12. package/dist/{chunk-P4LHVRNE.js → chunk-2RNMBXD2.js} +43 -19
  13. package/dist/{chunk-6HGEZJDH.js → chunk-5N32EN4O.js} +2 -2
  14. package/dist/{chunk-JCWB2ITW.js → chunk-5Y54AE3L.js} +35 -5
  15. package/dist/{chunk-HCYQRME2.js → chunk-6NLW72HP.js} +2 -2
  16. package/dist/chunk-7HN63Q7J.js +1317 -0
  17. package/dist/chunk-7YJY3F2R.js +38 -0
  18. package/dist/{chunk-EADX54RG.js → chunk-AI47FDGL.js} +6 -7
  19. package/dist/{chunk-IH5TKXX6.js → chunk-BSHZ23MS.js} +12 -11
  20. package/dist/{chunk-K3JOLGWF.js → chunk-CDKQJWGK.js} +76 -63
  21. package/dist/{chunk-L27YYONJ.js → chunk-CV6BNGFK.js} +3 -3
  22. package/dist/{chunk-NOIAHMJB.js → chunk-DMK5ASKV.js} +1 -1
  23. package/dist/chunk-DN3LKLYR.js +109 -0
  24. package/dist/{chunk-JBIFXB6I.js → chunk-EDR47UE2.js} +3 -5
  25. package/dist/{chunk-SHKM5RGD.js → chunk-EEOMA4DX.js} +1 -1
  26. package/dist/{chunk-YIJGKHIS.js → chunk-EGV6R6UF.js} +43 -20
  27. package/dist/{chunk-KHCKZ36O.js → chunk-FLIEXG2T.js} +1 -1
  28. package/dist/chunk-FNJCBRD3.js +31 -0
  29. package/dist/{chunk-JPGHSXSV.js → chunk-FXWBFDQR.js} +23 -9
  30. package/dist/{chunk-JCBPU57A.js → chunk-G7A5LEVI.js} +6 -6
  31. package/dist/{chunk-PY24ZL62.js → chunk-GLM7TVWP.js} +1 -1
  32. package/dist/{chunk-RSBL3FAD.js → chunk-H6X6CKDN.js} +235 -24
  33. package/dist/{chunk-4DSGN4FL.js → chunk-HKP6OAJP.js} +43 -13
  34. package/dist/{chunk-C3KLHJDB.js → chunk-HTRSSOHY.js} +1 -1
  35. package/dist/{chunk-EG5LSKHU.js → chunk-I7U5EEZC.js} +2 -2
  36. package/dist/chunk-ICL6T4SN.js +562 -0
  37. package/dist/{chunk-FBBS4BVR.js → chunk-ICXY54ZC.js} +1 -1
  38. package/dist/{chunk-DAIIMBLB.js → chunk-IWAVIJO2.js} +3 -5
  39. package/dist/{chunk-TXJL36ZQ.js → chunk-JHKBALS2.js} +2 -2
  40. package/dist/{chunk-MG3Z34YE.js → chunk-L7BKTZ5U.js} +1 -1
  41. package/dist/{chunk-SX72DOO3.js → chunk-LAUCO2HC.js} +1 -1
  42. package/dist/chunk-LNNI3MS4.js +54 -0
  43. package/dist/{chunk-BCG45AQB.js → chunk-M74I3HHF.js} +15 -5
  44. package/dist/{chunk-DVN6DNMI.js → chunk-M77B73VV.js} +17 -2
  45. package/dist/{chunk-4B62E4CG.js → chunk-OXWUEU5V.js} +2 -2
  46. package/dist/chunk-Q7XQLKZB.js +289 -0
  47. package/dist/{chunk-GWVFDUNB.js → chunk-QT4D5XCI.js} +8 -10
  48. package/dist/{chunk-VNRSXEBF.js → chunk-TPX6E6PL.js} +38 -5
  49. package/dist/{chunk-3B427GUK.js → chunk-U54PJPTB.js} +2 -2
  50. package/dist/{chunk-EFT2OALG.js → chunk-UDZLPR34.js} +20 -5
  51. package/dist/{chunk-ZX46PGCC.js → chunk-UFTDRTCP.js} +1 -1
  52. package/dist/chunk-VAEZCIJC.js +1058 -0
  53. package/dist/chunk-VP5PFNZO.js +2905 -0
  54. package/dist/{chunk-VIUH5CHQ.js → chunk-VSBMLB2W.js} +51 -34
  55. package/dist/{chunk-RRZ4RQQU.js → chunk-X67BDDZR.js} +5 -7
  56. package/dist/cli.js +34 -27
  57. package/dist/{clock-6JZL2FS6.js → clock-ZT5YT6HA.js} +4 -3
  58. package/dist/{cloud-sync-VFDTNNCA.js → cloud-sync-GPXYPTIC.js} +23 -20
  59. package/dist/{config-RPSLA2HX.js → config-UGJS57VT.js} +3 -3
  60. package/dist/{conversations-TUM5PJJ4.js → conversations-ZL6PHROD.js} +8 -4
  61. package/dist/{create-Z5FSDNVZ.js → create-G2RIKQYK.js} +2 -2
  62. package/dist/{create-VIQVP3QB.js → create-M727VUDF.js} +2 -1
  63. package/dist/{daemon-client-ZFY5MOAL.js → daemon-client-VVYFZI2D.js} +2 -1
  64. package/dist/{daemon-restart-54L4B6AQ.js → daemon-restart-2EWBRTN5.js} +6 -7
  65. package/dist/daemon.js +2005 -762
  66. package/dist/{db-F6UMFRYD.js → db-BONH4SUG.js} +2 -2
  67. package/dist/{delete-ESEP7NGQ.js → delete-O7PEFQDF.js} +1 -1
  68. package/dist/delivery-manager-HGVZSEES.js +35 -0
  69. package/dist/{delivery-router-VUOL5YSA.js → delivery-router-RHANNTAC.js} +7 -5
  70. package/dist/{down-6X7KX7BZ.js → down-QSNEMTTX.js} +5 -5
  71. package/dist/echo-text-6ANI5HHN.js +39 -0
  72. package/dist/{env-ZYMSCB7B.js → env-IBW2QI6K.js} +3 -2
  73. package/dist/{env-KUL4XKN2.js → env-SQFCWW52.js} +3 -3
  74. package/dist/{exec-FBXA3CM4.js → exec-ABD3DYA2.js} +3 -3
  75. package/dist/{export-TNBPDH77.js → export-VBNWM7I4.js} +4 -4
  76. package/dist/{extension-J4ZQ2GHV.js → extension-MRYJWSMV.js} +2 -1
  77. package/dist/{extensions-AWNO3SZF.js → extensions-NY6WST33.js} +23 -20
  78. package/dist/{files-BVBRJJ2P.js → files-BRY5RRIM.js} +5 -4
  79. package/dist/{import-ZUNKMCKC.js → import-DB2KULRO.js} +8 -8
  80. package/dist/{isolation-OPCZQRTH.js → isolation-GVV2IBAU.js} +5 -3
  81. package/dist/{join-34TR2GXQ.js → join-PYA36HEC.js} +1 -1
  82. package/dist/{list-PMUXZNAM.js → list-UQGIW7OF.js} +8 -3
  83. package/dist/{login-LKNLCJPA.js → login-RJJTCVR3.js} +2 -1
  84. package/dist/{login-KAKNFCW5.js → login-SB5OENMV.js} +4 -3
  85. package/dist/{logout-YZRMZ3O2.js → logout-BYV433X6.js} +2 -1
  86. package/dist/{logout-RLT62RQP.js → logout-YFZAJRPY.js} +4 -3
  87. package/dist/message-delivery-JHMAJ2GU.js +43 -0
  88. package/dist/migrate-thread-config-EQSEP2XG.js +90 -0
  89. package/dist/{mind-IF6CGMFW.js → mind-WF6QIXWV.js} +19 -19
  90. package/dist/{mind-activity-tracker-YMMERB6B.js → mind-activity-tracker-QF6DCX2X.js} +4 -4
  91. package/dist/{mind-contacts-BKUFPUCU.js → mind-contacts-QYU4TPEA.js} +3 -2
  92. package/dist/{mind-history-WI5OI3BW.js → mind-history-OGNR4L7W.js} +30 -7
  93. package/dist/{mind-list-OTHV4ZO4.js → mind-list-SYRA7CH5.js} +3 -2
  94. package/dist/{mind-profile-LUG353ZT.js → mind-profile-MRCVZPYT.js} +1 -1
  95. package/dist/mind-service-DZCUUC7J.js +47 -0
  96. package/dist/{mind-sleep-VBV7YRSU.js → mind-sleep-LZAFBDZD.js} +4 -3
  97. package/dist/{mind-status-BJNPGXKC.js → mind-status-Z2LQFA6F.js} +3 -2
  98. package/dist/{mind-wake-LVTZ55EZ.js → mind-wake-LDGPRA42.js} +2 -1
  99. package/dist/{package-PD67VPHB.js → package-B6O5APCZ.js} +23 -23
  100. package/dist/{prompts-MDHDLWEX.js → prompts-NBO3HRK3.js} +3 -3
  101. package/dist/{read-YSWUXYL7.js → read-RJ5NAW5J.js} +5 -14
  102. package/dist/{register-TWQDKQRT.js → register-OX4HRSGC.js} +2 -1
  103. package/dist/{registry-3ELJPUGJ.js → registry-TRC2HJXR.js} +6 -4
  104. package/dist/{reject-SXGCYN4A.js → reject-TWB4QJHG.js} +2 -1
  105. package/dist/{restart-5F5QDBOG.js → restart-AGEGNSYQ.js} +2 -1
  106. package/dist/{sandbox-XPYA7UIF.js → sandbox-NNWCTHRI.js} +7 -4
  107. package/dist/scheduler-TNBN54ZK.js +25 -0
  108. package/dist/{schema-6YONVTKZ.js → schema-ZZQVM4GR.js} +5 -3
  109. package/dist/{seed-NQED7PSX.js → seed-K2LYODIH.js} +1 -1
  110. package/dist/{seed-check-TYD7RRZY.js → seed-check-NRJEQZST.js} +14 -5
  111. package/dist/{seed-cmd-QF3F2G4S.js → seed-cmd-2EWBTR6P.js} +3 -3
  112. package/dist/{seed-create-XOO4N2OE.js → seed-create-VGTLU5FD.js} +4 -4
  113. package/dist/{seed-readiness-3LRYASA4.js → seed-readiness-DYOVCYHN.js} +4 -5
  114. package/dist/{seed-sprout-HSBBGSS2.js → seed-sprout-PILHJ3YS.js} +9 -9
  115. package/dist/{send-IHRJYNXA.js → send-2YKOZJJ5.js} +108 -54
  116. package/dist/{service-YCOPCTSZ.js → service-XQPMZAGI.js} +4 -4
  117. package/dist/{service-install-Y46OTF55.js → service-install-2EWD7KXD.js} +5 -5
  118. package/dist/{setup-F4TE64YI.js → setup-MDPSK2XG.js} +6 -7
  119. package/dist/{setup-TCN7VH5U.js → setup-WZSXHHRJ.js} +10 -3
  120. package/dist/{skill-4CYJLYVY.js → skill-B5OJEQUA.js} +3 -2
  121. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  122. package/dist/skills/imagegen/SKILL.md +21 -4
  123. package/dist/skills/imagegen/scripts/imagegen.ts +124 -21
  124. package/dist/skills/tending/SKILL.md +7 -7
  125. package/dist/skills/volute-mind/SKILL.md +3 -3
  126. package/dist/skills/volute-mind/references/integrations.md +2 -2
  127. package/dist/skills/volute-mind/references/routing.md +15 -15
  128. package/dist/{skills-NN5UZCUU.js → skills-NH2GQKAF.js} +4 -5
  129. package/dist/sleep-manager-7JXEQQ2P.js +41 -0
  130. package/dist/{spirit-SSJMYC6A.js → spirit-25JVAL3M.js} +11 -8
  131. package/dist/spirit-availability-PJDWZJ3J.js +42 -0
  132. package/dist/{split-JF2VWTXA.js → split-BU5VWX6P.js} +1 -1
  133. package/dist/{sprout-AXA2SECQ.js → sprout-EXUZMIOR.js} +1 -1
  134. package/dist/{src-YH6PQ7LJ.js → src-HGNE2XG4.js} +7 -1
  135. package/dist/{src-ZN4LXMPI.js → src-LCSOQY6Z.js} +57 -51
  136. package/dist/{src-JAOVXO5W.js → src-UDMJZ6MH.js} +8 -8
  137. package/dist/{start-BTVMTP3K.js → start-MUMNFR52.js} +2 -1
  138. package/dist/{status-VJGXMQ4L.js → status-MWLE3RVC.js} +7 -6
  139. package/dist/{stop-YORESK4U.js → stop-EKERNYTX.js} +2 -1
  140. package/dist/system-events-ZPSH63QX.js +46 -0
  141. package/dist/system-install-YZSWCPDK.js +8 -0
  142. package/dist/{systems-5MH2ELQE.js → systems-VDRUZXNF.js} +5 -4
  143. package/dist/{tailscale-AMMVKDXB.js → tailscale-KHNZFC7L.js} +2 -2
  144. package/dist/{template-hash-GSOUARW4.js → template-hash-YKWMVYMM.js} +2 -2
  145. package/dist/{template-staleness-HKGCTS3H.js → template-staleness-PY2CULH6.js} +5 -5
  146. package/dist/{turn-tracker-JQ2YUUY2.js → turn-tracker-TAT2HGK6.js} +6 -6
  147. package/dist/{up-SDLTLPAO.js → up-J7XCAH7P.js} +5 -6
  148. package/dist/{update-WEA4N2TR.js → update-CIWI2JYA.js} +5 -5
  149. package/dist/{update-check-DZOTONII.js → update-check-SZ7B5IFI.js} +3 -3
  150. package/dist/{upgrade-K5FIQPQD.js → upgrade-LIE46UBB.js} +1 -1
  151. package/dist/variant-cleanup-YXN25ZVW.js +22 -0
  152. package/dist/{version-notify-WFA5XB5A.js → version-notify-EN3KJ7HH.js} +11 -28
  153. package/dist/web-assets/assets/index-BOyJ4Gqs.css +1 -0
  154. package/dist/web-assets/assets/index-CrxBwnzp.js +95 -0
  155. package/dist/web-assets/index.html +2 -2
  156. package/dist/xai-P42CRCZA.js +129 -0
  157. package/drizzle/0014_system_events.sql +26 -0
  158. package/drizzle/0015_rename_session_to_thread.sql +12 -0
  159. package/drizzle/0016_brain_to_human.sql +4 -0
  160. package/drizzle/0017_event_history_rows.sql +9 -0
  161. package/drizzle/0018_api_tokens.sql +20 -0
  162. package/drizzle/meta/_journal.json +35 -0
  163. package/package.json +23 -23
  164. package/packages/extensions/notes/dist/ui/assets/index-BBs8pyel.js +67 -0
  165. package/packages/extensions/notes/dist/ui/index.html +1 -1
  166. package/packages/extensions/pages/dist/ui/assets/index-B589e8Xe.js +2 -0
  167. package/packages/extensions/pages/dist/ui/index.html +1 -1
  168. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +67 -0
  169. package/packages/extensions/plan/dist/ui/index.html +1 -1
  170. package/templates/_base/.init/.config/prompts.json +1 -0
  171. package/templates/_base/.init/.local/hooks/startup-context.ts +24 -9
  172. package/templates/_base/{biome.json.tmpl → biome.json} +2 -1
  173. package/templates/_base/gitignore +44 -0
  174. package/templates/_base/home/.config/routes.json +2 -2
  175. package/templates/_base/home/VOLUTE.md +18 -5
  176. package/templates/_base/home/public/.gitkeep +0 -0
  177. package/templates/_base/src/lib/daemon-client.ts +1 -1
  178. package/templates/_base/src/lib/event-turn.ts +39 -0
  179. package/templates/_base/src/lib/format-prefix.ts +36 -10
  180. package/templates/_base/src/lib/router.ts +19 -10
  181. package/templates/_base/src/lib/routing.ts +8 -8
  182. package/templates/_base/src/lib/startup.ts +9 -1
  183. package/templates/_base/src/lib/types.ts +7 -1
  184. package/templates/_base/src/lib/volute-server.ts +30 -1
  185. package/templates/claude/.init/.config/routes.json +3 -3
  186. package/templates/claude/.init/CLAUDE.md +1 -1
  187. package/templates/claude/{package.json.tmpl → package.json} +4 -4
  188. package/templates/claude/src/agent.ts +4 -1
  189. package/templates/claude/src/lib/hooks/reply-instructions.ts +26 -1
  190. package/templates/claude/volute-template.json +1 -3
  191. package/templates/codex/.init/.config/routes.json +3 -3
  192. package/templates/codex/.init/AGENTS.md +1 -1
  193. package/templates/codex/{package.json.tmpl → package.json} +4 -4
  194. package/templates/codex/src/agent.ts +11 -11
  195. package/templates/codex/src/lib/turn-context.ts +51 -0
  196. package/templates/codex/volute-template.json +1 -3
  197. package/templates/pi/.init/.config/routes.json +3 -3
  198. package/templates/pi/.init/MINDS.md +1 -1
  199. package/templates/pi/{package.json.tmpl → package.json} +6 -6
  200. package/templates/pi/src/agent.ts +9 -1
  201. package/templates/pi/src/lib/event-handler.ts +1 -1
  202. package/templates/pi/src/lib/reply-instructions-extension.ts +28 -1
  203. package/templates/pi/volute-template.json +1 -3
  204. package/dist/chunk-5T5YMX6S.js +0 -23
  205. package/dist/chunk-NJHN6IRB.js +0 -177
  206. package/dist/chunk-QQBOI3QA.js +0 -5345
  207. package/dist/chunk-XTEKINWG.js +0 -158
  208. package/dist/delivery-manager-RBXBPVWM.js +0 -36
  209. package/dist/echo-text-B6NZ5FGE.js +0 -36
  210. package/dist/message-delivery-ABPV76EA.js +0 -42
  211. package/dist/mind-service-42GDO4KH.js +0 -44
  212. package/dist/notices-N6BCGHFV.js +0 -25
  213. package/dist/scheduler-K7Y7Z7FQ.js +0 -34
  214. package/dist/sleep-manager-DSPXFHGX.js +0 -38
  215. package/dist/system-chat-M2XOBKEM.js +0 -40
  216. package/dist/variant-cleanup-P4MBNLJD.js +0 -33
  217. package/dist/web-assets/assets/index-BostMFoR.css +0 -1
  218. package/dist/web-assets/assets/index-CKlqUkyG.js +0 -92
  219. package/packages/extensions/notes/dist/ui/assets/index-D0-DgYyj.js +0 -67
  220. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +0 -2
  221. package/packages/extensions/plan/dist/ui/assets/index-Cfzs-0G9.js +0 -67
  222. /package/templates/_base/home/.config/{config.json.tmpl → config.json} +0 -0
  223. /package/templates/codex/home/.config/{config.json.tmpl → config.json} +0 -0
  224. /package/templates/pi/home/.config/{config.json.tmpl → config.json} +0 -0
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ detectSystemInstallHint
4
+ } from "./chunk-FNJCBRD3.js";
2
5
 
3
6
  // packages/cli/src/lib/daemon-client.ts
4
7
  import { existsSync, readFileSync } from "fs";
@@ -40,12 +43,19 @@ function readCliSession() {
40
43
  }
41
44
  }
42
45
  function readDaemonConfig() {
46
+ const mindPort = process.env.VOLUTE_MIND_TOKEN && process.env.VOLUTE_DAEMON_PORT;
47
+ if (mindPort) {
48
+ return {
49
+ port: Number(mindPort),
50
+ hostname: process.env.VOLUTE_DAEMON_HOSTNAME || "127.0.0.1",
51
+ token: process.env.VOLUTE_MIND_TOKEN
52
+ };
53
+ }
43
54
  const configPath = resolve(voluteSystemDir(), "daemon.json");
44
55
  if (!existsSync(configPath)) {
45
- if (existsSync("/etc/systemd/system/volute.service") && !process.env.VOLUTE_HOME) {
46
- console.error("Volute is running as a system service but VOLUTE_HOME is not set.");
47
- console.error("Re-run setup to update the CLI wrapper: sudo volute service install --system");
48
- console.error("Then start a new shell or run: source /etc/profile.d/volute.sh");
56
+ const hint = detectSystemInstallHint();
57
+ if (hint) {
58
+ console.error(hint);
49
59
  } else {
50
60
  console.error("Volute is not running. Start with: volute up");
51
61
  }
@@ -111,7 +121,7 @@ async function daemonFetch(path, options) {
111
121
  headers.set("Origin", url);
112
122
  const voluteSession = process.env.VOLUTE_SESSION ?? readMindSessionFile();
113
123
  if (voluteSession) {
114
- headers.set("X-Volute-Session", voluteSession);
124
+ headers.set("X-Volute-Thread", voluteSession);
115
125
  }
116
126
  try {
117
127
  const res = await fetch(`${url}${path}`, {
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getBaseName,
4
+ isSpiritName,
4
5
  validateMindName
5
- } from "./chunk-RSBL3FAD.js";
6
+ } from "./chunk-H6X6CKDN.js";
6
7
 
7
8
  // packages/daemon/src/lib/mind/isolation.ts
8
9
  import { execFileSync as execFileSync2 } from "child_process";
@@ -63,7 +64,7 @@ function isIsolationEnabled() {
63
64
  }
64
65
  function mindUserName(mindName) {
65
66
  const err = validateMindName(mindName);
66
- if (err && mindName !== "volute") {
67
+ if (err && !isSpiritName(mindName)) {
67
68
  throw new Error(`Invalid mind name for isolation: ${err}`);
68
69
  }
69
70
  const prefix = process.env.VOLUTE_USER_PREFIX ?? "mind-";
@@ -255,6 +256,19 @@ async function chownMindDir(dir, name) {
255
256
  throw new Error(`Failed to chmod ${dir}${stderr ? `: ${stderr}` : ""}`);
256
257
  }
257
258
  }
259
+ async function chownMindFile(filePath, name) {
260
+ if (!isIsolationEnabled()) return;
261
+ const user = mindUserName(name);
262
+ const group = process.platform === "darwin" ? "volute" : user;
263
+ try {
264
+ await exec("chown", [`${user}:${group}`, filePath]);
265
+ } catch (err) {
266
+ const stderr = String(err?.stderr ?? "").trim();
267
+ throw new Error(
268
+ `Failed to chown ${filePath} to ${user}:${group}${stderr ? `: ${stderr}` : ""}`
269
+ );
270
+ }
271
+ }
258
272
 
259
273
  export {
260
274
  isIsolationEnabled,
@@ -265,6 +279,7 @@ export {
265
279
  wrapForIsolation,
266
280
  chownTargets,
267
281
  chownMindDir,
282
+ chownMindFile,
268
283
  exec,
269
284
  gitExec,
270
285
  resolveVoluteBin,
@@ -3,10 +3,10 @@ import {
3
3
  LAUNCHD_PLIST_LABEL,
4
4
  LAUNCHD_PLIST_PATH,
5
5
  USER_SYSTEMD_UNIT
6
- } from "./chunk-HCYQRME2.js";
6
+ } from "./chunk-6NLW72HP.js";
7
7
  import {
8
8
  resolveVoluteBin
9
- } from "./chunk-DVN6DNMI.js";
9
+ } from "./chunk-M77B73VV.js";
10
10
 
11
11
  // packages/daemon/src/lib/config/service-install.ts
12
12
  import { execFile } from "child_process";
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildMindBaseEnv,
4
+ clearJsonMap,
5
+ generateMindToken,
6
+ getMindToken,
7
+ loadJsonMap,
8
+ saveJsonMapAsync
9
+ } from "./chunk-VAEZCIJC.js";
10
+ import {
11
+ deliverEvent
12
+ } from "./chunk-ICL6T4SN.js";
13
+ import {
14
+ isSandboxEnabled,
15
+ wrapForSandbox
16
+ } from "./chunk-5Y54AE3L.js";
17
+ import {
18
+ readVoluteConfig,
19
+ writeVoluteConfig
20
+ } from "./chunk-WCFSBH33.js";
21
+ import {
22
+ exec
23
+ } from "./chunk-M77B73VV.js";
24
+ import {
25
+ loadMergedEnv
26
+ } from "./chunk-GLM7TVWP.js";
27
+ import {
28
+ logger_default
29
+ } from "./chunk-QUSMYGNQ.js";
30
+ import {
31
+ findMind,
32
+ mindDir,
33
+ mindTmpDir,
34
+ mindTmpEnv,
35
+ stateDir,
36
+ voluteSystemDir
37
+ } from "./chunk-H6X6CKDN.js";
38
+
39
+ // packages/daemon/src/lib/daemon/scheduler.ts
40
+ import { resolve } from "path";
41
+ import { CronExpressionParser } from "cron-parser";
42
+ var slog = logger_default.child("scheduler");
43
+ var CATCHUP_STALE_MINUTES = 10;
44
+ var Scheduler = class {
45
+ schedules = /* @__PURE__ */ new Map();
46
+ mindDirs = /* @__PURE__ */ new Map();
47
+ // mindName → dir override
48
+ interval = null;
49
+ lastFired = /* @__PURE__ */ new Map();
50
+ // "mind:scheduleId" → epoch minute
51
+ get statePath() {
52
+ return resolve(voluteSystemDir(), "scheduler-state.json");
53
+ }
54
+ start() {
55
+ this.loadState();
56
+ this.interval = setInterval(() => {
57
+ this.tick().catch((err) => slog.error("scheduler tick failed", logger_default.errorData(err)));
58
+ }, 6e4);
59
+ }
60
+ stop() {
61
+ if (this.interval) clearInterval(this.interval);
62
+ }
63
+ loadState() {
64
+ this.lastFired = loadJsonMap(this.statePath);
65
+ }
66
+ async saveState() {
67
+ await saveJsonMapAsync(this.statePath, this.lastFired);
68
+ }
69
+ clearState() {
70
+ clearJsonMap(this.statePath, this.lastFired);
71
+ }
72
+ loadSchedules(mindName, dir) {
73
+ if (dir) this.mindDirs.set(mindName, dir);
74
+ const resolvedDir = this.mindDirs.get(mindName) ?? mindDir(mindName);
75
+ const config = readVoluteConfig(resolvedDir);
76
+ if (!config) return;
77
+ const schedules = config.schedules ?? [];
78
+ if (schedules.length > 0) {
79
+ this.schedules.set(mindName, schedules);
80
+ } else {
81
+ this.schedules.delete(mindName);
82
+ }
83
+ const epochMinute = Math.floor(Date.now() / 6e4);
84
+ const validKeys = new Set(schedules.map((s) => `${mindName}:${s.id}`));
85
+ let changed = false;
86
+ for (const key of this.lastFired.keys()) {
87
+ if (key.startsWith(`${mindName}:`) && !validKeys.has(key)) {
88
+ this.lastFired.delete(key);
89
+ changed = true;
90
+ }
91
+ }
92
+ for (const key of validKeys) {
93
+ if (!this.lastFired.has(key)) {
94
+ this.lastFired.set(key, epochMinute);
95
+ changed = true;
96
+ }
97
+ }
98
+ if (changed) {
99
+ this.saveState().catch(
100
+ (err) => slog.warn(`failed to persist scheduler state for ${mindName}`, logger_default.errorData(err))
101
+ );
102
+ }
103
+ }
104
+ unloadSchedules(mindName) {
105
+ this.schedules.delete(mindName);
106
+ this.mindDirs.delete(mindName);
107
+ }
108
+ async tick() {
109
+ const now = /* @__PURE__ */ new Date();
110
+ const epochMinute = Math.floor(now.getTime() / 6e4);
111
+ const cronCache = /* @__PURE__ */ new Map();
112
+ let anyFired = false;
113
+ for (const [mind, schedules] of this.schedules) {
114
+ for (const schedule of schedules) {
115
+ if (!schedule.enabled) continue;
116
+ if (this.shouldFire(schedule, epochMinute, mind, cronCache)) {
117
+ anyFired = true;
118
+ this.fire(mind, schedule);
119
+ }
120
+ }
121
+ }
122
+ if (anyFired) await this.saveState();
123
+ }
124
+ shouldFire(schedule, epochMinute, mind, cronCache) {
125
+ const key = `${mind}:${schedule.id}`;
126
+ if (this.lastFired.get(key) === epochMinute) return false;
127
+ if (schedule.fireAt) {
128
+ const fireTime = Math.floor(new Date(schedule.fireAt).getTime() / 6e4);
129
+ if (epochMinute >= fireTime) {
130
+ this.lastFired.set(key, epochMinute);
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+ if (!schedule.cron) return false;
136
+ let prevMinute = cronCache.get(schedule.cron);
137
+ if (prevMinute === void 0) {
138
+ try {
139
+ const interval = CronExpressionParser.parse(schedule.cron);
140
+ const prev = interval.prev().toDate();
141
+ prevMinute = Math.floor(prev.getTime() / 6e4);
142
+ cronCache.set(schedule.cron, prevMinute);
143
+ } catch (err) {
144
+ slog.warn(`invalid cron "${schedule.cron}" for ${mind}:${schedule.id}`, logger_default.errorData(err));
145
+ return false;
146
+ }
147
+ }
148
+ const lastFired = this.lastFired.get(key) ?? epochMinute;
149
+ if (prevMinute <= lastFired) return false;
150
+ this.lastFired.set(key, prevMinute);
151
+ if (epochMinute - prevMinute > CATCHUP_STALE_MINUTES) {
152
+ slog.info(`skipping stale catch-up fire for ${key} (${epochMinute - prevMinute}min late)`);
153
+ return false;
154
+ }
155
+ return true;
156
+ }
157
+ async fire(mindName, schedule) {
158
+ try {
159
+ let text;
160
+ if (schedule.script) {
161
+ const homeDir = resolve(this.mindDirs.get(mindName) ?? mindDir(mindName), "home");
162
+ try {
163
+ const output = await this.runScript(schedule.script, homeDir, mindName);
164
+ if (!output.trim()) {
165
+ slog.info(`fired script "${schedule.id}" for ${mindName} (no output)`);
166
+ return;
167
+ }
168
+ text = output;
169
+ } catch (err) {
170
+ const stderr = err.stderr ?? "";
171
+ text = `[script error] ${err.message}${stderr ? `
172
+ ${stderr}` : ""}`;
173
+ slog.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
174
+ }
175
+ } else if (Array.isArray(schedule.messages) && schedule.messages.length > 0) {
176
+ const pick = schedule.messages[Math.floor(Math.random() * schedule.messages.length)];
177
+ if (typeof pick !== "string" || !pick.trim()) {
178
+ slog.warn(`schedule "${schedule.id}" for ${mindName} has a malformed messages pool`);
179
+ return;
180
+ }
181
+ text = pick;
182
+ } else if (schedule.message) {
183
+ text = schedule.message;
184
+ } else {
185
+ slog.warn(`schedule "${schedule.id}" for ${mindName} has no message, messages, or script`);
186
+ return;
187
+ }
188
+ const { id, delivered } = await this.deliverSystem(mindName, schedule.id, text, {
189
+ // Default schedule fires to "queue" while asleep so an unadorned cron
190
+ // schedule doesn't inherit the DM wake-trigger fallback and wake the mind.
191
+ whileSleeping: schedule.whileSleeping ?? "queue",
192
+ thread: schedule.thread
193
+ });
194
+ slog.info(`fired "${schedule.id}" for ${mindName}${delivered ? "" : " (event pending)"}`);
195
+ if (schedule.fireAt && id != null) {
196
+ this.removeSchedule(mindName, schedule.id);
197
+ }
198
+ } catch (err) {
199
+ slog.warn(`failed to fire "${schedule.id}" for ${mindName}`, logger_default.errorData(err));
200
+ }
201
+ }
202
+ removeSchedule(mindName, scheduleId) {
203
+ const memSchedules = this.schedules.get(mindName);
204
+ if (memSchedules) {
205
+ const filtered = memSchedules.filter((s) => s.id !== scheduleId);
206
+ if (filtered.length > 0) {
207
+ this.schedules.set(mindName, filtered);
208
+ } else {
209
+ this.schedules.delete(mindName);
210
+ }
211
+ }
212
+ try {
213
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
214
+ const config = readVoluteConfig(dir);
215
+ if (!config?.schedules) return;
216
+ config.schedules = config.schedules.filter((s) => s.id !== scheduleId);
217
+ if (config.schedules.length === 0) config.schedules = void 0;
218
+ writeVoluteConfig(dir, config);
219
+ slog.info(`removed one-time schedule "${scheduleId}" for ${mindName}`);
220
+ } catch (err) {
221
+ slog.error(
222
+ `failed to persist removal of schedule "${scheduleId}" for ${mindName} (removed from memory)`,
223
+ logger_default.errorData(err)
224
+ );
225
+ }
226
+ }
227
+ async runScript(script, cwd, mindName) {
228
+ const env = await this.buildScriptEnv(mindName);
229
+ if (isSandboxEnabled()) {
230
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
231
+ const [cmd, args] = await wrapForSandbox("bash", ["-c", script], dir, mindName, [
232
+ dir,
233
+ mindTmpDir(dir)
234
+ ]);
235
+ return exec(cmd, args, { cwd, env });
236
+ }
237
+ return exec("bash", ["-c", script], { cwd, mindName, env });
238
+ }
239
+ /**
240
+ * Build the environment for a scheduled script — mirrors the mind process env
241
+ * (allowlisted base + merged mind env + VOLUTE_* runtime vars), authenticated
242
+ * with the mind's own per-mind, non-admin token. Reuses the running mind's
243
+ * token when present; otherwise mints one (stable across fires, regenerated on
244
+ * next mind start). The daemon admin token is never included.
245
+ */
246
+ async buildScriptEnv(mindName) {
247
+ const dir = this.mindDirs.get(mindName) ?? mindDir(mindName);
248
+ const entry = await findMind(mindName);
249
+ const token = getMindToken(mindName) ?? generateMindToken(mindName);
250
+ const mindLocalBin = resolve(dir, "home", ".local", "bin");
251
+ const currentPath = process.env.PATH ?? "";
252
+ return {
253
+ ...buildMindBaseEnv(),
254
+ ...loadMergedEnv(mindName),
255
+ VOLUTE_MIND: mindName,
256
+ VOLUTE_STATE_DIR: stateDir(mindName),
257
+ VOLUTE_MIND_DIR: dir,
258
+ VOLUTE_MIND_PORT: entry ? String(entry.port) : void 0,
259
+ VOLUTE_MIND_TOKEN: token,
260
+ ...mindTmpEnv(dir),
261
+ PATH: `${mindLocalBin}:${currentPath}`
262
+ };
263
+ }
264
+ async deliverSystem(mindName, scheduleId, text, opts) {
265
+ return deliverEvent(mindName, {
266
+ type: "schedule",
267
+ body: text,
268
+ meta: { scheduleId },
269
+ thread: opts?.thread,
270
+ whileSleeping: opts?.whileSleeping
271
+ });
272
+ }
273
+ };
274
+ var instance = null;
275
+ function initScheduler() {
276
+ if (instance) throw new Error("Scheduler already initialized");
277
+ instance = new Scheduler();
278
+ return instance;
279
+ }
280
+ function getScheduler() {
281
+ if (!instance) throw new Error("Scheduler not initialized \u2014 call initScheduler() first");
282
+ return instance;
283
+ }
284
+
285
+ export {
286
+ Scheduler,
287
+ initScheduler,
288
+ getScheduler
289
+ };
@@ -2,23 +2,21 @@
2
2
  import {
3
3
  exec,
4
4
  gitExec
5
- } from "./chunk-DVN6DNMI.js";
6
- import {
7
- readGlobalConfig,
8
- writeGlobalConfig
9
- } from "./chunk-NJHN6IRB.js";
5
+ } from "./chunk-M77B73VV.js";
10
6
  import {
11
7
  logger_default
12
8
  } from "./chunk-QUSMYGNQ.js";
13
9
  import {
14
10
  getDb,
15
11
  mindDir,
12
+ readGlobalConfig,
16
13
  readRegistry,
17
- voluteHome
18
- } from "./chunk-RSBL3FAD.js";
14
+ voluteHome,
15
+ writeGlobalConfig
16
+ } from "./chunk-H6X6CKDN.js";
19
17
  import {
20
18
  sharedSkills
21
- } from "./chunk-P4LHVRNE.js";
19
+ } from "./chunk-2RNMBXD2.js";
22
20
 
23
21
  // packages/daemon/src/lib/skills.ts
24
22
  import { createHash } from "crypto";
@@ -47,7 +45,7 @@ async function initDefaultSkills() {
47
45
  const config = readGlobalConfig();
48
46
  let extensionSkills = [];
49
47
  try {
50
- const { getExtensionStandardSkills } = await import("./extensions-AWNO3SZF.js");
48
+ const { getExtensionStandardSkills } = await import("./extensions-NY6WST33.js");
51
49
  extensionSkills = getExtensionStandardSkills();
52
50
  } catch (err) {
53
51
  logger_default.warn("failed to load extension standard skills during init", logger_default.errorData(err));
@@ -639,7 +637,7 @@ async function syncBuiltinSkills() {
639
637
  const destDir = join(sharedSkillsDir(), entry.name);
640
638
  if (existsSync(destDir)) {
641
639
  const destHash = hashSkillDir(destDir);
642
- if (sourceHash === destHash) continue;
640
+ if (sourceHash === destHash && await getSharedSkill(entry.name)) continue;
643
641
  }
644
642
  await importSkillFromDir(sourceDir, "volute");
645
643
  logger_default.info(`synced built-in skill: ${entry.name}`);
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  bufferEvent,
4
4
  subscribe
5
- } from "./chunk-3B427GUK.js";
5
+ } from "./chunk-U54PJPTB.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-QUSMYGNQ.js";
9
9
  import {
10
10
  getDb
11
- } from "./chunk-RSBL3FAD.js";
11
+ } from "./chunk-H6X6CKDN.js";
12
12
  import {
13
13
  channels,
14
14
  conversationParticipants,
@@ -16,7 +16,7 @@ import {
16
16
  conversations,
17
17
  messages,
18
18
  users
19
- } from "./chunk-P4LHVRNE.js";
19
+ } from "./chunk-2RNMBXD2.js";
20
20
 
21
21
  // packages/daemon/src/lib/events/conversations.ts
22
22
  import { randomUUID } from "crypto";
@@ -99,6 +99,21 @@ function subscribe2(conversationId, callback) {
99
99
  if (set.size === 0) subscribers.delete(conversationId);
100
100
  };
101
101
  }
102
+ var participantSubscribers = /* @__PURE__ */ new Set();
103
+ function subscribeParticipantAdded(callback) {
104
+ participantSubscribers.add(callback);
105
+ return () => participantSubscribers.delete(callback);
106
+ }
107
+ function publishParticipantAdded(conversationId, userId) {
108
+ for (const cb of participantSubscribers) {
109
+ try {
110
+ cb({ conversationId, userId });
111
+ } catch (err) {
112
+ console.error("[conversation-events] participant subscriber threw:", err);
113
+ participantSubscribers.delete(cb);
114
+ }
115
+ }
116
+ }
102
117
  function publish(conversationId, event) {
103
118
  const set = subscribers.get(conversationId);
104
119
  if (!set) return;
@@ -134,6 +149,9 @@ async function createConversation(opts) {
134
149
  role: i === 0 ? "owner" : "member"
135
150
  }))
136
151
  );
152
+ for (const uid of opts.participantIds) {
153
+ publishParticipantAdded(id, uid);
154
+ }
137
155
  }
138
156
  fireWebhook({
139
157
  event: "conversation_created",
@@ -161,6 +179,7 @@ async function addParticipant(conversationId, userId, role = "member") {
161
179
  user_id: userId,
162
180
  role
163
181
  });
182
+ publishParticipantAdded(conversationId, userId);
164
183
  }
165
184
  async function removeParticipant(conversationId, userId) {
166
185
  const db = await getDb();
@@ -251,6 +270,11 @@ async function addMessage(conversationId, role, senderName, content, opts) {
251
270
  });
252
271
  return msg;
253
272
  }
273
+ async function getLastMessageBySender(conversationId, sender) {
274
+ const db = await getDb();
275
+ const row = await db.select().from(messages).where(and(eq(messages.conversation_id, conversationId), eq(messages.sender_name, sender))).orderBy(desc(messages.id)).limit(1).get();
276
+ return row ? parseMessageRow(row) : null;
277
+ }
254
278
  async function getMessages(conversationId, limit = 200) {
255
279
  const db = await getDb();
256
280
  const rows = await db.select().from(messages).where(eq(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(limit).all();
@@ -351,6 +375,12 @@ async function listConversationsWithParticipants(userId) {
351
375
  const convs = await listConversationsForUser(userId);
352
376
  return enrichConversations(convs);
353
377
  }
378
+ async function getConversationWithParticipants(conversationId) {
379
+ const conv = await getConversation(conversationId);
380
+ if (!conv) return null;
381
+ const [enriched] = await enrichConversations([conv]);
382
+ return enriched ?? null;
383
+ }
354
384
  async function findDMConversation(participantIds) {
355
385
  const db = await getDb();
356
386
  const [id1, id2] = participantIds;
@@ -513,12 +543,13 @@ async function markConversationRead(userId, conversationId) {
513
543
  }
514
544
 
515
545
  export {
546
+ subscribe2 as subscribe,
547
+ subscribeParticipantAdded,
548
+ publish,
516
549
  getWebhookUrl,
517
550
  getAuthHeaders,
518
551
  fireWebhook,
519
552
  initWebhook,
520
- subscribe2 as subscribe,
521
- publish,
522
553
  createConversation,
523
554
  getConversation,
524
555
  addParticipant,
@@ -529,9 +560,11 @@ export {
529
560
  isParticipantOrOwner,
530
561
  deleteConversationForUser,
531
562
  addMessage,
563
+ getLastMessageBySender,
532
564
  getMessages,
533
565
  getMessagesPaginated,
534
566
  listConversationsWithParticipants,
567
+ getConversationWithParticipants,
535
568
  findDMConversation,
536
569
  listConversationsForMind,
537
570
  isConversationForMind,
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-QUSMYGNQ.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-RSBL3FAD.js";
7
+ } from "./chunk-H6X6CKDN.js";
8
8
  import {
9
9
  activity
10
- } from "./chunk-P4LHVRNE.js";
10
+ } from "./chunk-2RNMBXD2.js";
11
11
 
12
12
  // packages/daemon/src/lib/events/event-sequencer.ts
13
13
  var BUFFER_SIZE = 1e3;
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-QUSMYGNQ.js";
5
5
  import {
6
6
  mindDir
7
- } from "./chunk-RSBL3FAD.js";
7
+ } from "./chunk-H6X6CKDN.js";
8
8
 
9
9
  // packages/daemon/src/lib/delivery/delivery-router.ts
10
10
  import { readFileSync, statSync } from "fs";
@@ -63,6 +63,7 @@ function getRoutingConfig(mindName) {
63
63
  try {
64
64
  const config = JSON.parse(readFileSync(path, "utf-8"));
65
65
  const changed = cached != null && cached.mtime !== mtime;
66
+ warnUnknownRuleKeys(mindName, config);
66
67
  configCache.set(mindName, { config, mtime });
67
68
  if (changed) notifyRoutesChanged(mindName);
68
69
  return config;
@@ -94,7 +95,20 @@ function globMatch(pattern, value) {
94
95
  return regex.test(value);
95
96
  }
96
97
  var GLOB_MATCH_KEYS = /* @__PURE__ */ new Set(["channel", "sender"]);
97
- var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["session", "destination", "path", "mode", "batch"]);
98
+ var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["thread", "destination", "path", "mode", "batch"]);
99
+ var KNOWN_RULE_KEYS = /* @__PURE__ */ new Set([...GLOB_MATCH_KEYS, ...NON_MATCH_KEYS, "isDM", "participants"]);
100
+ function warnUnknownRuleKeys(mindName, config) {
101
+ if (!Array.isArray(config.rules)) return;
102
+ for (const rule of config.rules) {
103
+ if (rule == null || typeof rule !== "object") continue;
104
+ const unknown = Object.keys(rule).filter((k) => !KNOWN_RULE_KEYS.has(k));
105
+ if (unknown.length > 0) {
106
+ dlog.warn(
107
+ `routes.json for ${mindName} has a rule with unrecognized key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the rule will never match (known keys: ${[...KNOWN_RULE_KEYS].join(", ")})`
108
+ );
109
+ }
110
+ }
111
+ }
98
112
  function ruleMatches(rule, meta) {
99
113
  for (const [key, pattern] of Object.entries(rule)) {
100
114
  if (NON_MATCH_KEYS.has(key)) continue;
@@ -137,7 +151,7 @@ function resolveRoute(config, meta) {
137
151
  }
138
152
  return {
139
153
  destination: "mind",
140
- session: sanitizeSessionName(expandTemplate(rule.session ?? fallback, meta)),
154
+ session: sanitizeSessionName(expandTemplate(rule.thread ?? fallback, meta)),
141
155
  matched: true,
142
156
  mode: rule.mode,
143
157
  rule
@@ -160,7 +174,7 @@ function resolveDeliveryMode(config, sessionName, rule) {
160
174
  const defaults = {
161
175
  delivery: { mode: "immediate" }
162
176
  };
163
- if (!config.sessions) {
177
+ if (!config.threads) {
164
178
  if (ruleBatch != null) {
165
179
  const batch = normalizeBatchConfig(ruleBatch);
166
180
  return {
@@ -174,7 +188,7 @@ function resolveDeliveryMode(config, sessionName, rule) {
174
188
  }
175
189
  return defaults;
176
190
  }
177
- for (const [pattern, sessionConfig] of Object.entries(config.sessions)) {
191
+ for (const [pattern, sessionConfig] of Object.entries(config.threads)) {
178
192
  if (globMatch(pattern, sessionName)) {
179
193
  let delivery;
180
194
  if (sessionConfig.delivery == null || sessionConfig.delivery === "immediate") {
@@ -219,6 +233,7 @@ export {
219
233
  registerMindDir,
220
234
  getRoutingConfig,
221
235
  clearConfigCache,
236
+ warnUnknownRuleKeys,
222
237
  resolveRoute,
223
238
  shouldGate,
224
239
  resolveDeliveryMode
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-3B427GUK.js";
4
+ } from "./chunk-U54PJPTB.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-QUSMYGNQ.js";