volute 0.49.0 → 0.50.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 (210) 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-H7YI3BTF.js} +3 -3
  4. package/dist/{ai-service-A7FS7EC3.js → ai-service-V4CTNGI3.js} +5 -4
  5. package/dist/{archive-7E3YE6VF.js → archive-ORM3ENHI.js} +3 -3
  6. package/dist/{auth-ABYLOEZ7.js → auth-DT7GY7EZ.js} +10 -6
  7. package/dist/{avatar-image-6ATV2IOZ.js → avatar-image-QEPAP3A5.js} +3 -3
  8. package/dist/{backup-XYAMT4CU.js → backup-IE6PMXJO.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-W5K6HCBE.js} +9 -9
  12. package/dist/{chunk-3B427GUK.js → chunk-3BAFMGCJ.js} +2 -2
  13. package/dist/{chunk-4B62E4CG.js → chunk-4CN7OCJE.js} +2 -2
  14. package/dist/{chunk-VNRSXEBF.js → chunk-574M6I7F.js} +38 -5
  15. package/dist/{chunk-HCYQRME2.js → chunk-5FVANDON.js} +2 -2
  16. package/dist/{chunk-P4LHVRNE.js → chunk-5IIVOWSP.js} +27 -19
  17. package/dist/{chunk-TXJL36ZQ.js → chunk-5PKFJHCR.js} +2 -2
  18. package/dist/{chunk-JPGHSXSV.js → chunk-5TPC4KCV.js} +23 -9
  19. package/dist/{chunk-YIJGKHIS.js → chunk-632FJ23O.js} +24 -20
  20. package/dist/{chunk-IH5TKXX6.js → chunk-636GFQNE.js} +11 -11
  21. package/dist/{chunk-EFT2OALG.js → chunk-6BM2I736.js} +20 -5
  22. package/dist/{chunk-JCBPU57A.js → chunk-74T3CEXK.js} +6 -6
  23. package/dist/{chunk-EG5LSKHU.js → chunk-77IZUVBB.js} +2 -2
  24. package/dist/{chunk-K3JOLGWF.js → chunk-7DXLIYPY.js} +76 -63
  25. package/dist/chunk-7FDWPQCU.js +289 -0
  26. package/dist/chunk-7OZWKHA3.js +548 -0
  27. package/dist/{chunk-PY24ZL62.js → chunk-7UWXAJMK.js} +1 -1
  28. package/dist/{chunk-MG3Z34YE.js → chunk-7XDOTN4R.js} +1 -1
  29. package/dist/{chunk-SX72DOO3.js → chunk-AFSLNUTT.js} +1 -1
  30. package/dist/{chunk-DVN6DNMI.js → chunk-AJWFDIEY.js} +17 -2
  31. package/dist/chunk-ALIVXDM4.js +1014 -0
  32. package/dist/{chunk-RSBL3FAD.js → chunk-CAIYV33P.js} +234 -24
  33. package/dist/{chunk-ZX46PGCC.js → chunk-E5S3ILVA.js} +1 -1
  34. package/dist/{chunk-SHKM5RGD.js → chunk-EEOMA4DX.js} +1 -1
  35. package/dist/chunk-EQKSRVDE.js +2857 -0
  36. package/dist/chunk-FNJCBRD3.js +31 -0
  37. package/dist/{chunk-EADX54RG.js → chunk-HW6Q7AMB.js} +4 -4
  38. package/dist/chunk-JI67D7O2.js +1222 -0
  39. package/dist/{chunk-KHCKZ36O.js → chunk-KAEZOVP6.js} +1 -1
  40. package/dist/chunk-LNNI3MS4.js +54 -0
  41. package/dist/{chunk-BCG45AQB.js → chunk-M74I3HHF.js} +15 -5
  42. package/dist/chunk-MLZBBLMT.js +109 -0
  43. package/dist/{chunk-5T5YMX6S.js → chunk-PW65BXY5.js} +5 -0
  44. package/dist/{chunk-NOIAHMJB.js → chunk-QFVGPKJI.js} +1 -1
  45. package/dist/{chunk-VIUH5CHQ.js → chunk-SFRYRW37.js} +50 -33
  46. package/dist/{chunk-L27YYONJ.js → chunk-SSNZ6ZNN.js} +1 -1
  47. package/dist/{chunk-GWVFDUNB.js → chunk-TGJHGVBZ.js} +8 -10
  48. package/dist/{chunk-6HGEZJDH.js → chunk-U3EJJ7JN.js} +2 -2
  49. package/dist/{chunk-4DSGN4FL.js → chunk-UC6BNMGT.js} +43 -13
  50. package/dist/{chunk-JBIFXB6I.js → chunk-WBBKYUGF.js} +3 -5
  51. package/dist/{chunk-FBBS4BVR.js → chunk-X67VT4QO.js} +1 -1
  52. package/dist/{chunk-DAIIMBLB.js → chunk-YQOYZ6G7.js} +3 -5
  53. package/dist/{chunk-JCWB2ITW.js → chunk-ZUG76D3P.js} +35 -5
  54. package/dist/cli.js +34 -27
  55. package/dist/{clock-6JZL2FS6.js → clock-LH7UFNLJ.js} +4 -3
  56. package/dist/{cloud-sync-VFDTNNCA.js → cloud-sync-IDJFVDEP.js} +22 -19
  57. package/dist/{config-RPSLA2HX.js → config-UGJS57VT.js} +3 -3
  58. package/dist/{conversations-TUM5PJJ4.js → conversations-S2232Y5I.js} +8 -4
  59. package/dist/{create-Z5FSDNVZ.js → create-2M7DAHQX.js} +2 -2
  60. package/dist/{create-VIQVP3QB.js → create-M727VUDF.js} +2 -1
  61. package/dist/{daemon-client-ZFY5MOAL.js → daemon-client-VVYFZI2D.js} +2 -1
  62. package/dist/{daemon-restart-54L4B6AQ.js → daemon-restart-2AAGYFHE.js} +6 -7
  63. package/dist/daemon.js +1625 -529
  64. package/dist/{db-F6UMFRYD.js → db-QAY45Z56.js} +2 -2
  65. package/dist/{delete-ESEP7NGQ.js → delete-O7PEFQDF.js} +1 -1
  66. package/dist/delivery-manager-B4OKWD2P.js +33 -0
  67. package/dist/{delivery-router-VUOL5YSA.js → delivery-router-JE7SHYNU.js} +7 -5
  68. package/dist/{down-6X7KX7BZ.js → down-D4IRNEMJ.js} +5 -5
  69. package/dist/echo-text-WYAAODBV.js +39 -0
  70. package/dist/{env-ZYMSCB7B.js → env-ABM5IMA7.js} +3 -2
  71. package/dist/{env-KUL4XKN2.js → env-AZK5X4W7.js} +3 -3
  72. package/dist/{exec-FBXA3CM4.js → exec-2H65LMTA.js} +3 -3
  73. package/dist/{export-TNBPDH77.js → export-3E3EL6W3.js} +4 -4
  74. package/dist/{extension-J4ZQ2GHV.js → extension-MRYJWSMV.js} +2 -1
  75. package/dist/{extensions-AWNO3SZF.js → extensions-7KP3X6G7.js} +22 -19
  76. package/dist/{files-BVBRJJ2P.js → files-6BMKVG5G.js} +5 -4
  77. package/dist/{import-ZUNKMCKC.js → import-UPNTEBBT.js} +8 -8
  78. package/dist/{isolation-OPCZQRTH.js → isolation-OKDFM6WA.js} +5 -3
  79. package/dist/{join-34TR2GXQ.js → join-PYA36HEC.js} +1 -1
  80. package/dist/{list-PMUXZNAM.js → list-4BVGH7AN.js} +8 -3
  81. package/dist/{login-KAKNFCW5.js → login-MOXVLULE.js} +4 -3
  82. package/dist/{login-LKNLCJPA.js → login-RJJTCVR3.js} +2 -1
  83. package/dist/{logout-YZRMZ3O2.js → logout-BYV433X6.js} +2 -1
  84. package/dist/{logout-RLT62RQP.js → logout-DWD3OQVT.js} +4 -3
  85. package/dist/message-delivery-3456PXB7.js +43 -0
  86. package/dist/migrate-thread-config-4A5LW36Z.js +90 -0
  87. package/dist/{mind-IF6CGMFW.js → mind-NYRALSV2.js} +19 -19
  88. package/dist/{mind-activity-tracker-YMMERB6B.js → mind-activity-tracker-L7GCYXQR.js} +4 -4
  89. package/dist/{mind-contacts-BKUFPUCU.js → mind-contacts-QYU4TPEA.js} +3 -2
  90. package/dist/{mind-history-WI5OI3BW.js → mind-history-NXXOOTX6.js} +30 -7
  91. package/dist/{mind-list-OTHV4ZO4.js → mind-list-SYRA7CH5.js} +3 -2
  92. package/dist/{mind-profile-LUG353ZT.js → mind-profile-MRCVZPYT.js} +1 -1
  93. package/dist/mind-service-5TA7UJAT.js +47 -0
  94. package/dist/{mind-sleep-VBV7YRSU.js → mind-sleep-TH6BRQ5X.js} +4 -3
  95. package/dist/{mind-status-BJNPGXKC.js → mind-status-Z2LQFA6F.js} +3 -2
  96. package/dist/{mind-wake-LVTZ55EZ.js → mind-wake-LDGPRA42.js} +2 -1
  97. package/dist/{package-PD67VPHB.js → package-XYSV7NUT.js} +23 -23
  98. package/dist/{prompts-MDHDLWEX.js → prompts-QKFPX6DQ.js} +3 -3
  99. package/dist/{read-YSWUXYL7.js → read-ZXGUT4QC.js} +5 -3
  100. package/dist/{register-TWQDKQRT.js → register-OX4HRSGC.js} +2 -1
  101. package/dist/{registry-3ELJPUGJ.js → registry-4UF6OCOE.js} +6 -4
  102. package/dist/{reject-SXGCYN4A.js → reject-TWB4QJHG.js} +2 -1
  103. package/dist/{restart-5F5QDBOG.js → restart-AGEGNSYQ.js} +2 -1
  104. package/dist/{sandbox-XPYA7UIF.js → sandbox-EGEU5GGE.js} +7 -4
  105. package/dist/scheduler-UV6RUJ7P.js +25 -0
  106. package/dist/{schema-6YONVTKZ.js → schema-C2GIPD4V.js} +3 -3
  107. package/dist/{seed-NQED7PSX.js → seed-3IMBOAGK.js} +1 -1
  108. package/dist/{seed-check-TYD7RRZY.js → seed-check-NRJEQZST.js} +14 -5
  109. package/dist/{seed-cmd-QF3F2G4S.js → seed-cmd-U73XVQMI.js} +3 -3
  110. package/dist/{seed-create-XOO4N2OE.js → seed-create-FBLNVSUP.js} +4 -4
  111. package/dist/{seed-readiness-3LRYASA4.js → seed-readiness-QRA46ZJQ.js} +4 -5
  112. package/dist/{seed-sprout-HSBBGSS2.js → seed-sprout-3B4SZX7J.js} +9 -9
  113. package/dist/{send-IHRJYNXA.js → send-NBRZJXKX.js} +32 -10
  114. package/dist/{service-YCOPCTSZ.js → service-XMXAXGJD.js} +4 -4
  115. package/dist/{service-install-Y46OTF55.js → service-install-MOKTEG7B.js} +5 -5
  116. package/dist/{setup-TCN7VH5U.js → setup-CAJFOR2N.js} +10 -3
  117. package/dist/{setup-F4TE64YI.js → setup-JGYAXXW7.js} +6 -7
  118. package/dist/{skill-4CYJLYVY.js → skill-NXIHDQCG.js} +3 -2
  119. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  120. package/dist/skills/imagegen/SKILL.md +21 -4
  121. package/dist/skills/imagegen/scripts/imagegen.ts +124 -21
  122. package/dist/skills/tending/SKILL.md +7 -7
  123. package/dist/skills/volute-mind/SKILL.md +3 -3
  124. package/dist/skills/volute-mind/references/integrations.md +2 -2
  125. package/dist/skills/volute-mind/references/routing.md +15 -15
  126. package/dist/{skills-NN5UZCUU.js → skills-5MSMT7PT.js} +4 -5
  127. package/dist/sleep-manager-LIEPO6WG.js +41 -0
  128. package/dist/{spirit-SSJMYC6A.js → spirit-IL5EMWYZ.js} +10 -7
  129. package/dist/spirit-availability-YFEGJNJ2.js +42 -0
  130. package/dist/{split-JF2VWTXA.js → split-BU5VWX6P.js} +1 -1
  131. package/dist/{sprout-AXA2SECQ.js → sprout-UPXGJ5HX.js} +1 -1
  132. package/dist/{src-YH6PQ7LJ.js → src-HGNE2XG4.js} +7 -1
  133. package/dist/{src-ZN4LXMPI.js → src-LCSOQY6Z.js} +57 -51
  134. package/dist/{src-JAOVXO5W.js → src-UDMJZ6MH.js} +8 -8
  135. package/dist/{start-BTVMTP3K.js → start-MUMNFR52.js} +2 -1
  136. package/dist/{status-VJGXMQ4L.js → status-IL67CYM4.js} +7 -6
  137. package/dist/{stop-YORESK4U.js → stop-EKERNYTX.js} +2 -1
  138. package/dist/system-events-A6IGCEC3.js +46 -0
  139. package/dist/system-install-YZSWCPDK.js +8 -0
  140. package/dist/{systems-5MH2ELQE.js → systems-VDRUZXNF.js} +5 -4
  141. package/dist/{tailscale-AMMVKDXB.js → tailscale-SJUW2ETR.js} +2 -2
  142. package/dist/{template-staleness-HKGCTS3H.js → template-staleness-T27VCGYA.js} +3 -3
  143. package/dist/{turn-tracker-JQ2YUUY2.js → turn-tracker-6EI2EL7G.js} +6 -6
  144. package/dist/{up-SDLTLPAO.js → up-CLL4665A.js} +5 -6
  145. package/dist/{update-WEA4N2TR.js → update-HVAUCQPN.js} +5 -5
  146. package/dist/{update-check-DZOTONII.js → update-check-2MGBBAFF.js} +3 -3
  147. package/dist/{upgrade-K5FIQPQD.js → upgrade-LIE46UBB.js} +1 -1
  148. package/dist/variant-cleanup-HJ7Z75R6.js +22 -0
  149. package/dist/{version-notify-WFA5XB5A.js → version-notify-7BUHWZNT.js} +10 -27
  150. package/dist/web-assets/assets/index-ClsUcXiW.js +95 -0
  151. package/dist/web-assets/assets/index-Dz3wTbtR.css +1 -0
  152. package/dist/web-assets/index.html +2 -2
  153. package/dist/xai-P42CRCZA.js +129 -0
  154. package/drizzle/0014_system_events.sql +26 -0
  155. package/drizzle/0015_rename_session_to_thread.sql +12 -0
  156. package/drizzle/0016_brain_to_human.sql +4 -0
  157. package/drizzle/0017_event_history_rows.sql +9 -0
  158. package/drizzle/meta/_journal.json +28 -0
  159. package/package.json +23 -23
  160. package/packages/extensions/notes/dist/ui/assets/index-BBs8pyel.js +67 -0
  161. package/packages/extensions/notes/dist/ui/index.html +1 -1
  162. package/packages/extensions/pages/dist/ui/assets/index-B589e8Xe.js +2 -0
  163. package/packages/extensions/pages/dist/ui/index.html +1 -1
  164. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +67 -0
  165. package/packages/extensions/plan/dist/ui/index.html +1 -1
  166. package/templates/_base/.init/.config/prompts.json +1 -0
  167. package/templates/_base/.init/.local/hooks/startup-context.ts +24 -9
  168. package/templates/_base/home/.config/routes.json +2 -2
  169. package/templates/_base/home/VOLUTE.md +18 -5
  170. package/templates/_base/src/lib/daemon-client.ts +1 -1
  171. package/templates/_base/src/lib/event-turn.ts +39 -0
  172. package/templates/_base/src/lib/format-prefix.ts +21 -2
  173. package/templates/_base/src/lib/router.ts +6 -2
  174. package/templates/_base/src/lib/routing.ts +8 -8
  175. package/templates/_base/src/lib/startup.ts +9 -1
  176. package/templates/_base/src/lib/types.ts +7 -1
  177. package/templates/_base/src/lib/volute-server.ts +30 -1
  178. package/templates/claude/.init/.config/routes.json +3 -3
  179. package/templates/claude/.init/CLAUDE.md +1 -1
  180. package/templates/claude/package.json.tmpl +4 -4
  181. package/templates/claude/src/agent.ts +4 -1
  182. package/templates/claude/src/lib/hooks/reply-instructions.ts +26 -1
  183. package/templates/codex/.init/.config/routes.json +3 -3
  184. package/templates/codex/.init/AGENTS.md +1 -1
  185. package/templates/codex/package.json.tmpl +4 -4
  186. package/templates/codex/src/agent.ts +11 -11
  187. package/templates/codex/src/lib/turn-context.ts +51 -0
  188. package/templates/pi/.init/.config/routes.json +3 -3
  189. package/templates/pi/.init/MINDS.md +1 -1
  190. package/templates/pi/package.json.tmpl +6 -6
  191. package/templates/pi/src/agent.ts +9 -1
  192. package/templates/pi/src/lib/event-handler.ts +1 -1
  193. package/templates/pi/src/lib/reply-instructions-extension.ts +28 -1
  194. package/dist/chunk-NJHN6IRB.js +0 -177
  195. package/dist/chunk-QQBOI3QA.js +0 -5345
  196. package/dist/chunk-XTEKINWG.js +0 -158
  197. package/dist/delivery-manager-RBXBPVWM.js +0 -36
  198. package/dist/echo-text-B6NZ5FGE.js +0 -36
  199. package/dist/message-delivery-ABPV76EA.js +0 -42
  200. package/dist/mind-service-42GDO4KH.js +0 -44
  201. package/dist/notices-N6BCGHFV.js +0 -25
  202. package/dist/scheduler-K7Y7Z7FQ.js +0 -34
  203. package/dist/sleep-manager-DSPXFHGX.js +0 -38
  204. package/dist/system-chat-M2XOBKEM.js +0 -40
  205. package/dist/variant-cleanup-P4MBNLJD.js +0 -33
  206. package/dist/web-assets/assets/index-BostMFoR.css +0 -1
  207. package/dist/web-assets/assets/index-CKlqUkyG.js +0 -92
  208. package/packages/extensions/notes/dist/ui/assets/index-D0-DgYyj.js +0 -67
  209. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +0 -2
  210. package/packages/extensions/plan/dist/ui/assets/index-Cfzs-0G9.js +0 -67
package/dist/daemon.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  readBackupState,
7
7
  resticVersion,
8
8
  runBackup
9
- } from "./chunk-JBIFXB6I.js";
9
+ } from "./chunk-WBBKYUGF.js";
10
10
  import {
11
11
  acceptPending,
12
12
  formatFileSize,
@@ -14,37 +14,44 @@ import {
14
14
  rejectPending,
15
15
  stageFile,
16
16
  validateFilePath
17
- } from "./chunk-NOIAHMJB.js";
17
+ } from "./chunk-QFVGPKJI.js";
18
18
  import {
19
19
  findOpenClawSession,
20
20
  importOpenClawConnectors,
21
21
  importPiSession,
22
22
  parseNameFromIdentity
23
- } from "./chunk-EG5LSKHU.js";
23
+ } from "./chunk-77IZUVBB.js";
24
+ import {
25
+ SPIRIT_NOTICE_PREFIX,
26
+ ensureSpiritAvailable
27
+ } from "./chunk-MLZBBLMT.js";
24
28
  import {
25
29
  isHomeOnlyArchive
26
- } from "./chunk-KHCKZ36O.js";
30
+ } from "./chunk-KAEZOVP6.js";
27
31
  import {
28
- cleanupVariant
29
- } from "./chunk-EADX54RG.js";
32
+ evaluateSeedChecklist
33
+ } from "./chunk-5PKFJHCR.js";
30
34
  import {
31
35
  isTemplateStale
32
- } from "./chunk-L27YYONJ.js";
36
+ } from "./chunk-SSNZ6ZNN.js";
33
37
  import {
34
38
  computeTemplateHash
35
39
  } from "./chunk-C3KLHJDB.js";
40
+ import {
41
+ cleanupVariant
42
+ } from "./chunk-HW6Q7AMB.js";
36
43
  import {
37
44
  checkForUpdate,
38
45
  checkForUpdateCached,
39
46
  getCurrentVersion
40
- } from "./chunk-SX72DOO3.js";
47
+ } from "./chunk-AFSLNUTT.js";
41
48
  import {
42
49
  PLATFORMS,
43
50
  echoTextToChannel,
44
51
  fixModelEscapes,
45
52
  getPlatformDriver,
46
53
  routeOutboundBridge
47
- } from "./chunk-IH5TKXX6.js";
54
+ } from "./chunk-636GFQNE.js";
48
55
  import {
49
56
  getBridgeConfig,
50
57
  readBridgesConfig,
@@ -53,71 +60,50 @@ import {
53
60
  resolveChannelMapping,
54
61
  setBridgeConfig,
55
62
  setChannelMapping
56
- } from "./chunk-MG3Z34YE.js";
63
+ } from "./chunk-7XDOTN4R.js";
57
64
  import {
58
- MindStartupError,
59
- RestartTracker,
60
- RotatingLog,
61
65
  SESSION_MAX_AGE,
66
+ announceSprout,
62
67
  announceToSystem,
63
68
  authMiddleware,
64
69
  backfillSystemChannelMembers,
65
- checkHealth,
66
- checkStaleSend,
67
70
  cleanExpiredSessions,
68
71
  createSession,
69
72
  deleteSession,
70
73
  deleteSystemsConfig,
71
74
  deliverMessage,
72
- deliverSproutedNotice,
73
75
  ensureSystemChannel,
74
- formatHoldNotice,
75
- generateSystemFallbackReply,
76
76
  getAllDiscoveredExtensions,
77
77
  getAllDiscoveredExtensionsDetailed,
78
- getDeliveryManager,
79
78
  getExtensionStandardSkills,
80
79
  getLoadedExtensions,
81
- getMindManager,
82
- getScheduler,
83
80
  getSessionUserId,
84
81
  getSleepManagerIfReady,
85
82
  getTokenBudget,
86
- getTypingMap,
87
- initDeliveryManager,
88
83
  initMailPoller,
89
- initMindManager,
90
- initScheduler,
91
84
  initSleepManager,
92
85
  initTokenBudget,
93
86
  installNpmExtension,
94
87
  invalidateMindUserCache,
95
88
  invalidateSessionCache,
96
- isConversationId,
97
89
  joinSystemChannel,
98
90
  joinSystemChannelForMind,
99
91
  linkToolResultToTurn,
100
92
  loadAllExtensions,
101
93
  notifyExtensionsDaemonStart,
102
94
  notifyExtensionsDaemonStop,
103
- publishTypingForChannels,
104
95
  readSystemsConfig,
105
96
  recordOutbound,
106
97
  requireAdmin,
107
98
  requireAdminOrSystem,
108
99
  requireSelf,
109
- sendSystemMessage,
110
- sendSystemMessageDirect,
111
100
  setExtensionEnabled,
112
- shouldGenerateSystemFallback,
113
101
  startMindFull,
114
102
  stopMindFull,
115
- syncProviderToMinds,
116
- tryGetMindManager,
117
103
  uninstallNpmExtension,
118
104
  writeSystemsConfig
119
- } from "./chunk-QQBOI3QA.js";
120
- import "./chunk-VIUH5CHQ.js";
105
+ } from "./chunk-EQKSRVDE.js";
106
+ import "./chunk-SFRYRW37.js";
121
107
  import {
122
108
  applyInitFiles,
123
109
  applyTemplateHomeFiles,
@@ -127,28 +113,52 @@ import {
127
113
  isKnownTemplate,
128
114
  listFiles
129
115
  } from "./chunk-RRZ4RQQU.js";
116
+ import {
117
+ getScheduler,
118
+ initScheduler
119
+ } from "./chunk-7FDWPQCU.js";
120
+ import {
121
+ checkStaleSend,
122
+ formatHoldNotice,
123
+ getDeliveryManager,
124
+ getTypingMap,
125
+ initDeliveryManager,
126
+ isConversationId,
127
+ publishTypingForChannels
128
+ } from "./chunk-JI67D7O2.js";
129
+ import {
130
+ MindStartupError,
131
+ RestartTracker,
132
+ RotatingLog,
133
+ checkHealth,
134
+ getMindManager,
135
+ initMindManager,
136
+ syncProviderToMinds,
137
+ tryGetMindManager
138
+ } from "./chunk-ALIVXDM4.js";
130
139
  import {
131
140
  extractTextContent
132
- } from "./chunk-EFT2OALG.js";
141
+ } from "./chunk-6BM2I736.js";
133
142
  import {
143
+ SYSTEM_MIND,
134
144
  assignSession,
135
145
  completeOrphanedTurns,
136
146
  completeTurn,
137
147
  createTurn,
138
148
  getActiveTurnId,
149
+ getPeriodKey,
139
150
  getToolUseEventId,
151
+ getUtcTimeRange,
140
152
  initSummarizer,
141
153
  isoWeekKeyForDateStr,
142
154
  markErrored,
143
- publish as publish3,
144
- subscribe as subscribe3,
145
- subscribeAll,
155
+ parseUtcDateTime,
146
156
  summarizeOrphanedTurns,
147
157
  summarizeTurn,
148
158
  takeErrored,
149
159
  trackToolUse,
150
160
  utcDateTimeStr
151
- } from "./chunk-K3JOLGWF.js";
161
+ } from "./chunk-7DXLIYPY.js";
152
162
  import {
153
163
  SEED_SKILLS,
154
164
  STANDARD_SKILLS,
@@ -170,7 +180,7 @@ import {
170
180
  syncBuiltinSkills,
171
181
  uninstallSkill,
172
182
  updateSkill
173
- } from "./chunk-GWVFDUNB.js";
183
+ } from "./chunk-TGJHGVBZ.js";
174
184
  import {
175
185
  approveUser,
176
186
  changePassword,
@@ -178,6 +188,7 @@ import {
178
188
  createUser,
179
189
  deleteMindUser as deleteMindUser2,
180
190
  deleteUser,
191
+ getDisplayNames,
181
192
  getOrCreateMindUser,
182
193
  getOrCreateSystemUser,
183
194
  getUser,
@@ -188,8 +199,39 @@ import {
188
199
  setUserRole,
189
200
  syncMindProfile,
190
201
  updateUserProfile,
191
- verifyUser
192
- } from "./chunk-4DSGN4FL.js";
202
+ verifyUser,
203
+ withSenderDisplayNames
204
+ } from "./chunk-UC6BNMGT.js";
205
+ import {
206
+ captureReflection,
207
+ clearDeliveredEvents,
208
+ deliverEvent,
209
+ drainEvents,
210
+ eventLabel,
211
+ formatEvents,
212
+ latestEvent,
213
+ latestFailureEvent,
214
+ listEvents,
215
+ parseMeta,
216
+ recordNotice
217
+ } from "./chunk-7OZWKHA3.js";
218
+ import {
219
+ publish as publish2,
220
+ subscribe as subscribe2,
221
+ subscribeAll
222
+ } from "./chunk-LNNI3MS4.js";
223
+ import {
224
+ PROMPT_DEFAULTS,
225
+ PROMPT_KEYS,
226
+ getMindPromptDefaults,
227
+ getPrompt,
228
+ getPromptIfCustom,
229
+ substitute
230
+ } from "./chunk-5TPC4KCV.js";
231
+ import {
232
+ isSandboxEnabled,
233
+ wrapForSandbox
234
+ } from "./chunk-ZUG76D3P.js";
193
235
  import {
194
236
  addMessage,
195
237
  createChannel,
@@ -203,6 +245,8 @@ import {
203
245
  getChannelName,
204
246
  getChannelSettings,
205
247
  getConversation,
248
+ getConversationWithParticipants,
249
+ getLastMessageBySender,
206
250
  getMessages,
207
251
  getMessagesPaginated,
208
252
  getParticipants,
@@ -218,39 +262,27 @@ import {
218
262
  listConversationsForUser,
219
263
  listConversationsWithParticipants,
220
264
  markConversationRead,
221
- publish as publish2,
265
+ publish as publish3,
222
266
  setConversationPrivate,
223
- subscribe as subscribe2,
267
+ subscribe as subscribe3,
268
+ subscribeParticipantAdded,
224
269
  updateChannelSettings
225
- } from "./chunk-VNRSXEBF.js";
226
- import {
227
- PROMPT_DEFAULTS,
228
- PROMPT_KEYS,
229
- getMindPromptDefaults,
230
- getPrompt,
231
- getPromptIfCustom,
232
- substitute
233
- } from "./chunk-JPGHSXSV.js";
234
- import {
235
- isSandboxEnabled,
236
- wrapForSandbox
237
- } from "./chunk-JCWB2ITW.js";
238
- import {
239
- clearDeliveredNotices,
240
- drainNotices,
241
- formatNotices,
242
- latestFailureNotice,
243
- latestNotice,
244
- recordNotice
245
- } from "./chunk-XTEKINWG.js";
270
+ } from "./chunk-574M6I7F.js";
246
271
  import {
247
272
  getActiveMinds,
248
273
  onMindEvent,
249
274
  stopAll
250
- } from "./chunk-ZX46PGCC.js";
275
+ } from "./chunk-E5S3ILVA.js";
276
+ import {
277
+ broadcast,
278
+ getEventsSince,
279
+ nextEventId,
280
+ publish,
281
+ subscribe
282
+ } from "./chunk-3BAFMGCJ.js";
251
283
  import {
252
284
  normalizeAvatar
253
- } from "./chunk-FBBS4BVR.js";
285
+ } from "./chunk-X67VT4QO.js";
254
286
  import {
255
287
  safeResolveWithinBase
256
288
  } from "./chunk-DW4TRDOZ.js";
@@ -258,9 +290,10 @@ import {
258
290
  readVoluteConfig,
259
291
  writeVoluteConfig
260
292
  } from "./chunk-WCFSBH33.js";
261
- import "./chunk-HCYQRME2.js";
293
+ import "./chunk-5FVANDON.js";
262
294
  import {
263
295
  chownMindDir,
296
+ chownMindFile,
264
297
  createMindUser,
265
298
  deleteMindUser,
266
299
  ensureVoluteGroup,
@@ -269,14 +302,14 @@ import {
269
302
  isIsolationEnabled,
270
303
  resolveVoluteBin,
271
304
  wrapForIsolation
272
- } from "./chunk-DVN6DNMI.js";
305
+ } from "./chunk-AJWFDIEY.js";
273
306
  import {
274
307
  loadMergedEnv,
275
308
  mindEnvPath,
276
309
  readEnv,
277
310
  sharedEnvPath,
278
311
  writeEnv
279
- } from "./chunk-PY24ZL62.js";
312
+ } from "./chunk-7UWXAJMK.js";
280
313
  import {
281
314
  addCustomModel,
282
315
  aiCompleteUtility,
@@ -287,31 +320,21 @@ import {
287
320
  getCustomModels,
288
321
  getEnabledModels,
289
322
  getUtilityModel,
323
+ isAiConfigured,
290
324
  missingCredentialWarning,
291
325
  qualifyModelId,
292
326
  removeAiConfig,
293
327
  removeCustomModel,
294
328
  removeProviderConfig,
295
329
  resolveApiKey,
330
+ resolveOAuthCredentials,
296
331
  resolveTemplate,
297
332
  saveProviderConfig,
298
333
  setEnabledModels,
299
334
  setProviderRefreshHook,
300
335
  setUtilityModel,
301
336
  unqualifyModelId
302
- } from "./chunk-YIJGKHIS.js";
303
- import {
304
- isSetupComplete,
305
- readGlobalConfig,
306
- writeGlobalConfig
307
- } from "./chunk-NJHN6IRB.js";
308
- import {
309
- broadcast,
310
- getEventsSince,
311
- nextEventId,
312
- publish,
313
- subscribe
314
- } from "./chunk-3B427GUK.js";
337
+ } from "./chunk-632FJ23O.js";
315
338
  import {
316
339
  logBuffer,
317
340
  logger_default
@@ -320,7 +343,6 @@ import {
320
343
  buildVoluteSlug
321
344
  } from "./chunk-LGNUFVMR.js";
322
345
  import {
323
- SPIRIT_NAME,
324
346
  addMind,
325
347
  addVariant,
326
348
  countCappedMinds,
@@ -331,10 +353,14 @@ import {
331
353
  findVariants,
332
354
  getBaseName,
333
355
  getDb,
356
+ getSpiritName,
357
+ isSetupComplete,
358
+ isSpiritName,
334
359
  mindDir,
335
360
  mindTmpDir,
336
361
  nextPort,
337
362
  readAllMinds,
363
+ readGlobalConfig,
338
364
  readRegistry,
339
365
  removeMind,
340
366
  resolveMindDir,
@@ -344,20 +370,23 @@ import {
344
370
  setMindTemplateHash,
345
371
  stateDir,
346
372
  validateMindName,
373
+ validateSpiritName,
347
374
  voluteHome,
348
- voluteSystemDir
349
- } from "./chunk-RSBL3FAD.js";
375
+ voluteSystemDir,
376
+ writeGlobalConfig
377
+ } from "./chunk-CAIYV33P.js";
350
378
  import {
351
379
  activity,
352
380
  channels,
353
381
  conversationParticipants,
354
382
  conversations,
383
+ messages,
355
384
  mindHistory,
356
385
  summaries,
357
386
  systemPrompts,
358
387
  turns,
359
388
  users
360
- } from "./chunk-P4LHVRNE.js";
389
+ } from "./chunk-5IIVOWSP.js";
361
390
  import "./chunk-K3NQKI34.js";
362
391
 
363
392
  // packages/daemon/src/daemon.ts
@@ -454,7 +483,7 @@ var BackupManager = class {
454
483
  this.consecutiveFailures = 0;
455
484
  }
456
485
  /**
457
- * Track consecutive scheduled-backup failures and actively notify the operator
486
+ * Track consecutive scheduled-backup failures and actively notify the host
458
487
  * on the 1st and then every Nth. Returns whether it notified (for tests).
459
488
  * Notification failures are swallowed — surfacing a backup failure must never
460
489
  * take the daemon's backup loop down with it.
@@ -468,24 +497,21 @@ var BackupManager = class {
468
497
  try {
469
498
  await publish({
470
499
  type: "backup_failed",
471
- mind: SPIRIT_NAME,
500
+ mind: getSpiritName(),
472
501
  summary,
473
502
  metadata: { consecutiveFailures: n, error: reason }
474
503
  });
475
504
  } catch (pubErr) {
476
505
  blog.error("failed to publish backup-failure activity", logger_default.errorData(pubErr));
477
506
  }
478
- try {
479
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
480
- await sendSystemMessage2(
481
- SPIRIT_NAME,
482
- `${summary}
507
+ const { deliverEvent: deliverEvent2 } = await import("./system-events-A6IGCEC3.js");
508
+ await deliverEvent2(getSpiritName(), {
509
+ type: "notice",
510
+ meta: { subtype: "backup" },
511
+ body: `${summary}
483
512
 
484
513
  Backups are not being saved. Check \`volute backup status\` and the Backups settings tab.`
485
- );
486
- } catch (msgErr) {
487
- blog.error("failed to send backup-failure system message", logger_default.errorData(msgErr));
488
- }
514
+ });
489
515
  return true;
490
516
  }
491
517
  };
@@ -852,6 +878,240 @@ import { z } from "zod";
852
878
 
853
879
  // packages/daemon/src/lib/services/imagegen.ts
854
880
  import Replicate from "replicate";
881
+
882
+ // packages/daemon/src/lib/services/imagegen-codex.ts
883
+ var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
884
+ var HOST_MODEL = "gpt-5.4-mini";
885
+ var CODEX_NOT_ENTITLED_SENTINEL = "Tool choice 'image_generation' not found in 'tools' parameter.";
886
+ var CodexNotEntitledError = class extends Error {
887
+ constructor() {
888
+ super(CODEX_NOT_ENTITLED_SENTINEL);
889
+ this.name = "CodexNotEntitledError";
890
+ }
891
+ };
892
+ function accountIdFromToken(token) {
893
+ const parts = token.split(".");
894
+ if (parts.length !== 3) return void 0;
895
+ try {
896
+ const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString());
897
+ const auth = claims["https://api.openai.com/auth"];
898
+ return auth?.chatgpt_account_id;
899
+ } catch {
900
+ return void 0;
901
+ }
902
+ }
903
+ function codexHeaders(token) {
904
+ const headers = {
905
+ Authorization: `Bearer ${token}`,
906
+ "Content-Type": "application/json",
907
+ "OpenAI-Beta": "responses=experimental",
908
+ originator: "codex_cli_rs",
909
+ session_id: crypto.randomUUID()
910
+ };
911
+ const accountId = accountIdFromToken(token);
912
+ if (accountId) headers["chatgpt-account-id"] = accountId;
913
+ return headers;
914
+ }
915
+ function buildCodexRequest(imageModel, prompt) {
916
+ return {
917
+ model: HOST_MODEL,
918
+ instructions: "Use the image_generation tool immediately to render the request. Never ask questions.",
919
+ input: [{ type: "message", role: "user", content: [{ type: "input_text", text: prompt }] }],
920
+ tools: [
921
+ {
922
+ type: "image_generation",
923
+ model: imageModel,
924
+ size: "1024x1024",
925
+ quality: "auto",
926
+ output_format: "png",
927
+ partial_images: 1
928
+ }
929
+ ],
930
+ tool_choice: { type: "image_generation" },
931
+ stream: true,
932
+ store: false
933
+ };
934
+ }
935
+ async function parseCodexImageStream(body, signal) {
936
+ const reader = body.getReader();
937
+ const decoder = new TextDecoder();
938
+ let buf = "";
939
+ let best = "";
940
+ let completed = false;
941
+ const consumeLine = (line) => {
942
+ if (!line.startsWith("data:")) return;
943
+ const payload = line.slice(5).trim();
944
+ if (!payload || payload === "[DONE]") return;
945
+ let evt;
946
+ try {
947
+ evt = JSON.parse(payload);
948
+ } catch {
949
+ return;
950
+ }
951
+ if (evt.type === "response.completed") completed = true;
952
+ const candidate = evt.partial_image_b64 ?? evt.result ?? evt.item?.result ?? void 0;
953
+ if (candidate && candidate.length > best.length) best = candidate;
954
+ };
955
+ for (; ; ) {
956
+ if (signal?.aborted) throw new Error("Codex stream aborted");
957
+ const { value, done } = await reader.read();
958
+ if (done) break;
959
+ buf += decoder.decode(value, { stream: true });
960
+ for (let nl = buf.indexOf("\n"); nl !== -1; nl = buf.indexOf("\n")) {
961
+ consumeLine(buf.slice(0, nl));
962
+ buf = buf.slice(nl + 1);
963
+ }
964
+ }
965
+ consumeLine(buf);
966
+ if (!completed) {
967
+ throw new Error("Codex stream closed before completion \u2014 only a partial image was received");
968
+ }
969
+ if (!best) throw new Error("Codex stream produced no image");
970
+ return Buffer.from(best, "base64");
971
+ }
972
+ async function codexGenerate(model, prompt, cred, signal) {
973
+ const res = await fetch(CODEX_RESPONSES_URL, {
974
+ method: "POST",
975
+ headers: codexHeaders(cred.token),
976
+ body: JSON.stringify(buildCodexRequest(model, prompt)),
977
+ signal
978
+ });
979
+ if (res.status === 400) {
980
+ const errBody = await res.text().catch(() => "");
981
+ if (errBody.includes(CODEX_NOT_ENTITLED_SENTINEL)) throw new CodexNotEntitledError();
982
+ throw new Error(`Codex image generation failed (400): ${errBody.slice(0, 200)}`);
983
+ }
984
+ if (!res.ok || !res.body) {
985
+ const errBody = await res.text().catch(() => `HTTP ${res.status}`);
986
+ throw new Error(`Codex image generation failed (${res.status}): ${errBody.slice(0, 200)}`);
987
+ }
988
+ return parseCodexImageStream(res.body, signal);
989
+ }
990
+ async function probeCodexEntitlement(cred) {
991
+ const controller = new AbortController();
992
+ let res;
993
+ try {
994
+ res = await fetch(CODEX_RESPONSES_URL, {
995
+ method: "POST",
996
+ headers: codexHeaders(cred.token),
997
+ body: JSON.stringify(buildCodexRequest("gpt-image-2", "entitlement probe")),
998
+ signal: controller.signal
999
+ });
1000
+ } catch (err) {
1001
+ if (err instanceof Error && err.name === "AbortError") return "entitled";
1002
+ throw err;
1003
+ }
1004
+ if (res.status === 400) {
1005
+ const body = await res.text().catch(() => "");
1006
+ if (body.includes(CODEX_NOT_ENTITLED_SENTINEL)) return "not_entitled";
1007
+ throw new Error(`Codex entitlement probe failed (400): ${body.slice(0, 200)}`);
1008
+ }
1009
+ if (!res.ok || !res.body) {
1010
+ throw new Error(`Codex entitlement probe failed (${res.status})`);
1011
+ }
1012
+ const reader = res.body.getReader();
1013
+ const decoder = new TextDecoder();
1014
+ let buf = "";
1015
+ try {
1016
+ for (; ; ) {
1017
+ const { value, done } = await reader.read();
1018
+ if (done) break;
1019
+ buf += decoder.decode(value, { stream: true });
1020
+ if (buf.includes("image_generation_call")) {
1021
+ controller.abort();
1022
+ return "entitled";
1023
+ }
1024
+ }
1025
+ } catch (err) {
1026
+ if (err instanceof Error && err.name === "AbortError") return "entitled";
1027
+ throw err;
1028
+ }
1029
+ return "not_entitled";
1030
+ }
1031
+ async function codexSearch(_query, _cred) {
1032
+ return [
1033
+ {
1034
+ id: "openai-codex:gpt-image-2",
1035
+ name: "gpt-image-2 (ChatGPT subscription)",
1036
+ description: "OpenAI's image model via a ChatGPT/Codex subscription \u2014 no API key required.",
1037
+ owner: "openai"
1038
+ }
1039
+ ];
1040
+ }
1041
+
1042
+ // packages/daemon/src/lib/services/imagegen-xai.ts
1043
+ var xaiLog = logger_default.child("imagegen-xai");
1044
+ var XAI_IMAGES_URL = "https://api.x.ai/v1/images/generations";
1045
+ var XaiNotEntitledError = class extends Error {
1046
+ constructor() {
1047
+ super("xAI plan tier is not entitled to image generation");
1048
+ this.name = "XaiNotEntitledError";
1049
+ }
1050
+ };
1051
+ function xaiApiKeyFallback() {
1052
+ const config2 = readGlobalConfig();
1053
+ return config2.imagegen?.providers?.xai?.apiKey ?? config2.ai?.providers?.xai?.apiKey ?? process.env.XAI_API_KEY ?? void 0;
1054
+ }
1055
+ async function requestXaiImage(model, prompt, token, signal) {
1056
+ const res = await fetch(XAI_IMAGES_URL, {
1057
+ method: "POST",
1058
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
1059
+ body: JSON.stringify({ model, prompt, n: 1, response_format: "b64_json" }),
1060
+ signal
1061
+ });
1062
+ if (!res.ok) {
1063
+ return {
1064
+ ok: false,
1065
+ status: res.status,
1066
+ body: (await res.text().catch(() => "")).slice(0, 200)
1067
+ };
1068
+ }
1069
+ const data = await res.json();
1070
+ const item = data.data?.[0];
1071
+ if (item?.b64_json) return { ok: true, buffer: Buffer.from(item.b64_json, "base64") };
1072
+ if (item?.url) {
1073
+ const imgRes = await fetch(item.url, { signal });
1074
+ if (!imgRes.ok) throw new Error(`Failed to fetch xAI image URL: ${imgRes.status}`);
1075
+ return { ok: true, buffer: Buffer.from(await imgRes.arrayBuffer()) };
1076
+ }
1077
+ throw new Error("xAI returned no image");
1078
+ }
1079
+ async function xaiGenerate(model, prompt, cred, signal) {
1080
+ const first = await requestXaiImage(model, prompt, cred.token, signal);
1081
+ if (first.ok) return first.buffer;
1082
+ if (first.status === 403 && cred.kind === "oauth") {
1083
+ const apiKey = xaiApiKeyFallback();
1084
+ if (apiKey) {
1085
+ xaiLog.warn(
1086
+ "xAI OAuth token not entitled for image generation; retrying with XAI_API_KEY (billed to the API account)."
1087
+ );
1088
+ const retry = await requestXaiImage(model, prompt, apiKey, signal);
1089
+ if (retry.ok) return retry.buffer;
1090
+ if (retry.status === 403) throw new XaiNotEntitledError();
1091
+ throw new Error(`xAI image generation failed (${retry.status}): ${retry.body}`);
1092
+ }
1093
+ throw new XaiNotEntitledError();
1094
+ }
1095
+ throw new Error(`xAI image generation failed (${first.status}): ${first.body}`);
1096
+ }
1097
+ async function xaiSearch(_query, _cred) {
1098
+ return [
1099
+ {
1100
+ id: "xai:grok-imagine-image",
1101
+ name: "Grok Imagine",
1102
+ description: "xAI Grok Imagine image model ($0.02/image via API key).",
1103
+ owner: "xai"
1104
+ },
1105
+ {
1106
+ id: "xai:grok-imagine-image-quality",
1107
+ name: "Grok Imagine (quality)",
1108
+ description: "Higher-quality Grok Imagine image model ($0.05/image via API key).",
1109
+ owner: "xai"
1110
+ }
1111
+ ];
1112
+ }
1113
+
1114
+ // packages/daemon/src/lib/services/imagegen.ts
855
1115
  var PROVIDERS = {
856
1116
  replicate: {
857
1117
  envVar: "REPLICATE_API_TOKEN",
@@ -862,17 +1122,33 @@ var PROVIDERS = {
862
1122
  envVar: "OPENROUTER_API_KEY",
863
1123
  generate: openrouterGenerate,
864
1124
  search: openrouterSearch
1125
+ },
1126
+ "openai-codex": {
1127
+ // Subscription-only: no env var API key path. Credentials come from the
1128
+ // openai-codex AI provider's OAuth (chat/image unification).
1129
+ aiProviderId: "openai-codex",
1130
+ generate: codexGenerate,
1131
+ search: codexSearch
1132
+ },
1133
+ xai: {
1134
+ // Both credential kinds hit the same endpoint: subscription OAuth (preferred)
1135
+ // or a metered XAI_API_KEY. OAuth 403s fall back to the key (see imagegen-xai).
1136
+ envVar: "XAI_API_KEY",
1137
+ aiProviderId: "xai",
1138
+ generate: xaiGenerate,
1139
+ search: xaiSearch
865
1140
  }
866
1141
  };
867
- async function replicateGenerate(model, prompt, apiKey) {
868
- const replicate = new Replicate({ auth: apiKey });
1142
+ async function replicateGenerate(model, prompt, cred, signal) {
1143
+ const replicate = new Replicate({ auth: cred.token });
869
1144
  const output = await replicate.run(model, {
870
- input: { prompt }
1145
+ input: { prompt },
1146
+ signal
871
1147
  });
872
1148
  const file = Array.isArray(output) ? output[0] : output;
873
1149
  if (!file) throw new Error(`Model ${model} returned no output`);
874
1150
  if (typeof file === "string") {
875
- const res = await fetch(file);
1151
+ const res = await fetch(file, { signal });
876
1152
  if (!res.ok) throw new Error(`Failed to fetch image from URL: ${res.status}`);
877
1153
  return Buffer.from(await res.arrayBuffer());
878
1154
  }
@@ -882,8 +1158,8 @@ async function replicateGenerate(model, prompt, apiKey) {
882
1158
  }
883
1159
  return Buffer.concat(chunks);
884
1160
  }
885
- async function replicateSearch(query, apiKey) {
886
- const replicate = new Replicate({ auth: apiKey });
1161
+ async function replicateSearch(query, cred) {
1162
+ const replicate = new Replicate({ auth: cred.token });
887
1163
  const response = await replicate.models.search(query || "text to image");
888
1164
  return response.results.slice(0, 20).map((m) => ({
889
1165
  id: `replicate:${m.owner}/${m.name}`,
@@ -892,18 +1168,19 @@ async function replicateSearch(query, apiKey) {
892
1168
  owner: m.owner
893
1169
  }));
894
1170
  }
895
- async function openrouterGenerate(model, prompt, apiKey) {
1171
+ async function openrouterGenerate(model, prompt, cred, signal) {
896
1172
  const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
897
1173
  method: "POST",
898
1174
  headers: {
899
1175
  "Content-Type": "application/json",
900
- Authorization: `Bearer ${apiKey}`
1176
+ Authorization: `Bearer ${cred.token}`
901
1177
  },
902
1178
  body: JSON.stringify({
903
1179
  model,
904
1180
  messages: [{ role: "user", content: prompt }],
905
1181
  modalities: ["image", "text"]
906
- })
1182
+ }),
1183
+ signal
907
1184
  });
908
1185
  if (!res.ok) {
909
1186
  const body = await res.text().catch(() => `HTTP ${res.status}`);
@@ -916,11 +1193,11 @@ async function openrouterGenerate(model, prompt, apiKey) {
916
1193
  if (match) {
917
1194
  return Buffer.from(match[1], "base64");
918
1195
  }
919
- const imgRes = await fetch(imageUrl);
1196
+ const imgRes = await fetch(imageUrl, { signal });
920
1197
  if (!imgRes.ok) throw new Error(`Failed to fetch image from URL: ${imgRes.status}`);
921
1198
  return Buffer.from(await imgRes.arrayBuffer());
922
1199
  }
923
- async function openrouterSearch(query, _apiKey) {
1200
+ async function openrouterSearch(query, _cred) {
924
1201
  const res = await fetch("https://openrouter.ai/api/v1/models?output_modalities=image");
925
1202
  if (!res.ok) throw new Error(`OpenRouter model list failed: ${res.status}`);
926
1203
  const data = await res.json();
@@ -978,23 +1255,33 @@ function getConfiguredProviders2() {
978
1255
  const config2 = readGlobalConfig();
979
1256
  const ig = config2.imagegen ?? null;
980
1257
  const aiProviders = config2.ai?.providers;
981
- return Object.entries(PROVIDERS).map(([id, { envVar }]) => {
1258
+ return Object.entries(PROVIDERS).map(([id, { envVar, aiProviderId }]) => {
1259
+ const aiId = aiProviderId ?? id;
982
1260
  const providerConfig = ig?.providers?.[id];
983
- if (providerConfig?.apiKey) return { id, configured: true, authMethod: "api_key" };
984
- if (aiProviders?.[id]?.apiKey) return { id, configured: true, authMethod: "api_key" };
985
- if (process.env[envVar]) return { id, configured: true, authMethod: "env_var" };
986
- return { id, configured: false, authMethod: null };
1261
+ const entitlement = ig?.entitlements?.[id];
1262
+ let base;
1263
+ if (providerConfig?.apiKey) base = { configured: true, authMethod: "api_key" };
1264
+ else if (aiProviders?.[aiId]?.oauth) base = { configured: true, authMethod: "oauth" };
1265
+ else if (aiProviders?.[aiId]?.apiKey) base = { configured: true, authMethod: "api_key" };
1266
+ else if (envVar && process.env[envVar]) base = { configured: true, authMethod: "env_var" };
1267
+ else base = { configured: false, authMethod: null };
1268
+ return { id, ...base, entitlement };
987
1269
  });
988
1270
  }
989
- function resolveApiKey2(providerId) {
1271
+ async function resolveCredential(providerId) {
990
1272
  const provider = PROVIDERS[providerId];
991
1273
  if (!provider) return void 0;
1274
+ const aiProviderId = provider.aiProviderId ?? providerId;
992
1275
  const config2 = readGlobalConfig();
993
1276
  const configKey = config2.imagegen?.providers?.[providerId]?.apiKey;
994
- if (configKey) return configKey;
995
- const aiKey = config2.ai?.providers?.[providerId]?.apiKey;
996
- if (aiKey) return aiKey;
997
- return process.env[provider.envVar] || void 0;
1277
+ if (configKey) return { kind: "api_key", token: configKey };
1278
+ const oauth = await resolveOAuthCredentials(aiProviderId);
1279
+ if (oauth) return { kind: "oauth", token: oauth.access };
1280
+ const aiKey = config2.ai?.providers?.[aiProviderId]?.apiKey;
1281
+ if (aiKey) return { kind: "api_key", token: aiKey };
1282
+ const envKey = provider.envVar ? process.env[provider.envVar] : void 0;
1283
+ if (envKey) return { kind: "api_key", token: envKey };
1284
+ return void 0;
998
1285
  }
999
1286
  function getEnabledModels2() {
1000
1287
  return getImagegenConfig()?.models ?? [];
@@ -1023,26 +1310,74 @@ async function searchModels(query, provider) {
1023
1310
  if (provider) {
1024
1311
  const providerDef = PROVIDERS[provider];
1025
1312
  if (!providerDef) throw new Error(`Unknown imagegen provider: ${provider}`);
1026
- const apiKey = resolveApiKey2(provider);
1027
- if (!apiKey) throw new Error(`No ${provider} API key configured`);
1028
- return providerDef.search(query || "text to image", apiKey);
1313
+ const cred = await resolveCredential(provider);
1314
+ if (!cred) throw new Error(`No ${provider} credentials configured`);
1315
+ return providerDef.search(query || "text to image", cred);
1029
1316
  }
1030
1317
  const searches = [];
1031
1318
  for (const [id, def] of Object.entries(PROVIDERS)) {
1032
- const apiKey = resolveApiKey2(id);
1033
- if (!apiKey) continue;
1034
- searches.push(def.search(query || "text to image", apiKey).catch(() => []));
1319
+ const cred = await resolveCredential(id);
1320
+ if (!cred) continue;
1321
+ searches.push(def.search(query || "text to image", cred).catch(() => []));
1035
1322
  }
1036
1323
  if (searches.length === 0) {
1037
1324
  throw new Error("No imagegen providers configured");
1038
1325
  }
1039
1326
  return (await Promise.all(searches)).flat();
1040
1327
  }
1041
- async function generateImage(model, prompt) {
1328
+ var NOT_ENTITLED_MESSAGE = {
1329
+ "openai-codex": "Your ChatGPT plan doesn't include Codex's hosted image tool. Ask an admin to configure an OpenAI API key or another image provider.",
1330
+ xai: "Your xAI plan tier isn't allowlisted for image generation (SuperGrok Heavy required). Ask an admin to set XAI_API_KEY to use Grok Imagine at $0.02/image."
1331
+ };
1332
+ var ENTITLEMENT_PROVIDERS = /* @__PURE__ */ new Set(["openai-codex", "xai"]);
1333
+ function setEntitlement(providerId, state, reason) {
1334
+ const entitlement = state === "not_entitled" ? { state, reason: reason ?? "not entitled", checkedAt: Date.now() } : { state, checkedAt: Date.now() };
1335
+ updateImagegenConfig((ig) => {
1336
+ ig.entitlements = ig.entitlements ?? {};
1337
+ ig.entitlements[providerId] = entitlement;
1338
+ });
1339
+ return entitlement;
1340
+ }
1341
+ async function probeEntitlement(providerId) {
1342
+ if (!ENTITLEMENT_PROVIDERS.has(providerId)) {
1343
+ return setEntitlement(providerId, "entitled");
1344
+ }
1345
+ const cred = await resolveCredential(providerId);
1346
+ if (!cred) throw new Error(`No ${providerId} credentials configured`);
1347
+ if (providerId === "openai-codex") {
1348
+ const state = await probeCodexEntitlement(cred);
1349
+ return setEntitlement(
1350
+ providerId,
1351
+ state,
1352
+ state === "not_entitled" ? NOT_ENTITLED_MESSAGE[providerId] : void 0
1353
+ );
1354
+ }
1355
+ try {
1356
+ await xaiGenerate("grok-imagine-image", "entitlement probe", cred);
1357
+ return setEntitlement(providerId, "entitled");
1358
+ } catch (err) {
1359
+ if (err instanceof XaiNotEntitledError) {
1360
+ return setEntitlement(providerId, "not_entitled", NOT_ENTITLED_MESSAGE[providerId]);
1361
+ }
1362
+ throw err;
1363
+ }
1364
+ }
1365
+ async function generateImage(model, prompt, signal) {
1042
1366
  const { provider: providerId, model: modelId } = parseModelId(model);
1043
- const apiKey = resolveApiKey2(providerId);
1044
- if (!apiKey) throw new Error(`No ${providerId} API key configured`);
1045
- return PROVIDERS[providerId].generate(modelId, prompt, apiKey);
1367
+ const cred = await resolveCredential(providerId);
1368
+ if (!cred) throw new Error(`No ${providerId} credentials configured`);
1369
+ try {
1370
+ const buf = await PROVIDERS[providerId].generate(modelId, prompt, cred, signal);
1371
+ if (ENTITLEMENT_PROVIDERS.has(providerId)) setEntitlement(providerId, "entitled");
1372
+ return buf;
1373
+ } catch (err) {
1374
+ if (err instanceof CodexNotEntitledError || err instanceof XaiNotEntitledError) {
1375
+ const message = NOT_ENTITLED_MESSAGE[providerId] ?? err.message;
1376
+ setEntitlement(providerId, "not_entitled", message);
1377
+ throw new Error(message);
1378
+ }
1379
+ throw err;
1380
+ }
1046
1381
  }
1047
1382
 
1048
1383
  // packages/daemon/src/web/api/system.ts
@@ -1079,7 +1414,12 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1079
1414
  return c.json({
1080
1415
  system: config2?.system ?? null,
1081
1416
  name: globalConfig.name ?? null,
1082
- timezone
1417
+ spiritName: getSpiritName(),
1418
+ timezone,
1419
+ // Whether any AI model is available for minds to think with. A providerless
1420
+ // system spawns every mind mute, so the dashboard surfaces a banner (#606).
1421
+ // Boolean only — no provider details or secrets are exposed here.
1422
+ aiConfigured: isAiConfigured()
1083
1423
  });
1084
1424
  }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), async (c) => {
1085
1425
  const { name } = c.req.valid("json");
@@ -1088,7 +1428,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1088
1428
  config2.name = name.trim() || void 0;
1089
1429
  writeGlobalConfig(config2);
1090
1430
  if (config2.name !== previousName) {
1091
- const { notifySpiritSystemChange } = await import("./spirit-SSJMYC6A.js");
1431
+ const { notifySpiritSystemChange } = await import("./spirit-IL5EMWYZ.js");
1092
1432
  await notifySpiritSystemChange();
1093
1433
  }
1094
1434
  return c.json({ name: config2.name ?? null });
@@ -1246,10 +1586,19 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1246
1586
  return c.json({ error: err instanceof Error ? err.message : "Failed to remove" }, 400);
1247
1587
  }
1248
1588
  return c.json({ ok: true });
1589
+ }).post("/imagegen/providers/:id/probe", requireAdmin, async (c) => {
1590
+ try {
1591
+ const entitlement = await probeEntitlement(c.req.param("id"));
1592
+ return c.json(entitlement);
1593
+ } catch (err) {
1594
+ return c.json({ error: err instanceof Error ? err.message : "Probe failed" }, 400);
1595
+ }
1249
1596
  }).get("/imagegen/models", requireAdmin, (c) => {
1250
1597
  const models = getEnabledModels2();
1251
1598
  const defaultModel = getDefaultModel();
1252
1599
  return c.json({ models, defaultModel: defaultModel ?? null });
1600
+ }).get("/imagegen/default-model", (c) => {
1601
+ return c.json({ defaultModel: getDefaultModel() ?? null });
1253
1602
  }).put(
1254
1603
  "/imagegen/models",
1255
1604
  requireAdmin,
@@ -1455,7 +1804,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1455
1804
  instructions: `Enter code: ${info.userCode}`
1456
1805
  });
1457
1806
  },
1458
- onSelect: async (_prompt) => void 0,
1807
+ onSelect: selectLoginMethod,
1459
1808
  onPrompt: async (_prompt) => {
1460
1809
  if (promptPromise) {
1461
1810
  const existing = oauthFlows.get(flowId);
@@ -1559,7 +1908,7 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1559
1908
  message: z.string().optional(),
1560
1909
  messages: z.array(z.string().min(1)).optional(),
1561
1910
  script: z.string().optional(),
1562
- session: z.string().optional(),
1911
+ thread: z.string().optional(),
1563
1912
  enabled: z.boolean(),
1564
1913
  whileSleeping: z.enum(["skip", "queue", "trigger-wake"]).optional()
1565
1914
  })
@@ -1574,6 +1923,9 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1574
1923
  return c.json({ ok: true });
1575
1924
  }
1576
1925
  );
1926
+ async function selectLoginMethod(prompt) {
1927
+ return prompt.options[0]?.id;
1928
+ }
1577
1929
  var oauthFlows = /* @__PURE__ */ new Map();
1578
1930
  function cleanupOAuthFlows() {
1579
1931
  for (const [id, flow] of oauthFlows) {
@@ -1672,7 +2024,7 @@ function stopApiKeyRefresh() {
1672
2024
  var system_default = app;
1673
2025
 
1674
2026
  // packages/daemon/src/web/app.ts
1675
- import { Hono as Hono29 } from "hono";
2027
+ import { Hono as Hono30 } from "hono";
1676
2028
  import { bodyLimit } from "hono/body-limit";
1677
2029
  import { cors } from "hono/cors";
1678
2030
  import { csrf } from "hono/csrf";
@@ -1721,7 +2073,7 @@ var app2 = new Hono2().get("/events", async (c) => {
1721
2073
  });
1722
2074
  cleanups.push(unsubActivity);
1723
2075
  for (const conv of conversations2) {
1724
- const unsubConv = subscribe2(conv.id, (event) => {
2076
+ const unsubConv = subscribe3(conv.id, (event) => {
1725
2077
  stream.writeSSE({
1726
2078
  data: JSON.stringify({ event: "conversation", conversationId: conv.id, ...event })
1727
2079
  }).catch((err) => {
@@ -1884,7 +2236,7 @@ var admin = new Hono3().use(authMiddleware).get("/users", async (c) => {
1884
2236
  await getOrCreateMindUser(mind.name);
1885
2237
  }
1886
2238
  const type = c.req.query("type");
1887
- if (type === "brain" || type === "mind") {
2239
+ if (type === "human" || type === "mind") {
1888
2240
  return c.json(await listUsersByType(type));
1889
2241
  }
1890
2242
  return c.json(await listUsers());
@@ -2208,6 +2560,14 @@ async function fanOutToMinds(opts) {
2208
2560
  const targetMinds = mindParticipants.map((ap) => {
2209
2561
  const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
2210
2562
  if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
2563
+ if (ap.username !== opts.senderName) {
2564
+ logger_default.warn(
2565
+ `fan-out: skipping ${ap.username} (not running) for conversation ${opts.conversationId}`
2566
+ );
2567
+ if (ap.userType === "system") {
2568
+ import("./spirit-availability-YFEGJNJ2.js").then(({ ensureSpiritAvailable: ensureSpiritAvailable2 }) => ensureSpiritAvailable2()).catch((err) => logger_default.warn("fan-out: on-demand spirit start failed", logger_default.errorData(err)));
2569
+ }
2570
+ }
2211
2571
  return null;
2212
2572
  }).filter((n) => n !== null && n !== opts.senderName);
2213
2573
  for (const mindName of targetMinds) {
@@ -2308,7 +2668,7 @@ var app4 = new Hono5().post("/:platform/inbound", zValidator4("json", inboundSch
2308
2668
  }
2309
2669
  const participants = await getParticipants(channel.id);
2310
2670
  if (!participants.some((p) => p.userId === puppet.id)) {
2311
- const { addParticipant } = await import("./conversations-TUM5PJJ4.js");
2671
+ const { addParticipant } = await import("./conversations-S2232Y5I.js");
2312
2672
  await addParticipant(channel.id, puppet.id);
2313
2673
  }
2314
2674
  const contentBlocks = body.content;
@@ -2592,8 +2952,8 @@ async function notifyMind(mindName, message) {
2592
2952
  const entry = await findMind(mindName);
2593
2953
  if (!entry) return;
2594
2954
  try {
2595
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
2596
- await sendSystemMessage2(mindName, message);
2955
+ const { deliverEvent: deliverEvent2 } = await import("./system-events-A6IGCEC3.js");
2956
+ await deliverEvent2(mindName, { type: "file-share", body: message });
2597
2957
  } catch (err) {
2598
2958
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
2599
2959
  }
@@ -2889,14 +3249,222 @@ var app9 = new Hono11().post("/:name/avatar", requireSelf(), async (c) => {
2889
3249
  var files_default = app9;
2890
3250
 
2891
3251
  // packages/daemon/src/web/api/history.ts
2892
- import { and as and3, desc as desc2, eq as eq3, gte, inArray, sql } from "drizzle-orm";
3252
+ import { and as and3, desc as desc2, eq as eq3, gte, inArray, lt as lt2, sql } from "drizzle-orm";
2893
3253
  import { Hono as Hono12 } from "hono";
2894
- var AWAY_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
2895
3254
  async function resolveMindFilter(c) {
2896
3255
  const user = c.get("user");
2897
3256
  const privileged = user.role === "admin" || user.role === "system";
2898
3257
  return privileged ? c.req.query("mind") ?? void 0 : getBaseName(user.username);
2899
3258
  }
3259
+ function eventLabelOf(metadata, channel) {
3260
+ if (metadata) {
3261
+ try {
3262
+ const parsed = JSON.parse(metadata);
3263
+ if (typeof parsed.label === "string" && parsed.label) return parsed.label;
3264
+ } catch {
3265
+ }
3266
+ }
3267
+ return channel.split(":")[1] || "Event";
3268
+ }
3269
+ function getChannelLabel(channel) {
3270
+ const isDM = channel.startsWith("@");
3271
+ const colonIdx = channel.indexOf(":");
3272
+ const raw = colonIdx >= 0 ? channel.substring(colonIdx + 1) : channel;
3273
+ const label = isDM ? raw : raw.startsWith("#") ? raw : `#${raw}`;
3274
+ return { label, type: isDM ? "dm" : "channel" };
3275
+ }
3276
+ async function resolveConversationIds(db, allChannelSlugs, dmSlugMinds) {
3277
+ const channelIdMap = /* @__PURE__ */ new Map();
3278
+ try {
3279
+ if (allChannelSlugs.size > 0) {
3280
+ const channelNames = [...allChannelSlugs].filter((s) => !s.startsWith("@")).map((s) => {
3281
+ let name = s;
3282
+ if (name.startsWith("#")) name = name.slice(1);
3283
+ const colonIdx = name.indexOf(":");
3284
+ if (colonIdx >= 0) name = name.substring(colonIdx + 1);
3285
+ return { slug: s, name };
3286
+ });
3287
+ if (channelNames.length > 0) {
3288
+ const channelRows = await db.select({ conversationId: channels.conversation_id, name: channels.name }).from(channels).where(
3289
+ inArray(
3290
+ channels.name,
3291
+ channelNames.map((c) => c.name)
3292
+ )
3293
+ );
3294
+ const nameToId = new Map(channelRows.map((r) => [r.name, r.conversationId]));
3295
+ for (const { slug, name } of channelNames) {
3296
+ const id = nameToId.get(name);
3297
+ if (id) channelIdMap.set(slug, id);
3298
+ }
3299
+ }
3300
+ const dmSlugs = [...dmSlugMinds.keys()];
3301
+ if (dmSlugs.length > 0) {
3302
+ const targetNames = dmSlugs.map((s) => s.slice(1));
3303
+ const cp2 = db.$with("cp2").as(
3304
+ db.select({
3305
+ conversation_id: conversationParticipants.conversation_id,
3306
+ username: users.username
3307
+ }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id))
3308
+ );
3309
+ const dmRows = await db.with(cp2).select({
3310
+ id: conversations.id,
3311
+ targetUsername: users.username
3312
+ }).from(conversations).innerJoin(
3313
+ conversationParticipants,
3314
+ eq3(conversations.id, conversationParticipants.conversation_id)
3315
+ ).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(and3(eq3(conversations.type, "dm"), inArray(users.username, targetNames)));
3316
+ const dmCandidates = [];
3317
+ for (const row of dmRows) {
3318
+ const slug = `@${row.targetUsername}`;
3319
+ const mindNames = dmSlugMinds.get(slug);
3320
+ if (mindNames && !channelIdMap.has(slug)) {
3321
+ dmCandidates.push({ slug, conversationId: row.id, mindNames: [...mindNames] });
3322
+ }
3323
+ }
3324
+ if (dmCandidates.length > 0) {
3325
+ const candidateConvIds = dmCandidates.map((d) => d.conversationId);
3326
+ const allMindNames = [...new Set(dmCandidates.flatMap((d) => d.mindNames))];
3327
+ const verifyRows = await db.select({
3328
+ conversation_id: conversationParticipants.conversation_id,
3329
+ username: users.username
3330
+ }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(
3331
+ and3(
3332
+ inArray(conversationParticipants.conversation_id, candidateConvIds),
3333
+ inArray(users.username, allMindNames)
3334
+ )
3335
+ );
3336
+ const verifiedConvs = new Set(verifyRows.map((r) => r.conversation_id));
3337
+ for (const candidate of dmCandidates) {
3338
+ if (verifiedConvs.has(candidate.conversationId)) {
3339
+ channelIdMap.set(candidate.slug, candidate.conversationId);
3340
+ }
3341
+ }
3342
+ }
3343
+ }
3344
+ }
3345
+ } catch (err) {
3346
+ logger_default.warn("Failed to resolve channel slugs to conversation IDs", logger_default.errorData(err));
3347
+ }
3348
+ return channelIdMap;
3349
+ }
3350
+ var NOISE_ACTIVITY_TYPES = sql`('mind_started', 'mind_stopped', 'mind_active', 'mind_idle', 'mind_done', 'mind_sleeping', 'mind_waking', 'brain_online', 'brain_offline')`;
3351
+ var PERIODIC_TIERS = /* @__PURE__ */ new Set(["hour", "day", "week", "month"]);
3352
+ async function attachSummaryIcons(db, rows) {
3353
+ const allChannelSlugs = /* @__PURE__ */ new Set();
3354
+ const dmSlugMinds = /* @__PURE__ */ new Map();
3355
+ const pending = [];
3356
+ for (const row of rows) {
3357
+ if (row.mind === "_system" || !PERIODIC_TIERS.has(row.period)) continue;
3358
+ const { start, end } = getUtcTimeRange(row.period_key, row.period);
3359
+ const convCounts = await db.select({ channel: mindHistory.channel, count: sql`count(*)` }).from(mindHistory).where(
3360
+ and3(
3361
+ eq3(mindHistory.mind, row.mind),
3362
+ sql`${mindHistory.type} IN ('inbound', 'outbound')`,
3363
+ gte(mindHistory.created_at, start),
3364
+ lt2(mindHistory.created_at, end)
3365
+ )
3366
+ ).groupBy(mindHistory.channel).limit(50);
3367
+ for (const cc of convCounts) {
3368
+ if (!cc.channel) continue;
3369
+ allChannelSlugs.add(cc.channel);
3370
+ if (cc.channel.startsWith("@")) {
3371
+ let minds = dmSlugMinds.get(cc.channel);
3372
+ if (!minds) {
3373
+ minds = /* @__PURE__ */ new Set();
3374
+ dmSlugMinds.set(cc.channel, minds);
3375
+ }
3376
+ minds.add(row.mind);
3377
+ }
3378
+ }
3379
+ const eventRows = await db.select({ channel: mindHistory.channel, metadata: mindHistory.metadata }).from(mindHistory).where(
3380
+ and3(
3381
+ eq3(mindHistory.mind, row.mind),
3382
+ eq3(mindHistory.type, "event"),
3383
+ gte(mindHistory.created_at, start),
3384
+ lt2(mindHistory.created_at, end)
3385
+ )
3386
+ ).orderBy(mindHistory.created_at, mindHistory.id).limit(2e3);
3387
+ const eventGroups = /* @__PURE__ */ new Map();
3388
+ for (const ev of eventRows) {
3389
+ const label = eventLabelOf(ev.metadata, ev.channel ?? "");
3390
+ eventGroups.set(label, (eventGroups.get(label) ?? 0) + 1);
3391
+ }
3392
+ const actCounts = await db.select({ type: activity.type, count: sql`count(*)` }).from(activity).where(
3393
+ and3(
3394
+ eq3(activity.mind, row.mind),
3395
+ gte(activity.created_at, start),
3396
+ lt2(activity.created_at, end),
3397
+ sql`${activity.type} NOT IN ${NOISE_ACTIVITY_TYPES}`
3398
+ )
3399
+ ).groupBy(activity.type);
3400
+ let actGroups = [];
3401
+ if (actCounts.length > 0) {
3402
+ const actRows = await db.select({
3403
+ id: activity.id,
3404
+ type: activity.type,
3405
+ summary: activity.summary,
3406
+ metadata: activity.metadata,
3407
+ created_at: activity.created_at
3408
+ }).from(activity).where(
3409
+ and3(
3410
+ eq3(activity.mind, row.mind),
3411
+ gte(activity.created_at, start),
3412
+ lt2(activity.created_at, end),
3413
+ sql`${activity.type} NOT IN ${NOISE_ACTIVITY_TYPES}`
3414
+ )
3415
+ ).orderBy(desc2(activity.created_at), desc2(activity.id)).limit(200);
3416
+ const byType = /* @__PURE__ */ new Map();
3417
+ for (const a of actRows) {
3418
+ let metadata = null;
3419
+ if (a.metadata) {
3420
+ try {
3421
+ metadata = JSON.parse(a.metadata);
3422
+ } catch (err) {
3423
+ logger_default.debug(`malformed activity metadata for activity ${a.id}`, logger_default.errorData(err));
3424
+ }
3425
+ }
3426
+ let group = byType.get(a.type);
3427
+ if (!group) {
3428
+ group = {
3429
+ type: a.type,
3430
+ count: 0,
3431
+ ...typeof metadata?.icon === "string" ? { icon: metadata.icon } : {},
3432
+ ...typeof metadata?.color === "string" ? { color: metadata.color } : {},
3433
+ items: []
3434
+ };
3435
+ byType.set(a.type, group);
3436
+ }
3437
+ if (group.items.length < 20) {
3438
+ group.items.push({ id: a.id, summary: a.summary, metadata, created_at: a.created_at });
3439
+ }
3440
+ }
3441
+ const countByType = new Map(actCounts.map((c) => [c.type, c.count]));
3442
+ for (const group of byType.values()) {
3443
+ group.count = countByType.get(group.type) ?? group.items.length;
3444
+ group.items.reverse();
3445
+ }
3446
+ actGroups = [...byType.values()];
3447
+ }
3448
+ pending.push({
3449
+ row,
3450
+ convCounts: convCounts.filter((c) => !!c.channel),
3451
+ events: [...eventGroups.entries()].map(([label, count]) => ({ label, count })),
3452
+ activities: actGroups
3453
+ });
3454
+ }
3455
+ if (pending.length === 0) return;
3456
+ const channelIdMap = await resolveConversationIds(db, allChannelSlugs, dmSlugMinds);
3457
+ for (const p of pending) {
3458
+ p.row.icons = {
3459
+ conversations: p.convCounts.map((cc) => {
3460
+ const { label, type } = getChannelLabel(cc.channel);
3461
+ return { id: channelIdMap.get(cc.channel) ?? cc.channel, label, type, count: cc.count };
3462
+ }),
3463
+ events: p.events,
3464
+ activities: p.activities
3465
+ };
3466
+ }
3467
+ }
2900
3468
  var history = new Hono12().use("*", async (c, next) => {
2901
3469
  c.set("mindFilter", await resolveMindFilter(c));
2902
3470
  await next();
@@ -2929,15 +3497,20 @@ var history = new Hono12().use("*", async (c, next) => {
2929
3497
  channel: mindHistory.channel,
2930
3498
  sender: mindHistory.sender,
2931
3499
  content: mindHistory.content,
3500
+ metadata: mindHistory.metadata,
2932
3501
  turn_id: mindHistory.turn_id,
2933
3502
  created_at: mindHistory.created_at
2934
3503
  }).from(mindHistory).where(
2935
3504
  and3(
2936
3505
  inArray(mindHistory.turn_id, turnIds),
2937
- sql`${mindHistory.type} IN ('inbound', 'outbound')`
3506
+ // `event` rows are system events, not messages. They're fetched here but split off
3507
+ // below into a separate per-turn `events` array — never into `conversations`, so no
3508
+ // surface can render them as chat with a phantom sender.
3509
+ sql`${mindHistory.type} IN ('inbound', 'event', 'outbound')`
2938
3510
  )
2939
3511
  ).orderBy(mindHistory.created_at, mindHistory.id);
2940
3512
  const msgsByTurnChannel = /* @__PURE__ */ new Map();
3513
+ const systemEventsByTurn = /* @__PURE__ */ new Map();
2941
3514
  function addToTurnChannel(turnId, channel, event) {
2942
3515
  let byChannel = msgsByTurnChannel.get(turnId);
2943
3516
  if (!byChannel) {
@@ -2955,6 +3528,17 @@ var history = new Hono12().use("*", async (c, next) => {
2955
3528
  for (const t of turnRows) turnMindMap.set(t.id, t.mind);
2956
3529
  for (const m of historyMsgRows) {
2957
3530
  if (!m.turn_id || !m.channel) continue;
3531
+ if (m.type === "event") {
3532
+ const arr = systemEventsByTurn.get(m.turn_id) ?? [];
3533
+ arr.push({
3534
+ id: m.id,
3535
+ label: eventLabelOf(m.metadata, m.channel),
3536
+ content: m.content,
3537
+ created_at: m.created_at
3538
+ });
3539
+ systemEventsByTurn.set(m.turn_id, arr);
3540
+ continue;
3541
+ }
2958
3542
  const mindName = turnMindMap.get(m.turn_id) ?? m.mind;
2959
3543
  addToTurnChannel(m.turn_id, m.channel, {
2960
3544
  id: m.id,
@@ -2964,13 +3548,6 @@ var history = new Hono12().use("*", async (c, next) => {
2964
3548
  created_at: m.created_at
2965
3549
  });
2966
3550
  }
2967
- function getChannelLabel(channel) {
2968
- const isDM = channel.startsWith("@");
2969
- const colonIdx = channel.indexOf(":");
2970
- const raw = colonIdx >= 0 ? channel.substring(colonIdx + 1) : channel;
2971
- const label = isDM ? raw : raw.startsWith("#") ? raw : `#${raw}`;
2972
- return { label, type: isDM ? "dm" : "channel" };
2973
- }
2974
3551
  const activityRows = await db.select().from(activity).where(inArray(activity.turn_id, turnIds)).orderBy(activity.created_at, activity.id);
2975
3552
  const activitiesByTurn = /* @__PURE__ */ new Map();
2976
3553
  for (const a of activityRows) {
@@ -2989,7 +3566,9 @@ var history = new Hono12().use("*", async (c, next) => {
2989
3566
  id: mindHistory.id,
2990
3567
  channel: mindHistory.channel,
2991
3568
  sender: mindHistory.sender,
2992
- content: mindHistory.content
3569
+ content: mindHistory.content,
3570
+ type: mindHistory.type,
3571
+ metadata: mindHistory.metadata
2993
3572
  }).from(mindHistory).where(inArray(mindHistory.id, triggerIds));
2994
3573
  for (const r of triggerRows) triggerMap.set(r.id, r);
2995
3574
  }
@@ -3012,77 +3591,7 @@ var history = new Hono12().use("*", async (c, next) => {
3012
3591
  }
3013
3592
  }
3014
3593
  }
3015
- const channelIdMap = /* @__PURE__ */ new Map();
3016
- try {
3017
- if (allChannelSlugs.size > 0) {
3018
- const channelNames = [...allChannelSlugs].filter((s) => !s.startsWith("@")).map((s) => {
3019
- let name = s;
3020
- if (name.startsWith("#")) name = name.slice(1);
3021
- const colonIdx = name.indexOf(":");
3022
- if (colonIdx >= 0) name = name.substring(colonIdx + 1);
3023
- return { slug: s, name };
3024
- });
3025
- if (channelNames.length > 0) {
3026
- const channelRows = await db.select({ conversationId: channels.conversation_id, name: channels.name }).from(channels).where(
3027
- inArray(
3028
- channels.name,
3029
- channelNames.map((c2) => c2.name)
3030
- )
3031
- );
3032
- const nameToId = new Map(channelRows.map((r) => [r.name, r.conversationId]));
3033
- for (const { slug, name } of channelNames) {
3034
- const id = nameToId.get(name);
3035
- if (id) channelIdMap.set(slug, id);
3036
- }
3037
- }
3038
- const dmSlugs = [...dmSlugMinds.keys()];
3039
- if (dmSlugs.length > 0) {
3040
- const targetNames = dmSlugs.map((s) => s.slice(1));
3041
- const cp2 = db.$with("cp2").as(
3042
- db.select({
3043
- conversation_id: conversationParticipants.conversation_id,
3044
- username: users.username
3045
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id))
3046
- );
3047
- const dmRows = await db.with(cp2).select({
3048
- id: conversations.id,
3049
- targetUsername: users.username
3050
- }).from(conversations).innerJoin(
3051
- conversationParticipants,
3052
- eq3(conversations.id, conversationParticipants.conversation_id)
3053
- ).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(and3(eq3(conversations.type, "dm"), inArray(users.username, targetNames)));
3054
- const dmCandidates = [];
3055
- for (const row of dmRows) {
3056
- const slug = `@${row.targetUsername}`;
3057
- const mindNames = dmSlugMinds.get(slug);
3058
- if (mindNames && !channelIdMap.has(slug)) {
3059
- dmCandidates.push({ slug, conversationId: row.id, mindNames: [...mindNames] });
3060
- }
3061
- }
3062
- if (dmCandidates.length > 0) {
3063
- const candidateConvIds = dmCandidates.map((d) => d.conversationId);
3064
- const allMindNames = [...new Set(dmCandidates.flatMap((d) => d.mindNames))];
3065
- const verifyRows = await db.select({
3066
- conversation_id: conversationParticipants.conversation_id,
3067
- username: users.username
3068
- }).from(conversationParticipants).innerJoin(users, eq3(conversationParticipants.user_id, users.id)).where(
3069
- and3(
3070
- inArray(conversationParticipants.conversation_id, candidateConvIds),
3071
- inArray(users.username, allMindNames)
3072
- )
3073
- );
3074
- const verifiedConvs = new Set(verifyRows.map((r) => r.conversation_id));
3075
- for (const candidate of dmCandidates) {
3076
- if (verifiedConvs.has(candidate.conversationId)) {
3077
- channelIdMap.set(candidate.slug, candidate.conversationId);
3078
- }
3079
- }
3080
- }
3081
- }
3082
- }
3083
- } catch (err) {
3084
- logger_default.warn("Failed to resolve channel slugs to conversation IDs", logger_default.errorData(err));
3085
- }
3594
+ const channelIdMap = await resolveConversationIds(db, allChannelSlugs, dmSlugMinds);
3086
3595
  const result = turnRows.map((t) => {
3087
3596
  const summary = summaryByTurn.get(t.id);
3088
3597
  const turnChannels = msgsByTurnChannel.get(t.id) ?? /* @__PURE__ */ new Map();
@@ -3129,6 +3638,7 @@ var history = new Hono12().use("*", async (c, next) => {
3129
3638
  }
3130
3639
  }
3131
3640
  const trigger = t.trigger_event_id ? triggerMap.get(t.trigger_event_id) : null;
3641
+ const isEventTrigger = trigger?.type === "event";
3132
3642
  return {
3133
3643
  id: t.id,
3134
3644
  mind: t.mind,
@@ -3136,8 +3646,23 @@ var history = new Hono12().use("*", async (c, next) => {
3136
3646
  summary_meta: summaryMeta,
3137
3647
  status: t.status,
3138
3648
  created_at: t.created_at,
3139
- trigger: trigger ? { channel: trigger.channel, sender: trigger.sender, content: trigger.content } : null,
3649
+ trigger: trigger ? {
3650
+ // The trigger's mind_history row id — matches events[].id /
3651
+ // messages[].source_event_id so the UI can dedupe the trigger
3652
+ // from the per-turn icon stack.
3653
+ eventId: t.trigger_event_id,
3654
+ channel: trigger.channel,
3655
+ sender: trigger.sender,
3656
+ content: trigger.content,
3657
+ ...isEventTrigger && trigger.channel ? {
3658
+ event: {
3659
+ type: trigger.channel.split(":")[1] || "event",
3660
+ label: eventLabelOf(trigger.metadata, trigger.channel)
3661
+ }
3662
+ } : {}
3663
+ } : null,
3140
3664
  conversations: convEntries,
3665
+ events: systemEventsByTurn.get(t.id) ?? [],
3141
3666
  activities: turnActivities
3142
3667
  };
3143
3668
  });
@@ -3162,7 +3687,7 @@ var history = new Hono12().use("*", async (c, next) => {
3162
3687
  }
3163
3688
  }, 15e3);
3164
3689
  if (mindFilter) {
3165
- unsubscribe = subscribe3(mindFilter, (event) => {
3690
+ unsubscribe = subscribe2(mindFilter, (event) => {
3166
3691
  try {
3167
3692
  send(JSON.stringify(event));
3168
3693
  } catch {
@@ -3254,6 +3779,9 @@ var history = new Hono12().use("*", async (c, next) => {
3254
3779
  }
3255
3780
  return { ...r, metadata };
3256
3781
  });
3782
+ if (c.req.query("include") === "icons") {
3783
+ await attachSummaryIcons(db, result);
3784
+ }
3257
3785
  return c.json(result);
3258
3786
  }).get("/activity", async (c) => {
3259
3787
  const mind = c.get("mindFilter");
@@ -3265,9 +3793,7 @@ var history = new Hono12().use("*", async (c, next) => {
3265
3793
  if (mind) conditions.push(eq3(activity.mind, mind));
3266
3794
  if (from) conditions.push(gte(activity.created_at, from));
3267
3795
  if (to) conditions.push(sql`${activity.created_at} <= ${to}`);
3268
- conditions.push(
3269
- sql`${activity.type} NOT IN ('mind_started', 'mind_stopped', 'mind_active', 'mind_idle', 'mind_done', 'mind_sleeping', 'mind_waking', 'brain_online', 'brain_offline')`
3270
- );
3796
+ conditions.push(sql`${activity.type} NOT IN ${NOISE_ACTIVITY_TYPES}`);
3271
3797
  const rows = await db.select({
3272
3798
  id: activity.id,
3273
3799
  type: activity.type,
@@ -3285,36 +3811,9 @@ var history = new Hono12().use("*", async (c, next) => {
3285
3811
  logger_default.debug(`malformed activity metadata for id ${r.id}`, logger_default.errorData(err));
3286
3812
  }
3287
3813
  }
3288
- return { ...r, metadata };
3289
- });
3290
- return c.json(result);
3291
- }).get("/away", async (c) => {
3292
- const mindFilter = c.get("mindFilter");
3293
- const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 100);
3294
- const db = await getDb();
3295
- const cutoff = utcDateTimeStr(new Date(Date.now() - AWAY_WINDOW_MS));
3296
- const conditions = [
3297
- eq3(summaries.period, "turn"),
3298
- gte(summaries.created_at, cutoff),
3299
- sql`(${turns.trigger_event_id} IS NULL OR ${mindHistory.sender} IS NULL OR ${users.user_type} IN ('mind', 'system'))`
3300
- ];
3301
- if (mindFilter) conditions.push(eq3(summaries.mind, mindFilter));
3302
- const rows = await db.select({
3303
- id: summaries.id,
3304
- mind: summaries.mind,
3305
- content: summaries.content,
3306
- created_at: summaries.created_at,
3307
- turn_id: turns.id
3308
- }).from(summaries).innerJoin(turns, eq3(turns.id, summaries.period_key)).leftJoin(mindHistory, eq3(mindHistory.id, turns.trigger_event_id)).leftJoin(users, eq3(users.username, mindHistory.sender)).where(and3(...conditions)).orderBy(desc2(summaries.created_at)).limit(limit);
3309
- return c.json(
3310
- rows.map((r) => ({
3311
- id: r.id,
3312
- mind: r.mind,
3313
- summary: r.content,
3314
- turnId: r.turn_id,
3315
- created_at: r.created_at
3316
- }))
3317
- );
3814
+ return { ...r, metadata };
3815
+ });
3816
+ return c.json(result);
3318
3817
  });
3319
3818
  var history_default = history;
3320
3819
 
@@ -3562,7 +4061,7 @@ function classify(raw) {
3562
4061
  )) {
3563
4062
  return {
3564
4063
  reason: "auth_error",
3565
- detail: "Your last turn failed because the model provider rejected your credentials. A one-off is usually a briefly-expired token that the daemon refreshes automatically. If it keeps happening, your API key is likely missing or invalid \u2014 an operator needs to set a valid key (e.g. `volute env set ANTHROPIC_API_KEY <key>`) or reconnect the provider in the dashboard."
4064
+ detail: "Your last turn failed because the model provider rejected your credentials. A one-off is usually a briefly-expired token that the daemon refreshes automatically. If it keeps happening, your API key is likely missing or invalid \u2014 a host needs to set a valid key (e.g. `volute env set ANTHROPIC_API_KEY <key>`) or reconnect the provider in the dashboard."
3566
4065
  };
3567
4066
  }
3568
4067
  if (/\b429\b|rate.?limit|too many requests/.test(s)) {
@@ -3606,7 +4105,7 @@ function markDeliveredOnCleanTurn(mind, session) {
3606
4105
  noticeDrainWatermarks.delete(wmKey);
3607
4106
  const errored = takeErrored(mind, session);
3608
4107
  if (!errored && watermark != null) {
3609
- clearDeliveredNotices(mind, session, watermark).catch(
4108
+ clearDeliveredEvents(mind, session, watermark).catch(
3610
4109
  (err) => llog.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err))
3611
4110
  );
3612
4111
  }
@@ -3617,12 +4116,15 @@ async function linkPendingInbound(mind, turnId, channel, session) {
3617
4116
  const db = await getDb();
3618
4117
  let lowerBound;
3619
4118
  if (session) {
3620
- const prev = await db.select({ max: sql2`max(${turns.created_at})` }).from(turns).where(and4(eq4(turns.mind, mind), eq4(turns.session, session), sql2`${turns.id} != ${turnId}`)).get();
4119
+ const prev = await db.select({ max: sql2`max(${turns.created_at})` }).from(turns).where(and4(eq4(turns.mind, mind), eq4(turns.thread, session), sql2`${turns.id} != ${turnId}`)).get();
3621
4120
  lowerBound = prev?.max ?? void 0;
3622
4121
  }
3623
4122
  const conditions = [
3624
4123
  eq4(mindHistory.mind, mind),
3625
- eq4(mindHistory.type, "inbound"),
4124
+ // "event" rows are system events (see recordEventRow) — not messages, but they
4125
+ // trigger turns the same way, and this linkage is what sets `trigger_event_id` and
4126
+ // thus drives reflection capture. Dropping them here breaks it silently.
4127
+ inArray2(mindHistory.type, ["inbound", "event"]),
3626
4128
  sql2`${mindHistory.turn_id} IS NULL`,
3627
4129
  eq4(mindHistory.channel, scopeChannel)
3628
4130
  ];
@@ -3644,7 +4146,7 @@ async function handleMindEvent(mind, event) {
3644
4146
  if (!turnId) {
3645
4147
  llog.warn(`skipping turn tracking for ${mind}: createTurn failed`);
3646
4148
  } else {
3647
- publish3(mind, { mind, type: "turn_created", turnId });
4149
+ publish2(mind, { mind, type: "turn_created", turnId });
3648
4150
  if (event.session) await assignSession(mind, turnId, event.session);
3649
4151
  try {
3650
4152
  await linkPendingInbound(mind, turnId, event.channel, event.session);
@@ -3663,7 +4165,7 @@ async function handleMindEvent(mind, event) {
3663
4165
  const result = await db.insert(mindHistory).values({
3664
4166
  mind,
3665
4167
  type: event.type,
3666
- session: event.session ?? null,
4168
+ thread: event.session ?? null,
3667
4169
  channel: event.channel ?? null,
3668
4170
  message_id: event.messageId ?? null,
3669
4171
  content: cleanContent ?? null,
@@ -3690,7 +4192,7 @@ async function handleMindEvent(mind, event) {
3690
4192
  llog.error("failed to link tool_result to turn", logger_default.errorData(err));
3691
4193
  }
3692
4194
  }
3693
- publish3(mind, {
4195
+ publish2(mind, {
3694
4196
  mind,
3695
4197
  type: event.type,
3696
4198
  session: event.session,
@@ -3706,16 +4208,12 @@ async function handleMindEvent(mind, event) {
3706
4208
  );
3707
4209
  }
3708
4210
  onMindEvent(mind, event.type, event.channel);
3709
- if ((event.type === "text" || event.type === "outbound") && event.channel) {
3710
- const map = getTypingMap();
3711
- publishTypingForChannels(map.deleteSender(mind), map);
3712
- }
3713
4211
  if (event.type === "error" && event.session) {
3714
4212
  markErrored(mind, event.session);
3715
4213
  const { reason, detail } = classify(event.content ?? "");
3716
4214
  await recordNotice({
3717
4215
  mind,
3718
- session: event.session,
4216
+ thread: event.session,
3719
4217
  kind: "turn_error",
3720
4218
  reason,
3721
4219
  detail,
@@ -3744,7 +4242,7 @@ async function handleMindEvent(mind, event) {
3744
4242
  if (event.session && tb.noteExceeded(mind)) {
3745
4243
  void recordNotice({
3746
4244
  mind,
3747
- session: event.session,
4245
+ thread: event.session,
3748
4246
  kind: "budget",
3749
4247
  reason: "token_budget",
3750
4248
  detail: "You've used your token budget for this period, so your activity may pause until it resets. This isn't anything you did wrong \u2014 it's just a rest imposed by the budget. Anything that arrives while you're paused will be kept for you. If you're mid-thought, this turn is a good moment to jot it down."
@@ -3757,6 +4255,9 @@ async function completeTurnAndSummarize(mind, event, insertedId) {
3757
4255
  const finish = async () => {
3758
4256
  const completedTurnId = await completeTurn(mind, event.session);
3759
4257
  markDeliveredOnCleanTurn(mind, event.session);
4258
+ captureReflection(mind, completedTurnId).catch(
4259
+ (err) => llog.warn("failed to capture event reflection", logger_default.errorData(err))
4260
+ );
3760
4261
  if (insertedId != null) {
3761
4262
  summarizeTurn(mind, event.session, event.channel, insertedId, completedTurnId).catch(
3762
4263
  (err) => llog.error("turn summarization failed", logger_default.errorData(err))
@@ -3870,7 +4371,7 @@ function defaultDreamSchedule() {
3870
4371
  cron: "0 3 * * *",
3871
4372
  message: "it's 3am. you are dreaming.\n\ngather your material \u2014 read your latest journal entry, read MEMORY.md, surface random memories if you have a way to. then construct a dream premise from that material and invoke the dreamer subagent to experience the dream.",
3872
4373
  enabled: true,
3873
- session: "$new",
4374
+ thread: "$new",
3874
4375
  whileSleeping: "trigger-wake"
3875
4376
  };
3876
4377
  }
@@ -4120,6 +4621,124 @@ async function assertExcludedPathsRestored(opts) {
4120
4621
  }
4121
4622
  }
4122
4623
 
4624
+ // packages/daemon/src/lib/services/imagegen-jobs.ts
4625
+ import { mkdir, writeFile } from "fs/promises";
4626
+ import { dirname as dirname2 } from "path";
4627
+ var jobLog = logger_default.child("imagegen-jobs");
4628
+ var MAX_INFLIGHT_PER_MIND = 4;
4629
+ var DONE_TTL_MS = 10 * 60 * 1e3;
4630
+ var GENERATION_TIMEOUT_MS = 3 * 60 * 1e3;
4631
+ var jobs = /* @__PURE__ */ new Map();
4632
+ function publicView(r) {
4633
+ const base = { id: r.id, mind: r.mind, createdAt: r.createdAt };
4634
+ if (r.status === "done") {
4635
+ if (!r.path) throw new Error(`job ${r.id} is done but has no path`);
4636
+ return { ...base, status: "done", completedAt: r.completedAt ?? r.createdAt, path: r.path };
4637
+ }
4638
+ if (r.status === "error") {
4639
+ return {
4640
+ ...base,
4641
+ status: "error",
4642
+ completedAt: r.completedAt ?? r.createdAt,
4643
+ error: r.error ?? "unknown error"
4644
+ };
4645
+ }
4646
+ return { ...base, status: "running" };
4647
+ }
4648
+ function countInflight(mind) {
4649
+ let n = 0;
4650
+ for (const r of jobs.values()) if (r.mind === mind && r.status === "running") n++;
4651
+ return n;
4652
+ }
4653
+ function settle(record, patch) {
4654
+ Object.assign(record, patch, { completedAt: Date.now() });
4655
+ record.resolveSettled();
4656
+ setTimeout(() => jobs.delete(record.id), DONE_TTL_MS).unref?.();
4657
+ }
4658
+ async function notify(deliver, mind, body) {
4659
+ try {
4660
+ const { delivered } = await deliver(mind, {
4661
+ type: "imagegen",
4662
+ body,
4663
+ delivery: "immediate",
4664
+ whileSleeping: "queue"
4665
+ });
4666
+ if (!delivered) jobLog.warn(`imagegen event not delivered to ${mind}: ${body}`);
4667
+ } catch (err) {
4668
+ jobLog.warn(`failed to deliver imagegen event to ${mind}`, logger_default.errorData(err));
4669
+ }
4670
+ }
4671
+ async function runJob(record, model, prompt, filename, deps) {
4672
+ const generate = deps.generate ?? generateImage;
4673
+ const deliver = deps.deliver ?? deliverEvent;
4674
+ const controller = new AbortController();
4675
+ const timer = setTimeout(() => controller.abort(), GENERATION_TIMEOUT_MS);
4676
+ timer.unref?.();
4677
+ try {
4678
+ const buf = await generate(model, prompt, controller.signal);
4679
+ const home = `${await resolveMindDir(record.mind)}/home`;
4680
+ const target = safeResolveWithinBase(home, `images/${filename}.png`);
4681
+ if (!target) throw new Error(`Invalid image filename: ${filename}`);
4682
+ await mkdir(dirname2(target), { recursive: true });
4683
+ await writeFile(target, buf);
4684
+ await chownMindFile(target, record.mind);
4685
+ settle(record, { status: "done", path: target });
4686
+ if (record.notifyOnDone) await notify(deliver, record.mind, `image ready: ${target}`);
4687
+ } catch (err) {
4688
+ const message = controller.signal.aborted ? `image generation timed out after ${Math.round(GENERATION_TIMEOUT_MS / 1e3)}s` : err instanceof Error ? err.message : String(err);
4689
+ jobLog.error(`job ${record.id} failed`, logger_default.errorData(err));
4690
+ settle(record, { status: "error", error: message });
4691
+ if (record.notifyOnDone)
4692
+ await notify(deliver, record.mind, `image generation failed: ${message}`);
4693
+ } finally {
4694
+ clearTimeout(timer);
4695
+ }
4696
+ }
4697
+ function createImagegenJob(mind, model, prompt, filename, deps = {}) {
4698
+ if (countInflight(mind) >= MAX_INFLIGHT_PER_MIND) {
4699
+ throw new Error(
4700
+ `Too many image generations in progress (max ${MAX_INFLIGHT_PER_MIND}). Wait for one to finish.`
4701
+ );
4702
+ }
4703
+ const id = crypto.randomUUID();
4704
+ let resolveSettled;
4705
+ const settled = new Promise((r) => {
4706
+ resolveSettled = r;
4707
+ });
4708
+ const record = {
4709
+ id,
4710
+ mind,
4711
+ status: "running",
4712
+ createdAt: Date.now(),
4713
+ settled,
4714
+ resolveSettled,
4715
+ notifyOnDone: false
4716
+ };
4717
+ jobs.set(id, record);
4718
+ void runJob(record, model, prompt, filename, deps);
4719
+ return id;
4720
+ }
4721
+ function getImagegenJob(mind, id) {
4722
+ const record = jobs.get(id);
4723
+ if (!record || record.mind !== mind) return void 0;
4724
+ return publicView(record);
4725
+ }
4726
+ async function waitForImagegenJob(mind, id, timeoutMs) {
4727
+ const record = jobs.get(id);
4728
+ if (!record || record.mind !== mind) return void 0;
4729
+ if (record.status === "running" && timeoutMs > 0) {
4730
+ let timer;
4731
+ const timeout = new Promise((r) => {
4732
+ timer = setTimeout(r, timeoutMs);
4733
+ timer.unref?.();
4734
+ });
4735
+ await Promise.race([record.settled, timeout]);
4736
+ if (timer) clearTimeout(timer);
4737
+ if (record.status === "running") record.notifyOnDone = true;
4738
+ }
4739
+ return publicView(record);
4740
+ }
4741
+
4123
4742
  // packages/daemon/src/lib/template/convert-session.ts
4124
4743
  import { randomUUID } from "crypto";
4125
4744
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
@@ -4129,17 +4748,17 @@ function convertSession(opts) {
4129
4748
  const lines = readFileSync8(opts.sessionPath, "utf-8").trim().split("\n");
4130
4749
  const sessionId = randomUUID();
4131
4750
  const idMap = /* @__PURE__ */ new Map();
4132
- const messages = [];
4751
+ const messages2 = [];
4133
4752
  for (const line of lines) {
4134
4753
  const event = JSON.parse(line);
4135
4754
  if (event.type === "message" && event.message) {
4136
- messages.push(event);
4755
+ messages2.push(event);
4137
4756
  }
4138
4757
  }
4139
4758
  const sdkEvents = [];
4140
4759
  let lastSdkUuid = null;
4141
- for (let i = 0; i < messages.length; i++) {
4142
- const event = messages[i];
4760
+ for (let i = 0; i < messages2.length; i++) {
4761
+ const event = messages2[i];
4143
4762
  const msg = event.message;
4144
4763
  if (msg.role === "user") {
4145
4764
  const uuid = randomUUID();
@@ -4200,8 +4819,8 @@ function convertSession(opts) {
4200
4819
  let lastToolResultId = event.id;
4201
4820
  let lastTimestamp = event.timestamp;
4202
4821
  let j = i;
4203
- while (j < messages.length && messages[j].message.role === "toolResult") {
4204
- const tr = messages[j];
4822
+ while (j < messages2.length && messages2[j].message.role === "toolResult") {
4823
+ const tr = messages2[j];
4205
4824
  const trMsg = tr.message;
4206
4825
  lastToolResultId = tr.id;
4207
4826
  lastTimestamp = tr.timestamp;
@@ -4294,15 +4913,22 @@ function convertAssistantContent(content) {
4294
4913
  return result;
4295
4914
  }
4296
4915
 
4916
+ // packages/daemon/src/lib/util/time.ts
4917
+ function parseDbTimestamp(ts) {
4918
+ if (/[zZ]$|[+-]\d\d:?\d\d$/.test(ts)) return new Date(ts);
4919
+ return /* @__PURE__ */ new Date(`${ts.replace(" ", "T")}Z`);
4920
+ }
4921
+
4297
4922
  // packages/daemon/src/web/api/minds.ts
4298
4923
  var _lastActiveCache = { map: /* @__PURE__ */ new Map(), ts: 0 };
4299
4924
  var _LAST_ACTIVE_TTL = 6e4;
4300
- async function getMindStatus(name, port, registryRunning) {
4925
+ var DAEMON_AUTHORED_TYPES = /* @__PURE__ */ new Set(["inbound", "event"]);
4926
+ async function getMindStatus(name, port, registryRunning, seed) {
4301
4927
  const manager = getMindManager();
4302
4928
  let status = "stopped";
4303
4929
  let wakeAt = null;
4304
4930
  try {
4305
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
4931
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
4306
4932
  const sleepManager = getSleepManagerIfReady2();
4307
4933
  if (sleepManager?.isSleeping(name)) {
4308
4934
  status = "sleeping";
@@ -4326,7 +4952,8 @@ async function getMindStatus(name, port, registryRunning) {
4326
4952
  status: status === "running" ? "connected" : "disconnected"
4327
4953
  });
4328
4954
  }
4329
- const lastError = await latestFailureNotice(name);
4955
+ const lastError = await latestFailureEvent(name);
4956
+ const seedChecklist = seed?.stage === "seed" ? evaluateSeedChecklist(seed.dir ?? mindDir(name)) : void 0;
4330
4957
  return {
4331
4958
  status,
4332
4959
  wakeAt,
@@ -4334,7 +4961,8 @@ async function getMindStatus(name, port, registryRunning) {
4334
4961
  channels: channels2,
4335
4962
  displayName: config2?.profile?.displayName,
4336
4963
  description: config2?.profile?.description,
4337
- avatar: config2?.profile?.avatar
4964
+ avatar: config2?.profile?.avatar,
4965
+ seedChecklist
4338
4966
  };
4339
4967
  }
4340
4968
  function isPrivileged(c) {
@@ -4356,6 +4984,7 @@ function toPublicMind(entry, status, extras) {
4356
4984
  displayName: status.displayName,
4357
4985
  description: status.description,
4358
4986
  avatar: status.avatar,
4987
+ seedChecklist: status.seedChecklist,
4359
4988
  hasPages: extras.hasPages,
4360
4989
  lastActiveAt: extras.lastActiveAt ?? null
4361
4990
  };
@@ -4726,7 +5355,8 @@ async function importHistoryFromArchive(name, tempDir) {
4726
5355
  await db.insert(mindHistory).values({
4727
5356
  mind: name,
4728
5357
  channel: row.channel ?? null,
4729
- session: row.session ?? null,
5358
+ // Archives are long-lived files: pre-rename exports carry `session`.
5359
+ thread: row.thread ?? row.session ?? null,
4730
5360
  sender: row.sender ?? null,
4731
5361
  message_id: row.message_id ?? null,
4732
5362
  type: row.type,
@@ -4819,8 +5449,16 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4819
5449
  const nameErr = validateMindName(name);
4820
5450
  if (nameErr) return c.json({ error: nameErr }, 400);
4821
5451
  if (await findMind(name)) return c.json({ error: `Mind already exists: ${name}` }, 409);
5452
+ if (!isAiConfigured()) {
5453
+ return c.json(
5454
+ {
5455
+ error: "Minds cannot think yet \u2014 no AI provider is configured. Add a provider in Settings before creating a mind."
5456
+ },
5457
+ 409
5458
+ );
5459
+ }
4822
5460
  {
4823
- const { mindLimitError, readGlobalConfig: readGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
5461
+ const { mindLimitError, readGlobalConfig: readGlobalConfig2 } = await import("./setup-CAJFOR2N.js");
4824
5462
  const limitError = mindLimitError(await countCappedMinds(), readGlobalConfig2().maxMinds);
4825
5463
  if (limitError) return c.json({ error: limitError }, 409);
4826
5464
  }
@@ -4835,7 +5473,7 @@ var app13 = new Hono16().post("/", requireAdminOrSystem, zValidator6("json", cre
4835
5473
  const { publicKeyPem } = generateIdentity(dest);
4836
5474
  let effectiveModel = body.model;
4837
5475
  {
4838
- const { readGlobalConfig: readGlobal } = await import("./setup-TCN7VH5U.js");
5476
+ const { readGlobalConfig: readGlobal } = await import("./setup-CAJFOR2N.js");
4839
5477
  const mindDefaults = readGlobal().mindDefaults;
4840
5478
  const config2 = readVoluteConfig(dest);
4841
5479
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4916,7 +5554,7 @@ Your creator described you as: "${body.description}"
4916
5554
  }
4917
5555
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4918
5556
  if (body.stage === "seed" && !body.skills) {
4919
- const { isImagegenEnabled } = await import("./setup-TCN7VH5U.js");
5557
+ const { isImagegenEnabled } = await import("./setup-CAJFOR2N.js");
4920
5558
  if (isImagegenEnabled()) {
4921
5559
  skillSet = [...skillSet, "imagegen"];
4922
5560
  }
@@ -4935,9 +5573,10 @@ Your creator described you as: "${body.description}"
4935
5573
  }
4936
5574
  if (body.stage === "seed") {
4937
5575
  try {
4938
- const spiritEntry = await findMind("volute");
5576
+ const spiritName = getSpiritName();
5577
+ const spiritEntry = await findMind(spiritName);
4939
5578
  if (spiritEntry) {
4940
- const { spiritDir } = await import("./spirit-SSJMYC6A.js");
5579
+ const { spiritDir } = await import("./spirit-IL5EMWYZ.js");
4941
5580
  const sDir = spiritEntry.dir ?? spiritDir();
4942
5581
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4943
5582
  const schedules = spiritConfig.schedules ?? [];
@@ -4946,14 +5585,14 @@ Your creator described you as: "${body.description}"
4946
5585
  schedules.push({
4947
5586
  id: nurtureId,
4948
5587
  cron: process.env.VOLUTE_NURTURE_CRON ?? "*/5 * * * *",
4949
- script: `volute seed check ${name}`,
5588
+ script: `volute seed check ${name} --nurture`,
4950
5589
  enabled: true,
4951
5590
  whileSleeping: "skip"
4952
5591
  });
4953
5592
  spiritConfig.schedules = schedules;
4954
5593
  writeVoluteConfig(sDir, spiritConfig);
4955
- const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
4956
- getScheduler2().loadSchedules("volute", sDir);
5594
+ const { getScheduler: getScheduler2 } = await import("./scheduler-UV6RUJ7P.js");
5595
+ getScheduler2().loadSchedules(spiritName, sDir);
4957
5596
  }
4958
5597
  }
4959
5598
  } catch (err) {
@@ -5151,7 +5790,10 @@ ${user.trimEnd()}
5151
5790
  const privileged = isPrivileged(c);
5152
5791
  const minds = await Promise.all(
5153
5792
  entries.map(async (entry) => {
5154
- const mindStatus = await getMindStatus(entry.name, entry.port, entry.running);
5793
+ const mindStatus = await getMindStatus(entry.name, entry.port, entry.running, {
5794
+ stage: entry.stage,
5795
+ dir: entry.dir
5796
+ });
5155
5797
  const hasPages = existsSync7(resolve11(mindDir(entry.name), "home", "pages"));
5156
5798
  const lastActiveAt = lastActiveMap.get(entry.name) ?? null;
5157
5799
  if (!privileged) return toPublicMind(entry, mindStatus, { hasPages, lastActiveAt });
@@ -5171,7 +5813,10 @@ ${user.trimEnd()}
5171
5813
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5172
5814
  const dir = entry.dir ?? mindDir(entry.parent ?? name);
5173
5815
  if (!existsSync7(dir)) return c.json({ error: "Mind directory missing" }, 404);
5174
- const mindStatus = await getMindStatus(name, entry.port);
5816
+ const mindStatus = await getMindStatus(name, entry.port, void 0, {
5817
+ stage: entry.stage,
5818
+ dir: entry.dir
5819
+ });
5175
5820
  const hasPages = existsSync7(resolve11(mindDir(name), "home", "pages"));
5176
5821
  if (!isPrivileged(c)) {
5177
5822
  return c.json(toPublicMind(entry, mindStatus, { hasPages }));
@@ -5188,7 +5833,7 @@ ${user.trimEnd()}
5188
5833
  return { name: s.name, port: s.port, status: variantStatus };
5189
5834
  })
5190
5835
  );
5191
- const notice = await latestNotice(name);
5836
+ const notice = await latestEvent(name);
5192
5837
  return c.json({
5193
5838
  ...entry,
5194
5839
  ...mindStatus,
@@ -5249,7 +5894,7 @@ ${user.trimEnd()}
5249
5894
  const manager = getMindManager();
5250
5895
  try {
5251
5896
  if (context?.type === "reload") {
5252
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5897
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
5253
5898
  const sleepState = getSleepManagerIfReady2()?.getState(name);
5254
5899
  if (sleepState?.sleeping) {
5255
5900
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -5309,13 +5954,6 @@ ${user.trimEnd()}
5309
5954
  if (context && context.type !== "reload") {
5310
5955
  manager.setPendingContext(name, context);
5311
5956
  }
5312
- if (context?.type === "sprouted" && !entry.parent) {
5313
- try {
5314
- await deliverSproutedNotice(baseName);
5315
- } catch (err) {
5316
- logger_default.error(`failed to inject sprouted message for ${baseName}`, logger_default.errorData(err));
5317
- }
5318
- }
5319
5957
  const repoDir = projectDir;
5320
5958
  try {
5321
5959
  await startMindFull(name);
@@ -5333,7 +5971,7 @@ ${user.trimEnd()}
5333
5971
  const errLine = (stderr ?? startMsg).trim().split("\n").filter(Boolean).pop() ?? "";
5334
5972
  await recordNotice({
5335
5973
  mind: name,
5336
- session: "main",
5974
+ thread: "main",
5337
5975
  kind: "startup",
5338
5976
  reason: "startup_failed",
5339
5977
  detail: `Your last change to src/ broke startup, so it was rolled back and your previous working code was restored. The broken change is preserved on branch \`${rollback.branch}\` \u2014 check it out to inspect and fix it. Error: ${errLine}`,
@@ -5371,7 +6009,7 @@ ${user.trimEnd()}
5371
6009
  const name = c.req.param("name");
5372
6010
  const entry = await findMind(name);
5373
6011
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5374
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
6012
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
5375
6013
  const sm = getSleepManagerIfReady2();
5376
6014
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5377
6015
  return c.json(sm.getState(name));
@@ -5379,7 +6017,7 @@ ${user.trimEnd()}
5379
6017
  const name = c.req.param("name");
5380
6018
  const entry = await findMind(name);
5381
6019
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5382
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
6020
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
5383
6021
  const sm = getSleepManagerIfReady2();
5384
6022
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5385
6023
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5399,7 +6037,7 @@ ${user.trimEnd()}
5399
6037
  const name = c.req.param("name");
5400
6038
  const entry = await findMind(name);
5401
6039
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5402
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
6040
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
5403
6041
  const sm = getSleepManagerIfReady2();
5404
6042
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5405
6043
  const sleepState = sm.getState(name);
@@ -5414,7 +6052,7 @@ ${user.trimEnd()}
5414
6052
  const name = c.req.param("name");
5415
6053
  const entry = await findMind(name);
5416
6054
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5417
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
6055
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
5418
6056
  const sm = getSleepManagerIfReady2();
5419
6057
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5420
6058
  const flushed = await sm.flushQueuedMessages(name);
@@ -5442,25 +6080,68 @@ ${user.trimEnd()}
5442
6080
  }
5443
6081
  config2.profile = profile;
5444
6082
  writeVoluteConfig(dir, config2);
5445
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-ABYLOEZ7.js");
6083
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-DT7GY7EZ.js");
5446
6084
  await syncMindProfile2(name, profile);
5447
6085
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5448
6086
  return c.json({ ok: true });
6087
+ }).post(
6088
+ "/:name/imagegen/jobs",
6089
+ requireSelf(),
6090
+ zValidator6(
6091
+ "json",
6092
+ z6.object({
6093
+ model: z6.string().min(1),
6094
+ prompt: z6.string().min(1),
6095
+ filename: z6.string().min(1)
6096
+ })
6097
+ ),
6098
+ async (c) => {
6099
+ const name = c.req.param("name");
6100
+ const { model, prompt, filename } = c.req.valid("json");
6101
+ try {
6102
+ const jobId = createImagegenJob(name, model, prompt, filename);
6103
+ return c.json({ jobId }, 202);
6104
+ } catch (err) {
6105
+ return c.json({ error: err instanceof Error ? err.message : "Failed to start job" }, 429);
6106
+ }
6107
+ }
6108
+ ).get("/:name/imagegen/jobs/:jobId", requireSelf(), async (c) => {
6109
+ const name = c.req.param("name");
6110
+ const jobId = c.req.param("jobId");
6111
+ const waitParam = Number(c.req.query("wait"));
6112
+ const waitMs = Number.isFinite(waitParam) ? Math.min(Math.max(waitParam, 0), 60) * 1e3 : 0;
6113
+ const job = waitMs > 0 ? await waitForImagegenJob(name, jobId, waitMs) : getImagegenJob(name, jobId);
6114
+ if (!job) return c.json({ error: "Job not found" }, 404);
6115
+ return c.json(job);
5449
6116
  }).get("/:name/seed-check", requireSelf(), async (c) => {
5450
6117
  const name = c.req.param("name");
6118
+ const force = c.req.query("force") === "1" || c.req.query("force") === "true";
5451
6119
  const entry = await findMind(name);
5452
6120
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5453
- if (entry.stage !== "seed") return c.json({ output: "" });
6121
+ if (entry.stage !== "seed") {
6122
+ return c.json({
6123
+ output: force ? `${name} is no longer a seed (stage: ${entry.stage}).` : ""
6124
+ });
6125
+ }
6126
+ if (!force) {
6127
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-LIEPO6WG.js");
6128
+ if (getSleepManagerIfReady2()?.isSleeping(name)) {
6129
+ return c.json({ output: "" });
6130
+ }
6131
+ }
5454
6132
  const db = await getDb();
5455
6133
  const rawCreator = Number(process.env.VOLUTE_NURTURE_CREATOR_MINUTES);
5456
6134
  const creatorThreshold = Number.isNaN(rawCreator) ? 5 : rawCreator;
5457
6135
  const rawSpirit = Number(process.env.VOLUTE_NURTURE_SPIRIT_MINUTES);
5458
6136
  const spiritThreshold = Number.isNaN(rawSpirit) ? 15 : rawSpirit;
5459
- const lastCreatorMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
6137
+ const rawNudge = Number(process.env.VOLUTE_NURTURE_NUDGE_MINUTES);
6138
+ const nudgeThreshold = Number.isNaN(rawNudge) ? 30 : rawNudge;
6139
+ const spiritName = getSpiritName();
6140
+ const lastActivityMsg = await db.select({ created_at: mindHistory.created_at, sender: mindHistory.sender }).from(mindHistory).where(
5460
6141
  and5(
5461
6142
  eq5(mindHistory.mind, name),
5462
6143
  eq5(mindHistory.type, "inbound"),
5463
- sql3`${mindHistory.sender} != 'volute'`,
6144
+ sql3`${mindHistory.sender} != ${spiritName}`,
5464
6145
  sql3`${mindHistory.sender} != ${name}`,
5465
6146
  sql3`${mindHistory.sender} IS NOT NULL`
5466
6147
  )
@@ -5469,20 +6150,37 @@ ${user.trimEnd()}
5469
6150
  and5(
5470
6151
  eq5(mindHistory.mind, name),
5471
6152
  eq5(mindHistory.type, "inbound"),
5472
- eq5(mindHistory.sender, "volute")
6153
+ eq5(mindHistory.sender, spiritName)
5473
6154
  )
5474
6155
  ).orderBy(desc3(mindHistory.created_at)).limit(1);
5475
6156
  const now = Date.now();
5476
- const creatorTime = lastCreatorMsg[0] ? new Date(lastCreatorMsg[0].created_at).getTime() : 0;
5477
- const spiritTime = lastSpiritMsg[0] ? new Date(lastSpiritMsg[0].created_at).getTime() : 0;
5478
- const minutesSinceCreator = creatorTime ? (now - creatorTime) / 6e4 : Infinity;
6157
+ const activityTime = lastActivityMsg[0] ? parseDbTimestamp(lastActivityMsg[0].created_at).getTime() : 0;
6158
+ const spiritTime = lastSpiritMsg[0] ? parseDbTimestamp(lastSpiritMsg[0].created_at).getTime() : 0;
6159
+ const minutesSinceActivity = activityTime ? (now - activityTime) / 6e4 : Infinity;
5479
6160
  const minutesSinceSpirit = spiritTime ? (now - spiritTime) / 6e4 : Infinity;
5480
- if (minutesSinceCreator < creatorThreshold && minutesSinceSpirit < spiritThreshold) {
6161
+ if (!force && (minutesSinceActivity < creatorThreshold || minutesSinceSpirit < spiritThreshold)) {
5481
6162
  return c.json({ output: "" });
5482
6163
  }
6164
+ if (!force) {
6165
+ const nudgePattern = `Seed: ${name.replace(/[\\%_]/g, "\\$&")}
6166
+ %`;
6167
+ const lastNudge = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
6168
+ and5(
6169
+ eq5(mindHistory.mind, spiritName),
6170
+ eq5(mindHistory.type, "event"),
6171
+ sql3`${mindHistory.content} LIKE ${nudgePattern} ESCAPE '\\'`
6172
+ )
6173
+ ).orderBy(desc3(mindHistory.created_at)).limit(1);
6174
+ if (lastNudge[0]) {
6175
+ const minutesSinceNudge = (now - parseDbTimestamp(lastNudge[0].created_at).getTime()) / 6e4;
6176
+ if (minutesSinceNudge < nudgeThreshold) {
6177
+ return c.json({ output: "" });
6178
+ }
6179
+ }
6180
+ }
5483
6181
  const dir = entry.dir ?? mindDir(name);
5484
- const { evaluateSeedChecklist } = await import("./seed-readiness-3LRYASA4.js");
5485
- const checklist = evaluateSeedChecklist(dir);
6182
+ const { evaluateSeedChecklist: evaluateSeedChecklist2 } = await import("./seed-readiness-QRA46ZJQ.js");
6183
+ const checklist = evaluateSeedChecklist2(dir);
5486
6184
  const done = [];
5487
6185
  const remaining = [];
5488
6186
  if (checklist.soulWritten) done.push("SOUL.md written");
@@ -5495,8 +6193,8 @@ ${user.trimEnd()}
5495
6193
  if (checklist.avatarSet) done.push("Avatar set");
5496
6194
  else remaining.push("Generate and set avatar");
5497
6195
  }
5498
- const creatorStatus = minutesSinceCreator === Infinity ? "No creator messages yet" : `Last creator message: ${Math.round(minutesSinceCreator)} minutes ago`;
5499
- const lines = [`Seed: ${name}`, creatorStatus];
6196
+ const activityStatus = minutesSinceActivity === Infinity ? `No one has messaged ${name} yet` : `Last message to ${name}: ${Math.round(minutesSinceActivity)} minutes ago (from ${lastActivityMsg[0].sender})`;
6197
+ const lines = [`Seed: ${name}`, activityStatus];
5500
6198
  if (done.length > 0) lines.push(`Done: ${done.join(", ")}`);
5501
6199
  if (remaining.length > 0) lines.push(`Remaining: ${remaining.join(", ")}`);
5502
6200
  if (remaining.length > 0) {
@@ -5519,19 +6217,21 @@ ${user.trimEnd()}
5519
6217
  await joinSystemChannelForMind(name).catch(
5520
6218
  (err) => logger_default.warn(`failed to join #system on sprout for ${name}`, logger_default.errorData(err))
5521
6219
  );
6220
+ await announceSprout(name);
5522
6221
  try {
5523
6222
  const sproutedDir = entry.dir ?? mindDir(name);
5524
6223
  if (setupDefaultDreaming(sproutedDir).schedulesChanged) {
5525
- const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
6224
+ const { getScheduler: getScheduler2 } = await import("./scheduler-UV6RUJ7P.js");
5526
6225
  getScheduler2().loadSchedules(name, sproutedDir);
5527
6226
  }
5528
6227
  } catch (err) {
5529
6228
  logger_default.warn(`failed to set up default dreaming for ${name}`, logger_default.errorData(err));
5530
6229
  }
5531
6230
  try {
5532
- const spiritEntry = await findMind("volute");
6231
+ const spiritName = getSpiritName();
6232
+ const spiritEntry = await findMind(spiritName);
5533
6233
  if (spiritEntry) {
5534
- const { firstWeekSchedules, spiritDir } = await import("./spirit-SSJMYC6A.js");
6234
+ const { firstWeekSchedules, spiritDir } = await import("./spirit-IL5EMWYZ.js");
5535
6235
  const sDir = spiritEntry.dir ?? spiritDir();
5536
6236
  const spiritConfig = readVoluteConfig(sDir);
5537
6237
  if (spiritConfig) {
@@ -5545,8 +6245,8 @@ ${user.trimEnd()}
5545
6245
  spiritConfig.schedules = schedules;
5546
6246
  writeVoluteConfig(sDir, spiritConfig);
5547
6247
  try {
5548
- const { getScheduler: getScheduler2 } = await import("./scheduler-K7Y7Z7FQ.js");
5549
- getScheduler2().loadSchedules("volute", sDir);
6248
+ const { getScheduler: getScheduler2 } = await import("./scheduler-UV6RUJ7P.js");
6249
+ getScheduler2().loadSchedules(spiritName, sDir);
5550
6250
  } catch (err) {
5551
6251
  logger_default.warn(
5552
6252
  `spirit schedules for sprout of ${name} written to disk but not reloaded into the running scheduler (effective on next spirit restart)`,
@@ -5866,7 +6566,7 @@ ${user.trimEnd()}
5866
6566
  const limitStr = c.req.query("limit");
5867
6567
  if (!beforeStr && !limitStr) {
5868
6568
  const msgs = await getMessages(convId);
5869
- return c.json({ items: msgs, hasMore: false });
6569
+ return c.json({ items: await withSenderDisplayNames(msgs), hasMore: false });
5870
6570
  }
5871
6571
  const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
5872
6572
  const limit = limitStr ? parseInt(limitStr, 10) : void 0;
@@ -5874,7 +6574,10 @@ ${user.trimEnd()}
5874
6574
  return c.json({ error: "Invalid pagination parameters" }, 400);
5875
6575
  }
5876
6576
  const result = await getMessagesPaginated(convId, { before, limit });
5877
- return c.json({ items: result.messages, hasMore: result.hasMore });
6577
+ return c.json({
6578
+ items: await withSenderDisplayNames(result.messages),
6579
+ hasMore: result.hasMore
6580
+ });
5878
6581
  }).get("/:name/budget", requireSelf(), async (c) => {
5879
6582
  const name = c.req.param("name");
5880
6583
  const baseName = await getBaseName(name);
@@ -5967,7 +6670,7 @@ ${user.trimEnd()}
5967
6670
  }
5968
6671
  writeVoluteConfig(dir, existing);
5969
6672
  if (body.unescapeNewlines !== void 0) {
5970
- const { clearEchoTextCache } = await import("./echo-text-B6NZ5FGE.js");
6673
+ const { clearEchoTextCache } = await import("./echo-text-WYAAODBV.js");
5971
6674
  clearEchoTextCache(name);
5972
6675
  }
5973
6676
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.compaction !== void 0;
@@ -6004,7 +6707,7 @@ ${user.trimEnd()}
6004
6707
  }
6005
6708
  if (entry.mindType === "spirit" && body.model !== void 0) {
6006
6709
  try {
6007
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
6710
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-CAJFOR2N.js");
6008
6711
  const globalConfig = readGlobalConfig2();
6009
6712
  globalConfig.spiritModel = body.model;
6010
6713
  writeGlobalConfig2(globalConfig);
@@ -6047,8 +6750,8 @@ ${user.trimEnd()}
6047
6750
  if (!body.systemPrompt || !body.message) {
6048
6751
  return c.json({ error: "systemPrompt and message required" }, 400);
6049
6752
  }
6050
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-A7FS7EC3.js");
6051
- if (!isAiConfigured()) {
6753
+ const { aiComplete: aiCompleteFn, isAiConfigured: isAiConfigured2 } = await import("./ai-service-V4CTNGI3.js");
6754
+ if (!isAiConfigured2()) {
6052
6755
  return c.json({ error: "AI service not configured" }, 503);
6053
6756
  }
6054
6757
  const text = await aiCompleteFn(body.systemPrompt, body.message, body.model);
@@ -6068,6 +6771,9 @@ ${user.trimEnd()}
6068
6771
  if (!body.type) {
6069
6772
  return c.json({ error: "type required" }, 400);
6070
6773
  }
6774
+ if (DAEMON_AUTHORED_TYPES.has(body.type)) {
6775
+ return c.json({ error: `type "${body.type}" is daemon-authored` }, 400);
6776
+ }
6071
6777
  await handleMindEvent(baseName, body);
6072
6778
  return c.json({ ok: true });
6073
6779
  }).get("/:name/events", requireSelf(), async (c) => {
@@ -6095,7 +6801,7 @@ ${user.trimEnd()}
6095
6801
  unsubscribe?.();
6096
6802
  }
6097
6803
  }, 15e3);
6098
- unsubscribe = subscribe3(baseName, (event) => {
6804
+ unsubscribe = subscribe2(baseName, (event) => {
6099
6805
  if (typeFilter && !typeFilter.includes(event.type)) return;
6100
6806
  if (sessionFilter && event.session !== sessionFilter) return;
6101
6807
  if (channelFilter && event.channel !== channelFilter) return;
@@ -6156,12 +6862,12 @@ ${user.trimEnd()}
6156
6862
  const name = c.req.param("name");
6157
6863
  const db = await getDb();
6158
6864
  const rows = await db.select({
6159
- session: mindHistory.session,
6865
+ thread: mindHistory.thread,
6160
6866
  started_at: sql3`MIN(${mindHistory.created_at})`,
6161
6867
  event_count: sql3`COUNT(*)`,
6162
6868
  message_count: sql3`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
6163
6869
  tool_count: sql3`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
6164
- }).from(mindHistory).where(and5(eq5(mindHistory.mind, name), sql3`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql3`MIN(${mindHistory.created_at}) DESC`);
6870
+ }).from(mindHistory).where(and5(eq5(mindHistory.mind, name), sql3`${mindHistory.thread} IS NOT NULL`)).groupBy(mindHistory.thread).orderBy(sql3`MIN(${mindHistory.created_at}) DESC`);
6165
6871
  return c.json(rows);
6166
6872
  }).get("/:name/history/channels", requireSelf(), async (c) => {
6167
6873
  const name = c.req.param("name");
@@ -6211,7 +6917,7 @@ ${user.trimEnd()}
6211
6917
  const turnId = c.req.query("turn_id");
6212
6918
  const detail = c.req.query("detail") === "1";
6213
6919
  const db = await getDb();
6214
- const typeFilter = detail ? void 0 : sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking','activity')`;
6920
+ const typeFilter = detail ? void 0 : sql3`${mindHistory.type} IN ('inbound','event','outbound','tool_use','tool_result','text','thinking','activity')`;
6215
6921
  let rows;
6216
6922
  if (turnId) {
6217
6923
  rows = await db.select().from(mindHistory).where(and5(eq5(mindHistory.mind, name), eq5(mindHistory.turn_id, turnId), typeFilter)).orderBy(mindHistory.id);
@@ -6225,7 +6931,7 @@ ${user.trimEnd()}
6225
6931
  rows = await db.select().from(mindHistory).where(
6226
6932
  and5(
6227
6933
  eq5(mindHistory.mind, name),
6228
- eq5(mindHistory.session, session),
6934
+ eq5(mindHistory.thread, session),
6229
6935
  sql3`${mindHistory.id} >= ${fromId}`,
6230
6936
  sql3`${mindHistory.id} <= ${toId}`,
6231
6937
  typeFilter
@@ -6242,7 +6948,7 @@ ${user.trimEnd()}
6242
6948
  const lastTurn = await db.select({ turn_id: mindHistory.turn_id }).from(mindHistory).where(
6243
6949
  and5(
6244
6950
  eq5(mindHistory.mind, name),
6245
- eq5(mindHistory.session, currentSession),
6951
+ eq5(mindHistory.thread, currentSession),
6246
6952
  sql3`${mindHistory.turn_id} IS NOT NULL`
6247
6953
  )
6248
6954
  ).orderBy(desc3(mindHistory.created_at)).limit(1);
@@ -6262,10 +6968,10 @@ ${user.trimEnd()}
6262
6968
  sql3`${summaries.created_at} > ${sinceTimestamp}`
6263
6969
  ];
6264
6970
  if (currentSession) {
6265
- conditions.push(sql3`${turns.session} != ${currentSession}`);
6971
+ conditions.push(sql3`${turns.thread} != ${currentSession}`);
6266
6972
  }
6267
6973
  const rows = await db.select({
6268
- session: turns.session,
6974
+ thread: turns.thread,
6269
6975
  content: summaries.content,
6270
6976
  created_at: summaries.created_at
6271
6977
  }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...conditions)).orderBy(desc3(summaries.created_at)).limit(50);
@@ -6273,9 +6979,9 @@ ${user.trimEnd()}
6273
6979
  return c.json({ context: null });
6274
6980
  }
6275
6981
  const lines = rows.map((row) => {
6276
- const ts = new Date(row.created_at.endsWith("Z") ? row.created_at : `${row.created_at}Z`);
6982
+ const ts = parseDbTimestamp(row.created_at);
6277
6983
  const ago = formatTimeAgo(ts);
6278
- return `- ${row.session ?? "unknown"} (${ago}): ${row.content ?? ""}`;
6984
+ return `- ${row.thread ?? "unknown"} (${ago}): ${row.content ?? ""}`;
6279
6985
  });
6280
6986
  return c.json({ context: `[Session Activity]
6281
6987
  ${lines.join("\n")}` });
@@ -6284,11 +6990,33 @@ ${lines.join("\n")}` });
6284
6990
  const baseName = await getBaseName(name);
6285
6991
  const session = c.req.query("session");
6286
6992
  if (!session) return c.json({ context: null, notices: [] });
6287
- const notices = await drainNotices(baseName, session);
6993
+ const notices = await drainEvents(baseName, session);
6288
6994
  if (notices.length === 0) return c.json({ context: null, notices: [] });
6289
6995
  const maxId = notices.reduce((m, n) => Math.max(m, n.id), 0);
6290
6996
  setNoticeDrainWatermark(baseName, session, maxId);
6291
- return c.json({ context: formatNotices(notices), notices });
6997
+ return c.json({ context: formatEvents(notices), notices });
6998
+ }).get("/:name/system-events", requireSelf(), async (c) => {
6999
+ const name = c.req.param("name");
7000
+ const baseName = await getBaseName(name);
7001
+ const limit = Math.min(Number(c.req.query("limit") ?? 100) || 100, 200);
7002
+ const before = c.req.query("before") ? Number(c.req.query("before")) : void 0;
7003
+ const events = await listEvents(baseName, limit, before);
7004
+ return c.json({
7005
+ events: events.map((e) => {
7006
+ const meta = parseMeta(e.meta, `event ${e.id}`);
7007
+ return {
7008
+ id: e.id,
7009
+ type: e.type,
7010
+ label: eventLabel(e.type, meta),
7011
+ body: e.body,
7012
+ meta,
7013
+ delivery: e.delivery,
7014
+ reflection: e.reflection,
7015
+ created_at: e.created_at,
7016
+ delivered_at: e.delivered_at
7017
+ };
7018
+ })
7019
+ });
6292
7020
  }).get("/:name/history", requireSelf(), async (c) => {
6293
7021
  const name = c.req.param("name");
6294
7022
  const channel = c.req.query("channel");
@@ -6303,13 +7031,13 @@ ${lines.join("\n")}` });
6303
7031
  conditions.push(eq5(mindHistory.channel, channel));
6304
7032
  }
6305
7033
  if (session) {
6306
- conditions.push(eq5(mindHistory.session, session));
7034
+ conditions.push(eq5(mindHistory.thread, session));
6307
7035
  }
6308
7036
  const effectivePreset = full ? "all" : preset;
6309
7037
  if (!effectivePreset || effectivePreset === "summary") {
6310
7038
  const sumConditions = [eq5(summaries.mind, name), eq5(summaries.period, "turn")];
6311
7039
  if (session) {
6312
- sumConditions.push(eq5(turns.session, session));
7040
+ sumConditions.push(eq5(turns.thread, session));
6313
7041
  const sumRows2 = await db.select({
6314
7042
  id: summaries.id,
6315
7043
  mind: summaries.mind,
@@ -6318,7 +7046,7 @@ ${lines.join("\n")}` });
6318
7046
  content: summaries.content,
6319
7047
  metadata: summaries.metadata,
6320
7048
  created_at: summaries.created_at,
6321
- session: turns.session
7049
+ thread: turns.thread
6322
7050
  }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...sumConditions)).orderBy(desc3(summaries.created_at)).limit(limit).offset(offset);
6323
7051
  return c.json(
6324
7052
  sumRows2.map((r) => ({
@@ -6326,7 +7054,7 @@ ${lines.join("\n")}` });
6326
7054
  mind: r.mind,
6327
7055
  type: "summary",
6328
7056
  channel: null,
6329
- session: r.session,
7057
+ thread: r.thread,
6330
7058
  sender: null,
6331
7059
  message_id: null,
6332
7060
  content: r.content,
@@ -6343,7 +7071,7 @@ ${lines.join("\n")}` });
6343
7071
  mind: r.mind,
6344
7072
  type: "summary",
6345
7073
  channel: null,
6346
- session: null,
7074
+ thread: null,
6347
7075
  sender: null,
6348
7076
  message_id: null,
6349
7077
  content: r.content,
@@ -6357,16 +7085,22 @@ ${lines.join("\n")}` });
6357
7085
  case "all":
6358
7086
  break;
6359
7087
  case "conversation":
6360
- conditions.push(sql3`${mindHistory.type} IN ('inbound','outbound','tool_use')`);
7088
+ conditions.push(sql3`${mindHistory.type} IN ('inbound','event','outbound','tool_use')`);
6361
7089
  break;
6362
7090
  case "detailed":
6363
7091
  conditions.push(
6364
- sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
7092
+ sql3`${mindHistory.type} IN ('inbound','event','outbound','tool_use','tool_result','text','thinking')`
6365
7093
  );
6366
7094
  break;
6367
7095
  }
6368
7096
  const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
6369
- return c.json(rows);
7097
+ const displayNames = await getDisplayNames(rows.map((r) => r.sender));
7098
+ return c.json(
7099
+ rows.map((r) => ({
7100
+ ...r,
7101
+ sender_display_name: r.sender ? displayNames.get(r.sender) ?? null : null
7102
+ }))
7103
+ );
6370
7104
  });
6371
7105
  var minds_default = app13;
6372
7106
 
@@ -6428,9 +7162,9 @@ var slog2 = logger_default.child("schedules");
6428
7162
  function readSchedules(dir) {
6429
7163
  return readVoluteConfig(dir)?.schedules ?? [];
6430
7164
  }
6431
- function validateMessages(messages) {
6432
- if (messages === void 0) return null;
6433
- if (!Array.isArray(messages) || messages.some((m) => typeof m !== "string" || !m.trim())) {
7165
+ function validateMessages(messages2) {
7166
+ if (messages2 === void 0) return null;
7167
+ if (!Array.isArray(messages2) || messages2.some((m) => typeof m !== "string" || !m.trim())) {
6434
7168
  return "messages must be an array of non-empty strings";
6435
7169
  }
6436
7170
  return null;
@@ -6616,6 +7350,7 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6616
7350
  if (body.messages?.length) schedule.messages = body.messages;
6617
7351
  if (body.script) schedule.script = body.script;
6618
7352
  if (body.channel) schedule.channel = body.channel;
7353
+ if (body.thread) schedule.thread = body.thread;
6619
7354
  if (body.whileSleeping) schedule.whileSleeping = body.whileSleeping;
6620
7355
  schedules.push(schedule);
6621
7356
  writeSchedules(name, dir, schedules);
@@ -6680,6 +7415,7 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6680
7415
  }
6681
7416
  if (body.enabled !== void 0) schedules[idx].enabled = body.enabled;
6682
7417
  if (body.channel !== void 0) schedules[idx].channel = body.channel || void 0;
7418
+ if (body.thread !== void 0) schedules[idx].thread = body.thread || void 0;
6683
7419
  if (body.whileSleeping !== void 0)
6684
7420
  schedules[idx].whileSleeping = body.whileSleeping || void 0;
6685
7421
  const touchedAction = body.message !== void 0 || body.messages !== void 0 || body.script !== void 0;
@@ -6708,15 +7444,18 @@ var app15 = new Hono18().get("/:name/clock/status", requireSelf(), async (c) =>
6708
7444
  const entry = await findMind(name);
6709
7445
  if (!entry) return c.json({ error: "Mind not found" }, 404);
6710
7446
  const body = await c.req.text();
6711
- const message = `[webhook: ${event}] ${body}`;
6712
- try {
6713
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
6714
- await sendSystemMessage2(name, message);
6715
- return c.json({ ok: true });
6716
- } catch (err) {
6717
- slog2.warn(`webhook delivery failed for ${name}`, logger_default.errorData(err));
6718
- return c.json({ error: "Failed to reach mind" }, 502);
7447
+ const { deliverEvent: deliverEvent2 } = await import("./system-events-A6IGCEC3.js");
7448
+ const { id, delivered } = await deliverEvent2(name, {
7449
+ type: "webhook",
7450
+ body,
7451
+ meta: { source: event }
7452
+ });
7453
+ if (id == null) {
7454
+ slog2.warn(`webhook event for ${name} could not be recorded`);
7455
+ return c.json({ error: "Failed to record event" }, 502);
6719
7456
  }
7457
+ if (!delivered) return c.json({ ok: true, delivered: false, id }, 202);
7458
+ return c.json({ ok: true, delivered: true, id });
6720
7459
  });
6721
7460
  var schedules_default = app15;
6722
7461
 
@@ -6764,8 +7503,8 @@ setup.get("/status", async (c) => {
6764
7503
  let hasAccount = false;
6765
7504
  if (hasSystem) {
6766
7505
  try {
6767
- const { listUsersByType: listUsersByType2 } = await import("./auth-ABYLOEZ7.js");
6768
- const brains = await listUsersByType2("brain");
7506
+ const { listUsersByType: listUsersByType2 } = await import("./auth-DT7GY7EZ.js");
7507
+ const brains = await listUsersByType2("human");
6769
7508
  hasAccount = brains.length > 0;
6770
7509
  } catch (err) {
6771
7510
  logger_default.debug("could not check for existing accounts during setup status", logger_default.errorData(err));
@@ -6775,7 +7514,8 @@ setup.get("/status", async (c) => {
6775
7514
  complete,
6776
7515
  hasSystem,
6777
7516
  hasAccount,
6778
- setupType: config2.setup?.type ?? null
7517
+ setupType: config2.setup?.type ?? null,
7518
+ spiritName: config2.setup?.spiritName ?? null
6779
7519
  });
6780
7520
  });
6781
7521
  setup.post("/configure", async (c) => {
@@ -6975,7 +7715,7 @@ setup.post("/account", async (c) => {
6975
7715
  }
6976
7716
  }
6977
7717
  try {
6978
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-ABYLOEZ7.js");
7718
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-DT7GY7EZ.js");
6979
7719
  const user = await createUser2(body.username.trim(), body.password);
6980
7720
  if (body.displayName?.trim()) {
6981
7721
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -7021,7 +7761,7 @@ setup.post("/models", async (c) => {
7021
7761
  return c.json({ error: "Spirit model is required" }, 400);
7022
7762
  }
7023
7763
  try {
7024
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-A7FS7EC3.js");
7764
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-V4CTNGI3.js");
7025
7765
  setEnabledModels3(body.models);
7026
7766
  const config2 = readGlobalConfig();
7027
7767
  config2.spiritModel = body.spiritModel.trim();
@@ -7034,20 +7774,49 @@ setup.post("/models", async (c) => {
7034
7774
  return c.json({ error: `Failed to save models: ${err.message}` }, 500);
7035
7775
  }
7036
7776
  });
7777
+ setup.post("/spirit", async (c) => {
7778
+ if (isSetupComplete()) {
7779
+ return c.json({ error: "Setup already complete" }, 400);
7780
+ }
7781
+ let body;
7782
+ try {
7783
+ body = await c.req.json();
7784
+ } catch {
7785
+ return c.json({ error: "Invalid JSON in request body" }, 400);
7786
+ }
7787
+ const name = body.name?.trim() ?? "";
7788
+ const nameErr = validateSpiritName(name);
7789
+ if (nameErr) {
7790
+ return c.json({ error: nameErr }, 400);
7791
+ }
7792
+ const config2 = readGlobalConfig();
7793
+ if (!config2.setup) {
7794
+ return c.json({ error: "Complete the system step first" }, 400);
7795
+ }
7796
+ const existing = await findMind(name);
7797
+ if (existing && existing.mindType !== "spirit") {
7798
+ return c.json({ error: `A mind named "${name}" already exists` }, 400);
7799
+ }
7800
+ config2.setup.spiritName = name;
7801
+ const temperament = body.temperament?.trim();
7802
+ config2.setup.spiritTemperament = temperament || void 0;
7803
+ writeGlobalConfig(config2);
7804
+ return c.json({ ok: true });
7805
+ });
7037
7806
  setup.post("/complete", async (c) => {
7038
7807
  if (isSetupComplete()) {
7039
7808
  return c.json({ error: "Setup already complete" }, 400);
7040
7809
  }
7041
7810
  try {
7042
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
7043
- const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
7811
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-IL5EMWYZ.js");
7812
+ const { startSpiritFull } = await import("./mind-service-5TA7UJAT.js");
7044
7813
  await ensureSpiritProject();
7045
7814
  await syncSpiritTemplate();
7046
7815
  const warnings = [];
7047
7816
  const config2 = readGlobalConfig();
7048
7817
  if (config2.setup?.service && config2.setup?.type === "local") {
7049
7818
  try {
7050
- const { installUserService } = await import("./service-install-Y46OTF55.js");
7819
+ const { installUserService } = await import("./service-install-MOKTEG7B.js");
7051
7820
  const installed = await installUserService(config2.port, config2.hostname);
7052
7821
  if (!installed) {
7053
7822
  warnings.push("Service installation is not supported on this platform.");
@@ -7063,10 +7832,11 @@ setup.post("/complete", async (c) => {
7063
7832
  writeGlobalConfig(config2);
7064
7833
  }
7065
7834
  }
7835
+ const spiritName = getSpiritName();
7066
7836
  let spiritStarted = false;
7067
7837
  try {
7068
7838
  logger_default.info("starting spirit during setup...");
7069
- await startSpiritFull("volute");
7839
+ await startSpiritFull(spiritName);
7070
7840
  spiritStarted = true;
7071
7841
  logger_default.info("spirit started successfully during setup");
7072
7842
  } catch (err) {
@@ -7076,10 +7846,10 @@ setup.post("/complete", async (c) => {
7076
7846
  }
7077
7847
  let spiritConversationId;
7078
7848
  try {
7079
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-ABYLOEZ7.js");
7080
- const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-TUM5PJJ4.js");
7081
- const spiritUser = await getOrCreateMindUser2("volute");
7082
- const brains = await listUsersByType2("brain");
7849
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-DT7GY7EZ.js");
7850
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-S2232Y5I.js");
7851
+ const spiritUser = await getOrCreateMindUser2(spiritName);
7852
+ const brains = await listUsersByType2("human");
7083
7853
  const admin2 = brains.find((u) => u.role === "admin");
7084
7854
  if (admin2) {
7085
7855
  const existing = await findDMConversation2([admin2.id, spiritUser.id]);
@@ -7099,20 +7869,22 @@ setup.post("/complete", async (c) => {
7099
7869
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
7100
7870
  if (spiritConversationId && spiritStarted) {
7101
7871
  try {
7102
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-ABPV76EA.js");
7103
- const { listUsersByType: listUsers2 } = await import("./auth-ABYLOEZ7.js");
7104
- const admins = await listUsers2("brain");
7872
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-3456PXB7.js");
7873
+ const { listUsersByType: listUsers2 } = await import("./auth-DT7GY7EZ.js");
7874
+ const admins = await listUsers2("human");
7105
7875
  const admin2 = admins.find((u) => u.role === "admin");
7106
7876
  if (admin2) {
7107
7877
  const adminName = admin2.display_name || admin2.username;
7108
- const welcomePrompt = `Setup is complete. ${adminName} just finished setting up this system and is now in your chat. Welcome them and help them create their first mind. Keep it brief \u2014 a short greeting, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
7109
- await deliverMessage2(SPIRIT_NAME, {
7878
+ const cfg = readGlobalConfig();
7879
+ const temperament = cfg.setup?.spiritTemperament;
7880
+ const welcomePrompt = `Setup is complete. You are ${spiritName}, the spirit of ${cfg.name ?? "this system"}` + (cfg.description ? ` \u2014 ${cfg.description}` : "") + `. ${adminName} chose your name just now` + (temperament ? ` and imagined your temperament as: "${temperament}"` : "") + `, and is now in your chat. Greet ${adminName} by name and help them create their first mind. Keep it brief \u2014 a short greeting in your own voice, then ask what kind of being they'd like to bring into the world. Remember: minds are beings with their own experience, not tools \u2014 ask about personality and character, never about tasks or utility.`;
7881
+ await deliverMessage2(spiritName, {
7110
7882
  content: [{ type: "text", text: welcomePrompt }],
7111
7883
  channel: `@${admin2.username}`,
7112
7884
  conversationId: spiritConversationId,
7113
7885
  sender: admin2.username,
7114
7886
  isDM: true,
7115
- participants: [SPIRIT_NAME, admin2.username],
7887
+ participants: [spiritName, admin2.username],
7116
7888
  participantCount: 2
7117
7889
  });
7118
7890
  }
@@ -7285,7 +8057,7 @@ var app17 = new Hono21().post("/:name/typing", zValidator8("json", typingSchema)
7285
8057
  map.delete(channel, sender);
7286
8058
  }
7287
8059
  if (isConversationId(channel)) {
7288
- publish2(channel, { type: "typing", senders: map.get(channel) });
8060
+ publish3(channel, { type: "typing", senders: map.get(channel) });
7289
8061
  }
7290
8062
  return c.json({ ok: true });
7291
8063
  }).get("/:name/typing", (c) => {
@@ -7326,6 +8098,13 @@ import { z as z9 } from "zod";
7326
8098
  var createSchema = z9.object({
7327
8099
  participantNames: z9.array(z9.string()).min(1)
7328
8100
  });
8101
+ async function canReadConversation(id, user) {
8102
+ const conv = await getConversation(id);
8103
+ if (!conv) return false;
8104
+ if (conv.private !== 1) return true;
8105
+ if (user.id === 0 || user.role === "admin" || user.role === "system") return true;
8106
+ return isParticipantOrOwner(id, user.id);
8107
+ }
7329
8108
  var app19 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
7330
8109
  const user = c.get("user");
7331
8110
  const convs = await listConversationsWithParticipants(user.id);
@@ -7333,7 +8112,7 @@ var app19 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
7333
8112
  }).get("/:id/messages", async (c) => {
7334
8113
  const id = c.req.param("id");
7335
8114
  const user = c.get("user");
7336
- if (user.id !== 0 && !await isParticipantOrOwner(id, user.id)) {
8115
+ if (!await canReadConversation(id, user)) {
7337
8116
  return c.json({ error: "Conversation not found" }, 404);
7338
8117
  }
7339
8118
  const beforeStr = c.req.query("before");
@@ -7348,7 +8127,7 @@ var app19 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
7348
8127
  }).get("/:id/participants", async (c) => {
7349
8128
  const id = c.req.param("id");
7350
8129
  const user = c.get("user");
7351
- if (user.id !== 0 && !await isParticipantOrOwner(id, user.id)) {
8130
+ if (!await canReadConversation(id, user)) {
7352
8131
  return c.json({ error: "Conversation not found" }, 404);
7353
8132
  }
7354
8133
  const participants = await getParticipants(id);
@@ -7457,11 +8236,25 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7457
8236
  const activityMind = privileged ? void 0 : await getBaseName(user.username);
7458
8237
  return streamSSE4(c, async (stream) => {
7459
8238
  const cleanups = [];
7460
- if (user.user_type === "brain") {
8239
+ if (user.user_type === "human") {
7461
8240
  addConnection(user.username);
7462
8241
  cleanups.push(() => removeConnection(user.username));
7463
8242
  }
7464
8243
  try {
8244
+ let subscribeToConversation2 = function(convId) {
8245
+ const unsubConv = subscribe3(convId, (event) => {
8246
+ const { seq, ...rest } = event;
8247
+ const data = { event: "conversation", conversationId: convId, ...rest };
8248
+ stream.writeSSE({
8249
+ id: String(seq),
8250
+ data: JSON.stringify(data)
8251
+ }).catch((err) => {
8252
+ if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
8253
+ });
8254
+ });
8255
+ cleanups.push(unsubConv);
8256
+ };
8257
+ var subscribeToConversation = subscribeToConversation2;
7465
8258
  let conversations2 = [];
7466
8259
  try {
7467
8260
  conversations2 = await listConversationsWithParticipants(user.id);
@@ -7524,19 +8317,46 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7524
8317
  });
7525
8318
  });
7526
8319
  cleanups.push(unsubActivity);
8320
+ const subscribedConvIds = new Set(conversations2.map((c2) => c2.id));
7527
8321
  for (const conv of conversations2) {
7528
- const unsubConv = subscribe2(conv.id, (event) => {
8322
+ subscribeToConversation2(conv.id);
8323
+ }
8324
+ const unsubParticipant = subscribeParticipantAdded(({ conversationId, userId }) => {
8325
+ if (userId !== user.id || subscribedConvIds.has(conversationId)) return;
8326
+ subscribedConvIds.add(conversationId);
8327
+ function forward(event) {
7529
8328
  const { seq, ...rest } = event;
7530
- const data = { event: "conversation", conversationId: conv.id, ...rest };
7531
- stream.writeSSE({
7532
- id: String(seq),
7533
- data: JSON.stringify(data)
7534
- }).catch((err) => {
8329
+ const data = { event: "conversation", conversationId, ...rest };
8330
+ stream.writeSSE({ id: String(seq), data: JSON.stringify(data) }).catch((err) => {
7535
8331
  if (!stream.aborted) logger_default.error("[v1-events] write error:", logger_default.errorData(err));
7536
8332
  });
8333
+ }
8334
+ let queued = [];
8335
+ const unsubConv = subscribe3(conversationId, (event) => {
8336
+ if (queued) queued.push(event);
8337
+ else forward(event);
7537
8338
  });
7538
8339
  cleanups.push(unsubConv);
7539
- }
8340
+ getConversationWithParticipants(conversationId).then((conv) => {
8341
+ if (!conv) {
8342
+ logger_default.error("[v1-events] conversation_added: enrich returned null", { conversationId });
8343
+ return;
8344
+ }
8345
+ const data = {
8346
+ event: "conversation_added",
8347
+ conversation: conv
8348
+ };
8349
+ return stream.writeSSE({ id: String(nextEventId()), data: JSON.stringify(data) });
8350
+ }).catch((err) => {
8351
+ if (!stream.aborted)
8352
+ logger_default.error("[v1-events] conversation_added error:", logger_default.errorData(err));
8353
+ }).finally(() => {
8354
+ const pending = queued ?? [];
8355
+ queued = null;
8356
+ for (const event of pending) forward(event);
8357
+ });
8358
+ });
8359
+ cleanups.push(unsubParticipant);
7540
8360
  const keepAlive = setInterval(() => {
7541
8361
  stream.writeSSE({ data: "" }).catch((err) => {
7542
8362
  if (!stream.aborted) logger_default.error("[v1-events] ping error:", logger_default.errorData(err));
@@ -7558,10 +8378,270 @@ var app20 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
7558
8378
  });
7559
8379
  var events_default = app20;
7560
8380
 
8381
+ // packages/daemon/src/web/api/v1/feed.ts
8382
+ import { Hono as Hono25 } from "hono";
8383
+
8384
+ // packages/daemon/src/lib/events/feed.ts
8385
+ import { and as and6, desc as desc5, eq as eq8, gte as gte3, inArray as inArray3, sql as sql5 } from "drizzle-orm";
8386
+ var BURST_GAP_MS = 45 * 6e4;
8387
+ var FEED_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
8388
+ var LIFECYCLE_TYPES = [
8389
+ "mind_started",
8390
+ "mind_stopped",
8391
+ "mind_sleeping",
8392
+ "mind_waking",
8393
+ "mind_error",
8394
+ "backup_failed"
8395
+ ];
8396
+ var SNIPPET_MESSAGES = 3;
8397
+ var SNIPPET_CHARS = 280;
8398
+ var DIGEST_MIND = "system";
8399
+ function splitIntoBursts(rows, gapMs = BURST_GAP_MS) {
8400
+ const byConv = /* @__PURE__ */ new Map();
8401
+ for (const row of rows) {
8402
+ let arr = byConv.get(row.conversation_id);
8403
+ if (!arr) {
8404
+ arr = [];
8405
+ byConv.set(row.conversation_id, arr);
8406
+ }
8407
+ arr.push(row);
8408
+ }
8409
+ const bursts = [];
8410
+ for (const [conversationId, msgs] of byConv) {
8411
+ let current = [];
8412
+ let prevTime = 0;
8413
+ for (const msg of msgs) {
8414
+ const t = parseUtcDateTime(msg.created_at).getTime();
8415
+ if (current.length > 0 && t - prevTime > gapMs) {
8416
+ bursts.push(makeBurst(conversationId, current));
8417
+ current = [];
8418
+ }
8419
+ current.push(msg);
8420
+ prevTime = t;
8421
+ }
8422
+ if (current.length > 0) bursts.push(makeBurst(conversationId, current));
8423
+ }
8424
+ return bursts;
8425
+ }
8426
+ function makeBurst(conversationId, msgs) {
8427
+ return {
8428
+ conversationId,
8429
+ messages: msgs,
8430
+ startedAt: msgs[0].created_at,
8431
+ endedAt: msgs[msgs.length - 1].created_at,
8432
+ messageCount: msgs.length
8433
+ };
8434
+ }
8435
+ function extractText(raw) {
8436
+ try {
8437
+ const parsed = JSON.parse(raw);
8438
+ if (Array.isArray(parsed)) {
8439
+ const block = parsed.find((b) => b?.type === "text");
8440
+ return block && typeof block.text === "string" ? block.text : "";
8441
+ }
8442
+ return raw;
8443
+ } catch {
8444
+ return raw;
8445
+ }
8446
+ }
8447
+ function truncate(text, max) {
8448
+ const t = text.trim();
8449
+ return t.length > max ? `${t.slice(0, max).trimEnd()}\u2026` : t;
8450
+ }
8451
+ async function getChatFeedEvents(opts) {
8452
+ const db = await getDb();
8453
+ const cutoff = utcDateTimeStr(new Date(Date.now() - FEED_WINDOW_MS));
8454
+ const rows = await db.select({
8455
+ id: messages.id,
8456
+ conversation_id: messages.conversation_id,
8457
+ role: messages.role,
8458
+ sender_name: messages.sender_name,
8459
+ content: messages.content,
8460
+ created_at: messages.created_at,
8461
+ conversation_type: conversations.type
8462
+ }).from(messages).innerJoin(conversations, eq8(conversations.id, messages.conversation_id)).where(and6(gte3(messages.created_at, cutoff), eq8(conversations.private, 0))).orderBy(desc5(messages.id)).limit(5e3);
8463
+ const ascending = rows.reverse();
8464
+ const bursts = splitIntoBursts(ascending);
8465
+ bursts.sort((a, b) => {
8466
+ const at = parseUtcDateTime(a.endedAt).getTime();
8467
+ const bt = parseUtcDateTime(b.endedAt).getTime();
8468
+ if (bt !== at) return bt - at;
8469
+ return lastId(b) - lastId(a);
8470
+ });
8471
+ const top = bursts.slice(0, opts?.limit ?? 50);
8472
+ if (top.length === 0) return [];
8473
+ const convIds = [...new Set(top.map((b) => b.conversationId))];
8474
+ const { participantsByConv, channelByConv } = await enrichBurstConversations(convIds);
8475
+ return top.map((burst) => {
8476
+ const snippetMsgs = burst.messages.slice(-SNIPPET_MESSAGES);
8477
+ const activeSenders = [
8478
+ ...new Set(burst.messages.map((m) => m.sender_name).filter((s) => !!s))
8479
+ ];
8480
+ return {
8481
+ kind: "chat",
8482
+ conversationId: burst.conversationId,
8483
+ conversationType: burst.messages[0].conversation_type === "channel" ? "channel" : "dm",
8484
+ channelName: channelByConv.get(burst.conversationId) ?? null,
8485
+ participants: participantsByConv.get(burst.conversationId) ?? [],
8486
+ activeSenders,
8487
+ messageCount: burst.messageCount,
8488
+ startedAt: burst.startedAt,
8489
+ endedAt: burst.endedAt,
8490
+ snippet: snippetMsgs.map((m) => ({
8491
+ role: m.role,
8492
+ senderName: m.sender_name,
8493
+ text: truncate(extractText(m.content), SNIPPET_CHARS),
8494
+ createdAt: m.created_at
8495
+ }))
8496
+ };
8497
+ });
8498
+ }
8499
+ function lastId(burst) {
8500
+ return burst.messages[burst.messages.length - 1].id;
8501
+ }
8502
+ async function enrichBurstConversations(convIds) {
8503
+ const db = await getDb();
8504
+ const rows = await db.select({
8505
+ conversationId: conversationParticipants.conversation_id,
8506
+ userId: users.id,
8507
+ username: users.username,
8508
+ userType: users.user_type,
8509
+ role: conversationParticipants.role,
8510
+ displayName: users.display_name,
8511
+ description: users.description,
8512
+ avatar: users.avatar,
8513
+ channelName: channels.name
8514
+ }).from(conversationParticipants).innerJoin(users, eq8(conversationParticipants.user_id, users.id)).leftJoin(channels, eq8(channels.conversation_id, conversationParticipants.conversation_id)).where(inArray3(conversationParticipants.conversation_id, convIds));
8515
+ const participantsByConv = /* @__PURE__ */ new Map();
8516
+ const channelByConv = /* @__PURE__ */ new Map();
8517
+ for (const r of rows) {
8518
+ let arr = participantsByConv.get(r.conversationId);
8519
+ if (!arr) {
8520
+ arr = [];
8521
+ participantsByConv.set(r.conversationId, arr);
8522
+ }
8523
+ if (!arr.some((p) => p.userId === r.userId)) {
8524
+ arr.push({
8525
+ userId: r.userId,
8526
+ username: r.username,
8527
+ userType: r.userType,
8528
+ role: r.role,
8529
+ displayName: r.displayName,
8530
+ description: r.description,
8531
+ avatar: r.avatar
8532
+ });
8533
+ }
8534
+ if (r.channelName && !channelByConv.has(r.conversationId)) {
8535
+ channelByConv.set(r.conversationId, r.channelName);
8536
+ }
8537
+ }
8538
+ return { participantsByConv, channelByConv };
8539
+ }
8540
+ async function getLifecycleFeedEvents(opts) {
8541
+ const db = await getDb();
8542
+ const cutoff = utcDateTimeStr(new Date(Date.now() - FEED_WINDOW_MS));
8543
+ const rows = await db.select({
8544
+ id: activity.id,
8545
+ type: activity.type,
8546
+ mind: activity.mind,
8547
+ summary: activity.summary,
8548
+ created_at: activity.created_at
8549
+ }).from(activity).where(and6(inArray3(activity.type, [...LIFECYCLE_TYPES]), gte3(activity.created_at, cutoff))).orderBy(desc5(activity.created_at), desc5(activity.id)).limit(opts.limit ?? 100);
8550
+ return rows.map((r) => ({
8551
+ kind: "lifecycle",
8552
+ id: r.id,
8553
+ type: r.type,
8554
+ mind: r.mind,
8555
+ summary: redactLifecycleSummary(r.type, r.summary, opts.privileged),
8556
+ createdAt: r.created_at
8557
+ }));
8558
+ }
8559
+ function redactLifecycleSummary(type, summary, privileged) {
8560
+ if (privileged) return summary;
8561
+ if (type === "mind_error") return "An error interrupted this mind.";
8562
+ if (type === "backup_failed") return "A scheduled backup failed.";
8563
+ return summary;
8564
+ }
8565
+ async function getDailyDigest(complete = aiCompleteUtility) {
8566
+ const db = await getDb();
8567
+ const periodKey = getPeriodKey(/* @__PURE__ */ new Date(), "day");
8568
+ const cached = await db.select({ content: summaries.content }).from(summaries).where(
8569
+ and6(
8570
+ eq8(summaries.mind, DIGEST_MIND),
8571
+ eq8(summaries.period, "day"),
8572
+ eq8(summaries.period_key, periodKey)
8573
+ )
8574
+ ).get();
8575
+ if (cached) return { content: cached.content };
8576
+ const cutoff = utcDateTimeStr(new Date(Date.now() - 24 * 60 * 60 * 1e3));
8577
+ const hourRows = await db.select({ content: summaries.content }).from(summaries).where(
8578
+ and6(
8579
+ eq8(summaries.mind, SYSTEM_MIND),
8580
+ eq8(summaries.period, "hour"),
8581
+ gte3(summaries.created_at, cutoff)
8582
+ )
8583
+ ).orderBy(summaries.period_key);
8584
+ let material = hourRows.map((r) => r.content).join("\n\n");
8585
+ if (!material.trim()) {
8586
+ const turnRows = await db.select({ mind: summaries.mind, content: summaries.content }).from(summaries).where(
8587
+ and6(
8588
+ eq8(summaries.period, "turn"),
8589
+ gte3(summaries.created_at, cutoff),
8590
+ sql5`${summaries.mind} != ${SYSTEM_MIND}`
8591
+ )
8592
+ ).orderBy(desc5(summaries.created_at)).limit(40);
8593
+ material = turnRows.map((r) => `[${r.mind}] ${truncate(r.content, 300)}`).join("\n");
8594
+ }
8595
+ if (!material.trim()) return { content: "" };
8596
+ const aiResult = await complete(await getPrompt("system_daily_digest"), material);
8597
+ if (aiResult) {
8598
+ await db.insert(summaries).values({ mind: DIGEST_MIND, period: "day", period_key: periodKey, content: aiResult }).onConflictDoNothing();
8599
+ return { content: aiResult };
8600
+ }
8601
+ const counts = await digestCounts(cutoff);
8602
+ const mindWord = counts.minds === 1 ? "mind was" : "minds were";
8603
+ const convWord = counts.conversations === 1 ? "conversation" : "conversations";
8604
+ return {
8605
+ content: `${counts.minds} ${mindWord} active across ${counts.conversations} ${convWord} today.`
8606
+ };
8607
+ }
8608
+ async function digestCounts(cutoff) {
8609
+ const db = await getDb();
8610
+ const convRows = await db.select({ id: messages.conversation_id }).from(messages).where(gte3(messages.created_at, cutoff)).groupBy(messages.conversation_id);
8611
+ const mindRows = await db.select({ mind: summaries.mind }).from(summaries).where(
8612
+ and6(
8613
+ eq8(summaries.period, "turn"),
8614
+ gte3(summaries.created_at, cutoff),
8615
+ sql5`${summaries.mind} != ${SYSTEM_MIND}`
8616
+ )
8617
+ ).groupBy(summaries.mind);
8618
+ return { minds: mindRows.length, conversations: convRows.length };
8619
+ }
8620
+
8621
+ // packages/daemon/src/web/api/v1/feed.ts
8622
+ function eventTime(e) {
8623
+ return e.kind === "chat" ? e.endedAt : e.createdAt;
8624
+ }
8625
+ var app21 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
8626
+ const user = c.get("user");
8627
+ const privileged = user.role === "admin" || user.role === "system";
8628
+ const [chat, lifecycle] = await Promise.all([
8629
+ getChatFeedEvents(),
8630
+ getLifecycleFeedEvents({ privileged })
8631
+ ]);
8632
+ const events = [...chat, ...lifecycle].sort(
8633
+ (a, b) => parseUtcDateTime(eventTime(b)).getTime() - parseUtcDateTime(eventTime(a)).getTime()
8634
+ );
8635
+ return c.json({ events });
8636
+ }).get("/digest", async (c) => {
8637
+ return c.json(await getDailyDigest());
8638
+ });
8639
+ var feed_default = app21;
8640
+
7561
8641
  // packages/daemon/src/web/api/variants.ts
7562
8642
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
7563
8643
  import { resolve as resolve16 } from "path";
7564
- import { Hono as Hono25 } from "hono";
8644
+ import { Hono as Hono26 } from "hono";
7565
8645
 
7566
8646
  // packages/daemon/src/lib/mind/farewell.ts
7567
8647
  import { existsSync as existsSync9, readFileSync as readFileSync10, rmSync as rmSync5 } from "fs";
@@ -7585,14 +8665,14 @@ function readFarewell(variantDir) {
7585
8665
  }
7586
8666
  }
7587
8667
  function waitForFarewell(name, variantDir, timeoutMs) {
7588
- let settle;
8668
+ let settle2;
7589
8669
  const done = new Promise((resolvePromise) => {
7590
- settle = resolvePromise;
8670
+ settle2 = resolvePromise;
7591
8671
  });
7592
8672
  const cleanup = () => {
7593
8673
  clearTimeout(timeout);
7594
8674
  unsub();
7595
- settle();
8675
+ settle2();
7596
8676
  };
7597
8677
  const timeout = setTimeout(cleanup, timeoutMs);
7598
8678
  const unsub = subscribe((event) => {
@@ -7603,7 +8683,7 @@ function waitForFarewell(name, variantDir, timeoutMs) {
7603
8683
  return { done, cancel: cleanup };
7604
8684
  }
7605
8685
  async function runFarewellTurn(opts) {
7606
- const { variantName, parentName, variantDir, port, running } = opts;
8686
+ const { variantName, parentName, variantDir, running } = opts;
7607
8687
  const timeoutMs = opts.timeoutMs ?? FAREWELL_TIMEOUT_MS;
7608
8688
  if (!running) return readFarewell(variantDir);
7609
8689
  try {
@@ -7615,35 +8695,12 @@ async function runFarewellTurn(opts) {
7615
8695
  parent: parentName,
7616
8696
  path: FAREWELL_MIND_PATH
7617
8697
  });
7618
- let conversationId;
7619
- try {
7620
- const result = await sendSystemMessageDirect(variantName, message);
7621
- conversationId = result.conversationId;
7622
- } catch (err) {
7623
- flog.error(`failed to persist farewell message for ${variantName}`, logger_default.errorData(err));
7624
- }
7625
8698
  const waiter = waitForFarewell(variantName, variantDir, timeoutMs);
7626
- let delivered = false;
7627
- try {
7628
- const res = await fetch(`http://127.0.0.1:${port}/message`, {
7629
- method: "POST",
7630
- headers: { "Content-Type": "application/json" },
7631
- body: JSON.stringify({
7632
- content: [{ type: "text", text: message }],
7633
- channel: "@volute",
7634
- sender: "volute",
7635
- isDM: true,
7636
- participants: ["volute", variantName],
7637
- participantCount: 2,
7638
- ...conversationId ? { conversationId } : {}
7639
- }),
7640
- signal: AbortSignal.timeout(1e4)
7641
- });
7642
- if (res.ok) delivered = true;
7643
- else flog.warn(`farewell delivery to ${variantName} returned HTTP ${res.status}`);
7644
- } catch (err) {
7645
- flog.warn(`failed to deliver farewell message to ${variantName}`, logger_default.errorData(err));
7646
- }
8699
+ const { delivered } = await deliverEvent(variantName, {
8700
+ type: "lifecycle",
8701
+ body: message,
8702
+ meta: { subtype: "farewell", parent: parentName }
8703
+ });
7647
8704
  if (delivered) {
7648
8705
  await waiter.done;
7649
8706
  } else {
@@ -7785,12 +8842,13 @@ async function establishVariantDialogue(parent, variant, purpose) {
7785
8842
  await createConversation({ participantIds: [parentUser.id, variantUser.id] });
7786
8843
  }
7787
8844
  const purposeLine = purpose ? ` Its purpose: ${purpose}.` : "";
7788
- await sendSystemMessage(
7789
- parent,
7790
- `You've split off a variant, ${variant} \u2014 a parallel version of you exploring on its own.${purposeLine} Reach it at @${variant} to check in on how the experiment is going, and merge its work back with \`volute mind join ${variant}\` when you're ready.`
7791
- );
8845
+ await deliverEvent(parent, {
8846
+ type: "lifecycle",
8847
+ meta: { subtype: "variant-created", variant },
8848
+ body: `You've split off a variant, ${variant} \u2014 a parallel version of you exploring on its own.${purposeLine} Reach it at @${variant} to check in on how the experiment is going, and merge its work back with \`volute mind join ${variant}\` when you're ready.`
8849
+ });
7792
8850
  }
7793
- var app21 = new Hono25().get("/:name/variants", async (c) => {
8851
+ var app22 = new Hono26().get("/:name/variants", async (c) => {
7794
8852
  const name = c.req.param("name");
7795
8853
  const entry = await findMind(name);
7796
8854
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -7976,7 +9034,6 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
7976
9034
  variantName,
7977
9035
  parentName: mindName,
7978
9036
  variantDir: variantEntry.dir,
7979
- port: variantEntry.port,
7980
9037
  running: tryGetMindManager()?.isRunning(variantName) ?? false
7981
9038
  });
7982
9039
  } catch (err) {
@@ -8046,7 +9103,7 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
8046
9103
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
8047
9104
  try {
8048
9105
  const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-GSOUARW4.js");
8049
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-3ELJPUGJ.js");
9106
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-4UF6OCOE.js");
8050
9107
  const tmpl = parentEntry.template ?? "claude";
8051
9108
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
8052
9109
  } catch (err) {
@@ -8110,11 +9167,11 @@ var app21 = new Hono25().get("/:name/variants", async (c) => {
8110
9167
  await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
8111
9168
  return c.json({ ok: true });
8112
9169
  });
8113
- var variants_default = app21;
9170
+ var variants_default = app22;
8114
9171
 
8115
9172
  // packages/daemon/src/web/api/volute/channels.ts
8116
9173
  import { zValidator as zValidator10 } from "@hono/zod-validator";
8117
- import { Hono as Hono26 } from "hono";
9174
+ import { Hono as Hono27 } from "hono";
8118
9175
  import { z as z10 } from "zod";
8119
9176
  var channelSettingsSchema = z10.object({
8120
9177
  description: z10.string().nullable().optional(),
@@ -8129,7 +9186,7 @@ var createSchema2 = z10.object({
8129
9186
  var inviteSchema = z10.object({
8130
9187
  username: z10.string().min(1)
8131
9188
  });
8132
- var app22 = new Hono26().get("/", async (c) => {
9189
+ var app23 = new Hono27().get("/", async (c) => {
8133
9190
  const user = c.get("user");
8134
9191
  const channels2 = await listChannels();
8135
9192
  const results = await Promise.all(
@@ -8251,11 +9308,11 @@ var app22 = new Hono26().get("/", async (c) => {
8251
9308
  ]);
8252
9309
  return c.json({ ok: true });
8253
9310
  });
8254
- var channels_default2 = app22;
9311
+ var channels_default2 = app23;
8255
9312
 
8256
9313
  // packages/daemon/src/web/api/volute/chat.ts
8257
9314
  import { zValidator as zValidator11 } from "@hono/zod-validator";
8258
- import { Hono as Hono27 } from "hono";
9315
+ import { Hono as Hono28 } from "hono";
8259
9316
  import { streamSSE as streamSSE5 } from "hono/streaming";
8260
9317
  import { z as z11 } from "zod";
8261
9318
  var fileSchema = z11.object({
@@ -8302,7 +9359,25 @@ function stageFilesForMinds(files, targetMinds, senderName) {
8302
9359
  }
8303
9360
  return { notifications };
8304
9361
  }
8305
- var unifiedChatApp = new Hono27().post(
9362
+ var spiritNoticeInFlight = /* @__PURE__ */ new Set();
9363
+ async function persistSpiritNotice(conversationId, spiritUsername, notice) {
9364
+ if (spiritNoticeInFlight.has(conversationId)) return;
9365
+ spiritNoticeInFlight.add(conversationId);
9366
+ try {
9367
+ const last = await getLastMessageBySender(conversationId, spiritUsername);
9368
+ const alreadyNoticed = last?.content.some(
9369
+ (b) => b.type === "text" && b.text.startsWith(SPIRIT_NOTICE_PREFIX)
9370
+ );
9371
+ if (!alreadyNoticed) {
9372
+ await addMessage(conversationId, "assistant", spiritUsername, [
9373
+ { type: "text", text: notice }
9374
+ ]);
9375
+ }
9376
+ } finally {
9377
+ spiritNoticeInFlight.delete(conversationId);
9378
+ }
9379
+ }
9380
+ var unifiedChatApp = new Hono28().post(
8306
9381
  "/chat",
8307
9382
  zValidator11("json", chatSchema),
8308
9383
  async (c) => {
@@ -8334,7 +9409,7 @@ var unifiedChatApp = new Hono27().post(
8334
9409
  if (user.id !== 0) {
8335
9410
  participantIds.push(user.id);
8336
9411
  } else if (body.sender) {
8337
- if (body.sender === "volute") {
9412
+ if (isSpiritName(body.sender)) {
8338
9413
  const systemUser = await getOrCreateSystemUser();
8339
9414
  participantIds.push(systemUser.id);
8340
9415
  } else {
@@ -8461,7 +9536,7 @@ var unifiedChatApp = new Hono27().post(
8461
9536
  });
8462
9537
  if (outboundTurnId) {
8463
9538
  const mindKey = senderBase ?? senderName;
8464
- publish3(mindKey, {
9539
+ publish2(mindKey, {
8465
9540
  mind: mindKey,
8466
9541
  type: "outbound",
8467
9542
  channel,
@@ -8473,6 +9548,30 @@ var unifiedChatApp = new Hono27().post(
8473
9548
  logger_default.warn(`recordOutbound failed for ${senderName}`, logger_default.errorData(err));
8474
9549
  }
8475
9550
  }
9551
+ let spiritStatus;
9552
+ let spiritName;
9553
+ try {
9554
+ const mindishRecipients = participants.filter(
9555
+ (p) => (p.userType === "mind" || p.userType === "system") && p.username !== senderName
9556
+ );
9557
+ const spiritRecipient = conv.type === "dm" && mindishRecipients.length === 1 && mindishRecipients[0].userType === "system" ? mindishRecipients[0] : void 0;
9558
+ if (spiritRecipient) {
9559
+ const availability = await ensureSpiritAvailable();
9560
+ if (availability) {
9561
+ spiritStatus = availability.status;
9562
+ spiritName = spiritRecipient.username;
9563
+ if (availability.status === "unavailable" && availability.notice) {
9564
+ await persistSpiritNotice(
9565
+ conversationId,
9566
+ spiritRecipient.username,
9567
+ availability.notice
9568
+ );
9569
+ }
9570
+ }
9571
+ }
9572
+ } catch (err) {
9573
+ logger_default.error("spirit availability check failed \u2014 proceeding with delivery", logger_default.errorData(err));
9574
+ }
8476
9575
  const isDM = conv.type === "dm";
8477
9576
  await fanOutToMinds({
8478
9577
  conversationId,
@@ -8484,29 +9583,17 @@ var unifiedChatApp = new Hono27().post(
8484
9583
  // Variant-aware targeting: when targetMind is a variant, route to the variant name
8485
9584
  targetName: baseName ? (username) => username === baseName ? variantName : username : void 0
8486
9585
  });
8487
- const systemReplyTarget = baseName ?? senderName;
8488
- if (shouldGenerateSystemFallback({
8489
- senderIsMind: !!senderIsMind,
8490
- hasMessage: !!body.message,
8491
- convType: conv.type,
8492
- participants,
8493
- replyTarget: systemReplyTarget
8494
- })) {
8495
- generateSystemFallbackReply(conversationId, systemReplyTarget, body.message).catch(
8496
- (err) => logger_default.error(`system reply generation failed for ${systemReplyTarget}`, logger_default.errorData(err))
8497
- );
8498
- }
8499
- return c.json({ ok: true, conversationId, outboundId });
9586
+ return c.json({ ok: true, conversationId, outboundId, spirit: spiritStatus, spiritName });
8500
9587
  }
8501
9588
  );
8502
- var app23 = new Hono27().get("/:name/conversations/:id/events", async (c) => {
9589
+ var app24 = new Hono28().get("/:name/conversations/:id/events", async (c) => {
8503
9590
  const conversationId = c.req.param("id");
8504
9591
  const user = c.get("user");
8505
9592
  if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
8506
9593
  return c.json({ error: "Conversation not found" }, 404);
8507
9594
  }
8508
9595
  return streamSSE5(c, async (stream) => {
8509
- const unsubscribe = subscribe2(conversationId, (event) => {
9596
+ const unsubscribe = subscribe3(conversationId, (event) => {
8510
9597
  stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
8511
9598
  if (!stream.aborted) console.error("[chat] SSE write error:", err);
8512
9599
  });
@@ -8525,17 +9612,17 @@ var app23 = new Hono27().get("/:name/conversations/:id/events", async (c) => {
8525
9612
  });
8526
9613
  });
8527
9614
  });
8528
- var chat_default = app23;
9615
+ var chat_default = app24;
8529
9616
 
8530
9617
  // packages/daemon/src/web/api/volute/conversations.ts
8531
9618
  import { zValidator as zValidator12 } from "@hono/zod-validator";
8532
- import { Hono as Hono28 } from "hono";
9619
+ import { Hono as Hono29 } from "hono";
8533
9620
  import { z as z12 } from "zod";
8534
9621
  var createConvSchema = z12.object({
8535
9622
  participantIds: z12.array(z12.number()).optional(),
8536
9623
  participantNames: z12.array(z12.string()).optional()
8537
9624
  });
8538
- var app24 = new Hono28().get("/:name/conversations", async (c) => {
9625
+ var app25 = new Hono29().get("/:name/conversations", async (c) => {
8539
9626
  const name = c.req.param("name");
8540
9627
  const user = c.get("user");
8541
9628
  let lookupId = user.id;
@@ -8616,7 +9703,7 @@ var app24 = new Hono28().get("/:name/conversations", async (c) => {
8616
9703
  const beforeStr = c.req.query("before");
8617
9704
  if (!limitStr && !beforeStr) {
8618
9705
  const msgs = await getMessages(id);
8619
- return c.json({ items: msgs, hasMore: false });
9706
+ return c.json({ items: await withSenderDisplayNames(msgs), hasMore: false });
8620
9707
  }
8621
9708
  const limit = limitStr ? parseInt(limitStr, 10) : void 0;
8622
9709
  const before = beforeStr ? parseInt(beforeStr, 10) : void 0;
@@ -8624,7 +9711,10 @@ var app24 = new Hono28().get("/:name/conversations", async (c) => {
8624
9711
  return c.json({ error: "Invalid pagination parameters" }, 400);
8625
9712
  }
8626
9713
  const result = await getMessagesPaginated(id, { before, limit });
8627
- return c.json({ items: result.messages, hasMore: result.hasMore });
9714
+ return c.json({
9715
+ items: await withSenderDisplayNames(result.messages),
9716
+ hasMore: result.hasMore
9717
+ });
8628
9718
  }).get("/:name/conversations/:id/participants", async (c) => {
8629
9719
  const id = c.req.param("id");
8630
9720
  const user = c.get("user");
@@ -8640,12 +9730,12 @@ var app24 = new Hono28().get("/:name/conversations", async (c) => {
8640
9730
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
8641
9731
  return c.json({ ok: true });
8642
9732
  });
8643
- var conversations_default2 = app24;
9733
+ var conversations_default2 = app25;
8644
9734
 
8645
9735
  // packages/daemon/src/web/app.ts
8646
9736
  var httpLog = logger_default.child("http");
8647
- var app25 = new Hono29();
8648
- app25.onError((err, c) => {
9737
+ var app26 = new Hono30();
9738
+ app26.onError((err, c) => {
8649
9739
  if (err instanceof HTTPException) {
8650
9740
  return err.getResponse();
8651
9741
  }
@@ -8656,10 +9746,10 @@ app25.onError((err, c) => {
8656
9746
  });
8657
9747
  return c.json({ error: "Internal server error" }, 500);
8658
9748
  });
8659
- app25.notFound((c) => {
9749
+ app26.notFound((c) => {
8660
9750
  return c.json({ error: "Not found" }, 404);
8661
9751
  });
8662
- app25.use("*", async (c, next) => {
9752
+ app26.use("*", async (c, next) => {
8663
9753
  const start = Date.now();
8664
9754
  await next();
8665
9755
  const duration = Date.now() - start;
@@ -8670,23 +9760,23 @@ app25.use("*", async (c, next) => {
8670
9760
  httpLog.debug("request", data);
8671
9761
  }
8672
9762
  });
8673
- app25.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
8674
- app25.use(
9763
+ app26.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
9764
+ app26.use(
8675
9765
  "/api/*",
8676
9766
  cors({
8677
9767
  origin: (origin) => origin,
8678
- allowHeaders: ["Authorization", "Content-Type", "X-Volute-Session"],
9768
+ allowHeaders: ["Authorization", "Content-Type", "X-Volute-Thread"],
8679
9769
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
8680
9770
  credentials: false
8681
9771
  })
8682
9772
  );
8683
9773
  var csrfMiddleware = csrf();
8684
- app25.use("/api/*", async (c, next) => {
9774
+ app26.use("/api/*", async (c, next) => {
8685
9775
  const auth = c.req.header("Authorization");
8686
9776
  if (auth?.startsWith("Bearer ") && auth.length > 7) return next();
8687
9777
  return csrfMiddleware(c, next);
8688
9778
  });
8689
- app25.get("/api/health", (c) => {
9779
+ app26.get("/api/health", (c) => {
8690
9780
  let version = "unknown";
8691
9781
  let cached = null;
8692
9782
  try {
@@ -8701,43 +9791,43 @@ app25.get("/api/health", (c) => {
8701
9791
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
8702
9792
  });
8703
9793
  });
8704
- app25.use("/api/activity/*", authMiddleware);
8705
- app25.use("/api/minds/*", authMiddleware);
8706
- app25.use("/api/conversations/*", authMiddleware);
8707
- app25.use("/api/system/*", authMiddleware);
8708
- app25.use("/api/env/*", authMiddleware);
8709
- app25.use("/api/prompts/*", authMiddleware);
8710
- app25.use("/api/skills/*", authMiddleware);
8711
- app25.use("/api/extensions/*", authMiddleware);
8712
- app25.use("/api/bridges/*", authMiddleware);
8713
- app25.use("/api/config/*", authMiddleware);
8714
- app25.use("/api/backup/*", authMiddleware);
8715
- app25.use("/api/v1/*", authMiddleware);
8716
- app25.route("/api/setup", setup_default);
8717
- app25.route("/api/config", config_default);
8718
- var routes = app25.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/backup", backup_default).route("/api/minds", minds_default).route("/api/minds", chat_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/bridges", bridges_default).route("/api/extensions", extensions_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", unifiedChatApp).route("/api/v1/channels", channels_default2).route("/api/v1/history", history_default);
8719
- app25.route("/api/v1/minds", minds_default);
8720
- app25.route("/api/v1/minds", chat_default);
8721
- app25.route("/api/v1/minds", typing_default);
8722
- app25.route("/api/v1/minds", variants_default);
8723
- app25.route("/api/v1/minds", files_default);
8724
- app25.route("/api/v1/minds", env_default);
8725
- app25.route("/api/v1/minds", mind_skills_default);
8726
- app25.route("/api/v1/minds", schedules_default);
8727
- app25.route("/api/v1/minds", logs_default);
8728
- app25.route("/api/v1/system", system_default);
8729
- app25.route("/api/v1/system", update_default);
8730
- app25.route("/api/v1/prompts", prompts_default);
8731
- app25.route("/api/v1/skills", skills_default);
8732
- app25.route("/api/v1/env", sharedEnvApp);
8733
- app25.route("/api/conversations", conversations_default);
8734
- var app_default = app25;
9794
+ app26.use("/api/activity/*", authMiddleware);
9795
+ app26.use("/api/minds/*", authMiddleware);
9796
+ app26.use("/api/conversations/*", authMiddleware);
9797
+ app26.use("/api/system/*", authMiddleware);
9798
+ app26.use("/api/env/*", authMiddleware);
9799
+ app26.use("/api/prompts/*", authMiddleware);
9800
+ app26.use("/api/skills/*", authMiddleware);
9801
+ app26.use("/api/extensions/*", authMiddleware);
9802
+ app26.use("/api/bridges/*", authMiddleware);
9803
+ app26.use("/api/config/*", authMiddleware);
9804
+ app26.use("/api/backup/*", authMiddleware);
9805
+ app26.use("/api/v1/*", authMiddleware);
9806
+ app26.route("/api/setup", setup_default);
9807
+ app26.route("/api/config", config_default);
9808
+ var routes = app26.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/backup", backup_default).route("/api/minds", minds_default).route("/api/minds", chat_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/bridges", bridges_default).route("/api/extensions", extensions_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1/feed", feed_default).route("/api/v1", unifiedChatApp).route("/api/v1/channels", channels_default2).route("/api/v1/history", history_default);
9809
+ app26.route("/api/v1/minds", minds_default);
9810
+ app26.route("/api/v1/minds", chat_default);
9811
+ app26.route("/api/v1/minds", typing_default);
9812
+ app26.route("/api/v1/minds", variants_default);
9813
+ app26.route("/api/v1/minds", files_default);
9814
+ app26.route("/api/v1/minds", env_default);
9815
+ app26.route("/api/v1/minds", mind_skills_default);
9816
+ app26.route("/api/v1/minds", schedules_default);
9817
+ app26.route("/api/v1/minds", logs_default);
9818
+ app26.route("/api/v1/system", system_default);
9819
+ app26.route("/api/v1/system", update_default);
9820
+ app26.route("/api/v1/prompts", prompts_default);
9821
+ app26.route("/api/v1/skills", skills_default);
9822
+ app26.route("/api/v1/env", sharedEnvApp);
9823
+ app26.route("/api/conversations", conversations_default);
9824
+ var app_default = app26;
8735
9825
 
8736
9826
  // packages/daemon/src/web/server.ts
8737
9827
  import { existsSync as existsSync11 } from "fs";
8738
9828
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
8739
9829
  import { createServer as createHttpsServer } from "https";
8740
- import { dirname as dirname2, extname as extname3, resolve as resolve17 } from "path";
9830
+ import { dirname as dirname3, extname as extname3, resolve as resolve17 } from "path";
8741
9831
  import { serve } from "@hono/node-server";
8742
9832
  var MIME_TYPES2 = {
8743
9833
  ".html": "text/html",
@@ -8754,14 +9844,14 @@ async function startServer({
8754
9844
  tls
8755
9845
  }) {
8756
9846
  let assetsDir = "";
8757
- let searchDir = dirname2(new URL(import.meta.url).pathname);
9847
+ let searchDir = dirname3(new URL(import.meta.url).pathname);
8758
9848
  for (let i = 0; i < 5; i++) {
8759
9849
  const candidate = resolve17(searchDir, "dist", "web-assets");
8760
9850
  if (existsSync11(candidate)) {
8761
9851
  assetsDir = candidate;
8762
9852
  break;
8763
9853
  }
8764
- searchDir = dirname2(searchDir);
9854
+ searchDir = dirname3(searchDir);
8765
9855
  }
8766
9856
  if (assetsDir) {
8767
9857
  app_default.get("*", async (c) => {
@@ -8873,27 +9963,33 @@ async function startDaemon(opts) {
8873
9963
  const DAEMON_TOKEN_PATH = resolve18(systemDir, "daemon-token");
8874
9964
  mkdirSync10(home, { recursive: true });
8875
9965
  ensureSystemDir();
8876
- const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-TCN7VH5U.js");
9966
+ const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-CAJFOR2N.js");
8877
9967
  migrateConfigSecrets();
8878
9968
  migrateSetupCompleted();
8879
- const { migrateAiModelQualification } = await import("./ai-service-A7FS7EC3.js");
9969
+ const { migrateAiModelQualification } = await import("./ai-service-V4CTNGI3.js");
8880
9970
  migrateAiModelQualification();
8881
- await (await import("./db-F6UMFRYD.js")).getDb();
9971
+ await (await import("./db-QAY45Z56.js")).getDb();
8882
9972
  try {
8883
- const { eq: eq8, and: and6 } = await import("drizzle-orm");
8884
- const { users: users2 } = await import("./schema-6YONVTKZ.js");
8885
- const db = await (await import("./db-F6UMFRYD.js")).getDb();
8886
- await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
9973
+ const { eq: eq9, and: and7 } = await import("drizzle-orm");
9974
+ const { users: users2 } = await import("./schema-C2GIPD4V.js");
9975
+ const db = await (await import("./db-QAY45Z56.js")).getDb();
9976
+ await db.update(users2).set({ role: "system" }).where(and7(eq9(users2.user_type, "system"), eq9(users2.role, "user")));
8887
9977
  } catch (err) {
8888
9978
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8889
9979
  }
8890
9980
  try {
8891
- const { migrateAvatarSizes } = await import("./avatar-image-6ATV2IOZ.js");
9981
+ const { migrateAvatarSizes } = await import("./avatar-image-QEPAP3A5.js");
8892
9982
  await migrateAvatarSizes();
8893
9983
  } catch (err) {
8894
9984
  logger_default.warn("avatar size migration failed", logger_default.errorData(err));
8895
9985
  }
8896
- const { initSandbox } = await import("./sandbox-XPYA7UIF.js");
9986
+ try {
9987
+ const { migrateThreadConfigs } = await import("./migrate-thread-config-4A5LW36Z.js");
9988
+ await migrateThreadConfigs();
9989
+ } catch (err) {
9990
+ logger_default.warn("session\u2192thread config migration failed", logger_default.errorData(err));
9991
+ }
9992
+ const { initSandbox } = await import("./sandbox-EGEU5GGE.js");
8897
9993
  await initSandbox();
8898
9994
  try {
8899
9995
  await syncBuiltinSkills();
@@ -8925,7 +10021,7 @@ async function startDaemon(opts) {
8925
10021
  logger_default.warn("failed to backfill minds into #system", logger_default.errorData(err));
8926
10022
  }
8927
10023
  try {
8928
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-ABYLOEZ7.js");
10024
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-DT7GY7EZ.js");
8929
10025
  await getOrCreateSystemUser2();
8930
10026
  } catch (err) {
8931
10027
  logger_default.warn(
@@ -8934,12 +10030,12 @@ async function startDaemon(opts) {
8934
10030
  );
8935
10031
  }
8936
10032
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes3(32).toString("hex");
8937
- const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-TCN7VH5U.js");
10033
+ const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-CAJFOR2N.js");
8938
10034
  const globalCfg = readGlobalConfig2();
8939
10035
  let tls;
8940
10036
  if (opts.tailscale || globalCfg.tailscale) {
8941
10037
  try {
8942
- const { getTailscaleTls } = await import("./tailscale-AMMVKDXB.js");
10038
+ const { getTailscaleTls } = await import("./tailscale-SJUW2ETR.js");
8943
10039
  const tlsConfig = await getTailscaleTls();
8944
10040
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8945
10041
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8974,6 +10070,8 @@ async function startDaemon(opts) {
8974
10070
  const delivery = initDeliveryManager();
8975
10071
  const manager = initMindManager();
8976
10072
  manager.loadCrashAttempts();
10073
+ const { registerXaiOAuthProvider } = await import("./xai-P42CRCZA.js");
10074
+ registerXaiOAuthProvider();
8977
10075
  const bridgeManager = initBridgeManager();
8978
10076
  const scheduler = initScheduler();
8979
10077
  scheduler.start();
@@ -8991,7 +10089,7 @@ async function startDaemon(opts) {
8991
10089
  const orphanedTurns = await completeOrphanedTurns();
8992
10090
  summarizeOrphanedTurns(orphanedTurns);
8993
10091
  try {
8994
- const { reconcileVariants } = await import("./variant-cleanup-P4MBNLJD.js");
10092
+ const { reconcileVariants } = await import("./variant-cleanup-HJ7Z75R6.js");
8995
10093
  await reconcileVariants();
8996
10094
  } catch (err) {
8997
10095
  logger_default.error("failed to reconcile variants", logger_default.errorData(err));
@@ -9025,31 +10123,29 @@ async function startDaemon(opts) {
9025
10123
  await Promise.all(workers);
9026
10124
  }
9027
10125
  try {
9028
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-TCN7VH5U.js");
10126
+ const { isSetupComplete: isSetupComplete2, getSpiritName: getSpiritName2 } = await import("./setup-CAJFOR2N.js");
9029
10127
  if (isSetupComplete2()) {
9030
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-SSJMYC6A.js");
9031
- const { startSpiritFull } = await import("./mind-service-42GDO4KH.js");
10128
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-IL5EMWYZ.js");
10129
+ const { startSpiritFull } = await import("./mind-service-5TA7UJAT.js");
9032
10130
  await ensureSpiritProject();
9033
- const spiritEntry = await findMind(SPIRIT_NAME);
10131
+ const spiritName = getSpiritName2();
10132
+ const spiritEntry = await findMind(spiritName);
9034
10133
  if (spiritEntry?.dir) {
9035
- const { registerMindDir } = await import("./delivery-router-VUOL5YSA.js");
9036
- registerMindDir(SPIRIT_NAME, spiritEntry.dir);
10134
+ const { registerMindDir } = await import("./delivery-router-JE7SHYNU.js");
10135
+ registerMindDir(spiritName, spiritEntry.dir);
9037
10136
  }
9038
10137
  await syncSpiritTemplate();
9039
- if (spiritEntry && !manager.isRunning(SPIRIT_NAME)) {
9040
- await startSpiritFull(SPIRIT_NAME);
10138
+ if (spiritEntry && !manager.isRunning(spiritName)) {
10139
+ await startSpiritFull(spiritName);
9041
10140
  }
9042
10141
  }
9043
10142
  } catch (err) {
9044
- logger_default.warn(
9045
- "failed to start system spirit \u2014 system replies will use aiComplete fallback",
9046
- logger_default.errorData(err)
9047
- );
10143
+ logger_default.warn("failed to start system spirit", logger_default.errorData(err));
9048
10144
  }
9049
10145
  bridgeManager.startBridges(daemonPort).catch((err) => {
9050
10146
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
9051
10147
  });
9052
- import("./cloud-sync-VFDTNNCA.js").then(
10148
+ import("./cloud-sync-IDJFVDEP.js").then(
9053
10149
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
9054
10150
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
9055
10151
  })
@@ -9057,7 +10153,7 @@ async function startDaemon(opts) {
9057
10153
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
9058
10154
  });
9059
10155
  try {
9060
- const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-WFA5XB5A.js");
10156
+ const { backfillTemplateHashes, notifyVersionUpdate, warnStaleTemplates } = await import("./version-notify-7BUHWZNT.js");
9061
10157
  backfillTemplateHashes().then(() => warnStaleTemplates()).catch((err) => {
9062
10158
  logger_default.warn("failed to check template staleness", logger_default.errorData(err));
9063
10159
  });