volute 0.47.0 → 0.49.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 (177) hide show
  1. package/README.md +82 -331
  2. package/dist/{accept-YAFP5JFJ.js → accept-KYNC5X3U.js} +3 -3
  3. package/dist/{activity-events-MIOK3IOU.js → activity-events-GJJAI2WT.js} +4 -4
  4. package/dist/{ai-service-CVZEYKTK.js → ai-service-A7FS7EC3.js} +9 -5
  5. package/dist/{archive-6Z4GFQPH.js → archive-7E3YE6VF.js} +3 -3
  6. package/dist/{auth-A4VGHADI.js → auth-ABYLOEZ7.js} +5 -5
  7. package/dist/{avatar-image-7S3WA77A.js → avatar-image-6ATV2IOZ.js} +6 -5
  8. package/dist/{backup-IIAFOW2L.js → backup-XYAMT4CU.js} +15 -15
  9. package/dist/{bridge-TIZGSXOD.js → bridge-3FNSWLNQ.js} +3 -3
  10. package/dist/{channels-ITE4YC57.js → channels-SGQZHNQJ.js} +3 -3
  11. package/dist/{chat-AJLJCQ3M.js → chat-OGXAEXMV.js} +9 -9
  12. package/dist/{chunk-LTLFOSA4.js → chunk-3B427GUK.js} +3 -3
  13. package/dist/{chunk-RUFLGTOT.js → chunk-4B62E4CG.js} +2 -2
  14. package/dist/{chunk-ODDGGLM6.js → chunk-4DSGN4FL.js} +3 -3
  15. package/dist/{chunk-BGYHLR54.js → chunk-6HGEZJDH.js} +2 -2
  16. package/dist/{chunk-RBNMDAKB.js → chunk-BCG45AQB.js} +15 -1
  17. package/dist/{chunk-5IERLDUS.js → chunk-DAIIMBLB.js} +26 -6
  18. package/dist/{chunk-DGGZVZTM.js → chunk-DVN6DNMI.js} +1 -1
  19. package/dist/chunk-DW4TRDOZ.js +29 -0
  20. package/dist/chunk-EADX54RG.js +113 -0
  21. package/dist/{chunk-EIQDZC43.js → chunk-EFT2OALG.js} +6 -2
  22. package/dist/{chunk-XHN6XBWV.js → chunk-EG5LSKHU.js} +2 -2
  23. package/dist/{chunk-VMCZBXF2.js → chunk-FBBS4BVR.js} +10 -34
  24. package/dist/{chunk-IWJSNMSY.js → chunk-GWVFDUNB.js} +45 -14
  25. package/dist/{chunk-CBOIHNMR.js → chunk-HCYQRME2.js} +41 -5
  26. package/dist/{chunk-4I72V3E5.js → chunk-IH5TKXX6.js} +15 -15
  27. package/dist/{chunk-5NN2VHJV.js → chunk-JBIFXB6I.js} +4 -4
  28. package/dist/{chunk-JSVHH32K.js → chunk-JCBPU57A.js} +15 -13
  29. package/dist/{chunk-M3XDVS5Q.js → chunk-JCWB2ITW.js} +2 -2
  30. package/dist/{chunk-JCMGDCGX.js → chunk-JPGHSXSV.js} +20 -25
  31. package/dist/{chunk-K23P5Q5N.js → chunk-K3JOLGWF.js} +30 -8
  32. package/dist/{chunk-3YUBFTDN.js → chunk-KHCKZ36O.js} +1 -1
  33. package/dist/{chunk-7KRIHYLR.js → chunk-L27YYONJ.js} +2 -2
  34. package/dist/{chunk-SZJCJVXV.js → chunk-MG3Z34YE.js} +2 -2
  35. package/dist/{chunk-SKNXMUIU.js → chunk-NJHN6IRB.js} +26 -2
  36. package/dist/{chunk-2OIYNHWO.js → chunk-NOIAHMJB.js} +1 -1
  37. package/dist/{chunk-2MJ7EOMT.js → chunk-P4LHVRNE.js} +1 -0
  38. package/dist/{chunk-IYG4FBMP.js → chunk-PY24ZL62.js} +1 -1
  39. package/dist/{chunk-F55GRIEK.js → chunk-QQBOI3QA.js} +356 -296
  40. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  41. package/dist/{chunk-KDLYIECM.js → chunk-R772ET2M.js} +18 -4
  42. package/dist/{chunk-WW6C3PLV.js → chunk-RSBL3FAD.js} +39 -6
  43. package/dist/{mind-list-KEGRGQEQ.js → chunk-SHKM5RGD.js} +19 -6
  44. package/dist/{chunk-YKA7DCVW.js → chunk-SX72DOO3.js} +1 -1
  45. package/dist/chunk-TXJL36ZQ.js +48 -0
  46. package/dist/{chunk-M6TTZRGH.js → chunk-VIUH5CHQ.js} +72 -24
  47. package/dist/{chunk-HPGN6XRT.js → chunk-VNRSXEBF.js} +4 -4
  48. package/dist/{chunk-PFZBNKUH.js → chunk-WCFSBH33.js} +0 -1
  49. package/dist/chunk-XTEKINWG.js +158 -0
  50. package/dist/{chunk-LWDTP6DH.js → chunk-YIJGKHIS.js} +32 -2
  51. package/dist/{chunk-MTRMQFCQ.js → chunk-ZX46PGCC.js} +2 -2
  52. package/dist/cli.js +34 -26
  53. package/dist/{clock-E6N5Y3YW.js → clock-6JZL2FS6.js} +3 -3
  54. package/dist/{cloud-sync-LQTVDTQ3.js → cloud-sync-VFDTNNCA.js} +22 -20
  55. package/dist/{config-TJLYUV3U.js → config-RPSLA2HX.js} +3 -3
  56. package/dist/{conversations-C7IKYSHD.js → conversations-TUM5PJJ4.js} +5 -5
  57. package/dist/{create-A7PDHIIL.js → create-VIQVP3QB.js} +3 -3
  58. package/dist/{create-PQQXKTJK.js → create-Z5FSDNVZ.js} +16 -5
  59. package/dist/{daemon-client-IYR4KCPU.js → daemon-client-ZFY5MOAL.js} +5 -3
  60. package/dist/{daemon-restart-5F3YTLZK.js → daemon-restart-54L4B6AQ.js} +7 -7
  61. package/dist/daemon.js +1105 -601
  62. package/dist/{db-FO6LXYCW.js → db-F6UMFRYD.js} +2 -2
  63. package/dist/{db-LZTLA7XC.js → db-IPXRJUI7.js} +1 -1
  64. package/dist/{delete-HPLC7ETU.js → delete-ESEP7NGQ.js} +1 -1
  65. package/dist/delivery-manager-RBXBPVWM.js +36 -0
  66. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-VUOL5YSA.js} +8 -6
  67. package/dist/{down-ZI4S6265.js → down-6X7KX7BZ.js} +5 -5
  68. package/dist/echo-text-B6NZ5FGE.js +36 -0
  69. package/dist/env-KUL4XKN2.js +18 -0
  70. package/dist/{env-HRL2PWAT.js → env-ZYMSCB7B.js} +6 -6
  71. package/dist/{exec-QIFYZG4C.js → exec-FBXA3CM4.js} +3 -3
  72. package/dist/{export-4GVGNW54.js → export-TNBPDH77.js} +4 -4
  73. package/dist/{extension-QBY4C44L.js → extension-J4ZQ2GHV.js} +3 -3
  74. package/dist/{extensions-PIMIQ7J3.js → extensions-AWNO3SZF.js} +22 -20
  75. package/dist/{files-FEFZKMNM.js → files-BVBRJJ2P.js} +6 -6
  76. package/dist/{import-7MO3YTRE.js → import-ZUNKMCKC.js} +9 -9
  77. package/dist/{isolation-MCT4ZYLT.js → isolation-OPCZQRTH.js} +3 -3
  78. package/dist/{join-ZJV7W6MO.js → join-34TR2GXQ.js} +1 -1
  79. package/dist/{list-LQO2DCIK.js → list-PMUXZNAM.js} +3 -3
  80. package/dist/{login-OF4WXDAN.js → login-KAKNFCW5.js} +14 -5
  81. package/dist/{login-6TLMI2TQ.js → login-LKNLCJPA.js} +3 -3
  82. package/dist/{logout-ERE2BGA3.js → logout-RLT62RQP.js} +5 -5
  83. package/dist/{logout-BK53HXNY.js → logout-YZRMZ3O2.js} +3 -3
  84. package/dist/message-delivery-ABPV76EA.js +42 -0
  85. package/dist/{mind-POOHFZHP.js → mind-IF6CGMFW.js} +23 -19
  86. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-YMMERB6B.js} +5 -5
  87. package/dist/mind-contacts-BKUFPUCU.js +64 -0
  88. package/dist/{mind-history-47DN6ILW.js → mind-history-WI5OI3BW.js} +5 -5
  89. package/dist/mind-list-OTHV4ZO4.js +13 -0
  90. package/dist/{mind-profile-PEQYASQX.js → mind-profile-LUG353ZT.js} +1 -1
  91. package/dist/mind-service-42GDO4KH.js +44 -0
  92. package/dist/{mind-sleep-IN3WPA76.js → mind-sleep-VBV7YRSU.js} +5 -5
  93. package/dist/{mind-status-SMUWJKGV.js → mind-status-BJNPGXKC.js} +8 -3
  94. package/dist/{mind-wake-R54BPESY.js → mind-wake-LVTZ55EZ.js} +3 -3
  95. package/dist/notices-N6BCGHFV.js +25 -0
  96. package/dist/{package-WPEE2HVJ.js → package-PD67VPHB.js} +8 -5
  97. package/dist/prompts-MDHDLWEX.js +24 -0
  98. package/dist/{read-PWAJOQIQ.js → read-YSWUXYL7.js} +3 -3
  99. package/dist/{register-7SZJFHU6.js → register-TWQDKQRT.js} +3 -3
  100. package/dist/{registry-UMMDRPRM.js → registry-3ELJPUGJ.js} +6 -2
  101. package/dist/{reject-6VH7N2A4.js → reject-SXGCYN4A.js} +3 -3
  102. package/dist/{restart-H5WJDMA7.js → restart-5F5QDBOG.js} +5 -5
  103. package/dist/{sandbox-G4X4K3UN.js → sandbox-XPYA7UIF.js} +5 -5
  104. package/dist/scheduler-K7Y7Z7FQ.js +34 -0
  105. package/dist/{schema-VEVO7HG6.js → schema-6YONVTKZ.js} +1 -1
  106. package/dist/{seed-VANPU43L.js → seed-NQED7PSX.js} +1 -1
  107. package/dist/{seed-check-VLTVILOV.js → seed-check-TYD7RRZY.js} +1 -1
  108. package/dist/{seed-cmd-SYVDZKM4.js → seed-cmd-QF3F2G4S.js} +3 -3
  109. package/dist/{seed-create-3FGZPS6C.js → seed-create-XOO4N2OE.js} +6 -4
  110. package/dist/seed-readiness-3LRYASA4.js +18 -0
  111. package/dist/{seed-sprout-H7QLFPFM.js → seed-sprout-HSBBGSS2.js} +32 -33
  112. package/dist/{send-HYDFGC6G.js → send-IHRJYNXA.js} +8 -8
  113. package/dist/{service-SYJ5G6LP.js → service-YCOPCTSZ.js} +4 -4
  114. package/dist/{service-install-FQ43Z4DF.js → service-install-Y46OTF55.js} +5 -5
  115. package/dist/{setup-QJHGMGSN.js → setup-F4TE64YI.js} +7 -7
  116. package/dist/{setup-KFYVWTXG.js → setup-TCN7VH5U.js} +9 -3
  117. package/dist/{skill-GUKG2YVH.js → skill-4CYJLYVY.js} +6 -6
  118. package/dist/skills/dreaming/SKILL.md +5 -1
  119. package/dist/skills/dreaming/scripts/dream.ts +5 -1
  120. package/dist/skills/orientation/SKILL.md +8 -7
  121. package/dist/skills/tending/SKILL.md +51 -2
  122. package/dist/skills/volute-mind/SKILL.md +5 -3
  123. package/dist/skills/volute-mind/references/routing.md +6 -5
  124. package/dist/skills/volute-mind/references/variants.md +48 -14
  125. package/dist/{skills-L2CMTW7A.js → skills-NN5UZCUU.js} +8 -6
  126. package/dist/sleep-manager-DSPXFHGX.js +38 -0
  127. package/dist/spirit-SSJMYC6A.js +31 -0
  128. package/dist/{split-WBMZXBHF.js → split-JF2VWTXA.js} +5 -2
  129. package/dist/{sprout-LTL5P36T.js → sprout-AXA2SECQ.js} +1 -1
  130. package/dist/{src-OJHARHTF.js → src-ZN4LXMPI.js} +17 -8
  131. package/dist/{start-X46I3GXB.js → start-BTVMTP3K.js} +5 -5
  132. package/dist/{status-V4RJMHJ4.js → status-VJGXMQ4L.js} +10 -8
  133. package/dist/{stop-HJQV4YSM.js → stop-YORESK4U.js} +5 -5
  134. package/dist/system-chat-M2XOBKEM.js +40 -0
  135. package/dist/{systems-B5YJKMW4.js → systems-5MH2ELQE.js} +6 -6
  136. package/dist/{tailscale-MCLY5GYE.js → tailscale-AMMVKDXB.js} +3 -3
  137. package/dist/{template-staleness-EMQORSGG.js → template-staleness-HKGCTS3H.js} +4 -4
  138. package/dist/{turn-tracker-JZRZDFWV.js → turn-tracker-JQ2YUUY2.js} +7 -7
  139. package/dist/up-SDLTLPAO.js +20 -0
  140. package/dist/{update-ACV4SAY3.js → update-WEA4N2TR.js} +5 -5
  141. package/dist/{update-check-266R4ZSY.js → update-check-DZOTONII.js} +3 -3
  142. package/dist/{upgrade-NWAZ7MLD.js → upgrade-K5FIQPQD.js} +1 -1
  143. package/dist/variant-cleanup-P4MBNLJD.js +33 -0
  144. package/dist/{version-notify-XRGTEEDK.js → version-notify-WFA5XB5A.js} +27 -25
  145. package/dist/web-assets/assets/index-BostMFoR.css +1 -0
  146. package/dist/web-assets/assets/index-CKlqUkyG.js +92 -0
  147. package/dist/web-assets/index.html +2 -2
  148. package/drizzle/0013_variant_purpose.sql +3 -0
  149. package/drizzle/meta/_journal.json +7 -0
  150. package/package.json +8 -5
  151. package/packages/extensions/pages/dist/ui/assets/index-BuXnE-rb.css +1 -0
  152. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +2 -0
  153. package/packages/extensions/pages/dist/ui/index.html +2 -2
  154. package/templates/_base/.init/.local/hooks/startup-context.ts +19 -2
  155. package/templates/_base/home/VOLUTE.md +13 -1
  156. package/templates/_base/src/lib/daemon-client.ts +27 -7
  157. package/templates/_base/src/lib/startup.ts +16 -1
  158. package/templates/claude/src/agent.ts +10 -3
  159. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  160. package/templates/claude/src/server.ts +4 -2
  161. package/templates/pi/src/agent.ts +7 -10
  162. package/templates/pi/src/lib/dispatch.ts +65 -0
  163. package/templates/pi/src/lib/event-handler.ts +47 -6
  164. package/dist/delivery-manager-TN5KK2AP.js +0 -34
  165. package/dist/echo-text-SEQCLGAT.js +0 -34
  166. package/dist/message-delivery-ZE3YGJDD.js +0 -38
  167. package/dist/mind-service-JFY7UG5E.js +0 -40
  168. package/dist/scheduler-HQSDYJFY.js +0 -32
  169. package/dist/sleep-manager-3NA7ZJP3.js +0 -36
  170. package/dist/spirit-WXN4IDBF.js +0 -23
  171. package/dist/system-chat-N2F7HVY3.js +0 -38
  172. package/dist/up-J2PBPOZL.js +0 -18
  173. package/dist/volute-config-A52FHWKM.js +0 -14
  174. package/dist/web-assets/assets/index-DtctoDXY.css +0 -1
  175. package/dist/web-assets/assets/index-Wpyb2CML.js +0 -86
  176. package/packages/extensions/pages/dist/ui/assets/index-BcY6lnq4.css +0 -1
  177. package/packages/extensions/pages/dist/ui/assets/index-CQjEQg1r.js +0 -2
@@ -1,88 +1,97 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- loadMergedEnv
4
- } from "./chunk-IYG4FBMP.js";
5
2
  import {
6
3
  spiritDir
7
- } from "./chunk-M6TTZRGH.js";
4
+ } from "./chunk-VIUH5CHQ.js";
5
+ import {
6
+ extractTextContent,
7
+ getRoutingConfig,
8
+ resolveDeliveryMode,
9
+ resolveRoute,
10
+ setRoutesChangeListener,
11
+ shouldGate
12
+ } from "./chunk-EFT2OALG.js";
8
13
  import {
9
14
  clearMind,
10
15
  linkInboundToActiveTurn,
11
16
  publish as publish3,
12
17
  summarizeOrphanedTurns
13
- } from "./chunk-K23P5Q5N.js";
14
- import {
15
- getPrompt
16
- } from "./chunk-JCMGDCGX.js";
18
+ } from "./chunk-K3JOLGWF.js";
17
19
  import {
18
20
  hashSkillDir,
19
21
  importSkillFromDir,
20
22
  removeSharedSkill,
21
23
  sharedSkillsDir
22
- } from "./chunk-IWJSNMSY.js";
24
+ } from "./chunk-GWVFDUNB.js";
25
+ import {
26
+ getOrCreateMindUser,
27
+ getOrCreateSystemUser,
28
+ getUser,
29
+ getUserByUsername,
30
+ syncMindProfile
31
+ } from "./chunk-4DSGN4FL.js";
23
32
  import {
24
33
  addMessage,
25
34
  createChannel,
26
35
  createConversation,
27
36
  findDMConversation,
28
37
  getChannelByName,
38
+ getChannelSettings,
29
39
  getParticipants,
30
40
  joinChannel,
31
- publish as publish2
32
- } from "./chunk-HPGN6XRT.js";
41
+ listConversationsForMind,
42
+ publish as publish2,
43
+ updateChannelSettings
44
+ } from "./chunk-VNRSXEBF.js";
45
+ import {
46
+ getPrompt
47
+ } from "./chunk-JPGHSXSV.js";
33
48
  import {
34
49
  isSandboxEnabled,
35
50
  wrapForSandbox
36
- } from "./chunk-M3XDVS5Q.js";
51
+ } from "./chunk-JCWB2ITW.js";
52
+ import {
53
+ MIND_LEVEL_SESSION,
54
+ recordNotice
55
+ } from "./chunk-XTEKINWG.js";
37
56
  import {
38
57
  markIdle,
39
58
  onMindEvent
40
- } from "./chunk-MTRMQFCQ.js";
59
+ } from "./chunk-ZX46PGCC.js";
41
60
  import {
42
61
  renderAvatarBlock
43
- } from "./chunk-VMCZBXF2.js";
62
+ } from "./chunk-FBBS4BVR.js";
44
63
  import {
45
64
  readVoluteConfig,
46
65
  resolveWakeTriggers,
47
66
  writeVoluteConfig
48
- } from "./chunk-PFZBNKUH.js";
49
- import {
50
- extractTextContent,
51
- getRoutingConfig,
52
- resolveDeliveryMode,
53
- resolveRoute,
54
- setRoutesChangeListener
55
- } from "./chunk-EIQDZC43.js";
67
+ } from "./chunk-WCFSBH33.js";
56
68
  import {
57
69
  chownMindDir,
58
70
  exec,
59
71
  isIsolationEnabled,
60
72
  mindUserName,
61
73
  wrapForIsolation
62
- } from "./chunk-DGGZVZTM.js";
74
+ } from "./chunk-DVN6DNMI.js";
75
+ import {
76
+ loadMergedEnv
77
+ } from "./chunk-PY24ZL62.js";
63
78
  import {
64
79
  aiCompleteUtility,
65
80
  getAiConfig,
81
+ missingCredentialWarning,
66
82
  resolveApiKey
67
- } from "./chunk-LWDTP6DH.js";
83
+ } from "./chunk-YIJGKHIS.js";
68
84
  import {
69
85
  readGlobalConfig,
70
86
  writeGlobalConfig
71
- } from "./chunk-SKNXMUIU.js";
72
- import {
73
- getOrCreateMindUser,
74
- getOrCreateSystemUser,
75
- getUser,
76
- getUserByUsername,
77
- syncMindProfile
78
- } from "./chunk-ODDGGLM6.js";
87
+ } from "./chunk-NJHN6IRB.js";
79
88
  import {
80
89
  publish,
81
90
  subscribe
82
- } from "./chunk-LTLFOSA4.js";
91
+ } from "./chunk-3B427GUK.js";
83
92
  import {
84
93
  logger_default
85
- } from "./chunk-T2TP6ZC6.js";
94
+ } from "./chunk-QUSMYGNQ.js";
86
95
  import {
87
96
  SPIRIT_NAME,
88
97
  findMind,
@@ -91,39 +100,39 @@ import {
91
100
  isSpiritName,
92
101
  mindDir,
93
102
  mindTmpDir,
103
+ readRegistry,
94
104
  setMindRunning,
95
105
  stateDir,
96
106
  voluteHome,
97
107
  voluteSystemDir
98
- } from "./chunk-WW6C3PLV.js";
108
+ } from "./chunk-RSBL3FAD.js";
99
109
  import {
100
110
  activity,
101
111
  channelGates,
102
112
  deliveryQueue,
103
113
  messages,
104
114
  mindHistory,
105
- mindNotices,
106
115
  sessions
107
- } from "./chunk-2MJ7EOMT.js";
116
+ } from "./chunk-P4LHVRNE.js";
108
117
 
109
118
  // packages/daemon/src/lib/delivery/message-delivery.ts
110
- import { and as and5, eq as eq6, inArray as inArray3 } from "drizzle-orm";
119
+ import { and as and4, eq as eq5, inArray as inArray3 } from "drizzle-orm";
111
120
 
112
121
  // packages/daemon/src/lib/daemon/sleep-manager.ts
113
122
  import { execFile as execFile2, spawn as spawnChild } from "child_process";
114
123
  import {
115
- existsSync as existsSync8,
124
+ existsSync as existsSync9,
116
125
  mkdirSync as mkdirSync5,
117
126
  readdirSync as readdirSync2,
118
- readFileSync as readFileSync7,
127
+ readFileSync as readFileSync8,
119
128
  readlinkSync,
120
129
  renameSync,
121
130
  writeFileSync as writeFileSync6
122
131
  } from "fs";
123
- import { resolve as resolve7 } from "path";
132
+ import { resolve as resolve8 } from "path";
124
133
  import { promisify as promisify2 } from "util";
125
134
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
126
- import { and as and2, eq as eq3, inArray } from "drizzle-orm";
135
+ import { and, eq as eq2, inArray } from "drizzle-orm";
127
136
 
128
137
  // packages/daemon/src/lib/daemon/mind-manager.ts
129
138
  import { execFile, spawn } from "child_process";
@@ -152,7 +161,9 @@ function loadJsonMap(path) {
152
161
  const map = /* @__PURE__ */ new Map();
153
162
  try {
154
163
  if (existsSync(path)) {
155
- const data = JSON.parse(readFileSync(path, "utf-8"));
164
+ const raw = readFileSync(path, "utf-8").trim();
165
+ if (!raw) return map;
166
+ const data = JSON.parse(raw);
156
167
  for (const [key, value] of Object.entries(data)) {
157
168
  if (typeof value === "number") map.set(key, value);
158
169
  }
@@ -348,106 +359,6 @@ function getMindToken(mindName) {
348
359
  return mindToToken.get(mindName) ?? null;
349
360
  }
350
361
 
351
- // packages/daemon/src/lib/daemon/notices.ts
352
- import { and, asc, eq, lte, or, sql } from "drizzle-orm";
353
- var nlog = logger_default.child("notices");
354
- var MIND_LEVEL_SESSION = "";
355
- var MAX_NOTICES_PER_SESSION = 100;
356
- async function recordNotice(input) {
357
- try {
358
- const db = await getDb();
359
- await db.insert(mindNotices).values({
360
- mind: input.mind,
361
- session: input.session,
362
- kind: input.kind,
363
- reason: input.reason,
364
- detail: input.detail,
365
- raw: input.raw ?? null
366
- });
367
- await db.run(
368
- sql`DELETE FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} AND id NOT IN (SELECT id FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} ORDER BY id DESC LIMIT ${MAX_NOTICES_PER_SESSION})`
369
- );
370
- } catch (err) {
371
- nlog.error(`failed to record notice for ${input.mind}:${input.session}`, logger_default.errorData(err));
372
- }
373
- }
374
- async function drainNotices(mind, session, limit = MAX_NOTICES_PER_SESSION) {
375
- const db = await getDb();
376
- return db.select().from(mindNotices).where(
377
- and(
378
- eq(mindNotices.mind, mind),
379
- or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION))
380
- )
381
- ).orderBy(asc(mindNotices.id)).limit(limit);
382
- }
383
- async function clearDeliveredNotices(mind, session, uptoId) {
384
- try {
385
- const db = await getDb();
386
- await db.delete(mindNotices).where(
387
- and(
388
- eq(mindNotices.mind, mind),
389
- or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION)),
390
- lte(mindNotices.id, uptoId)
391
- )
392
- );
393
- } catch (err) {
394
- nlog.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err));
395
- }
396
- }
397
- var NOTICE_HEADER = "[Notices] While you were unavailable, one or more turns failed. You're back now:";
398
- function formatNotices(notices) {
399
- if (notices.length === 0) return null;
400
- const failures = notices.filter((n) => n.kind !== "extension" && n.kind !== "budget");
401
- const budgets = notices.filter((n) => n.kind === "budget");
402
- const extensions = notices.filter((n) => n.kind === "extension");
403
- const blocks = [];
404
- if (failures.length > 0) {
405
- const groups = /* @__PURE__ */ new Map();
406
- for (const n of failures) {
407
- const time = localHM(n.created_at);
408
- const g = groups.get(n.reason);
409
- if (g) {
410
- g.count += 1;
411
- g.detail = n.detail;
412
- g.last = time;
413
- } else {
414
- groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
415
- }
416
- }
417
- const lines = [...groups.values()].map((g) => {
418
- const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
419
- const plural = g.count === 1 ? "turn" : "turns";
420
- return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
421
- });
422
- blocks.push(`${NOTICE_HEADER}
423
- ${lines.join("\n")}`);
424
- }
425
- if (budgets.length > 0) {
426
- const lines = budgets.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
427
- blocks.push(`[Budget]
428
- ${lines.join("\n")}`);
429
- }
430
- if (extensions.length > 0) {
431
- const byExt = /* @__PURE__ */ new Map();
432
- for (const n of extensions) {
433
- const arr = byExt.get(n.reason);
434
- if (arr) arr.push(n);
435
- else byExt.set(n.reason, [n]);
436
- }
437
- for (const [ext, items] of byExt) {
438
- const header = `[${ext.charAt(0).toUpperCase()}${ext.slice(1)}]`;
439
- const lines = items.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
440
- blocks.push(`${header}
441
- ${lines.join("\n")}`);
442
- }
443
- }
444
- return blocks.join("\n\n");
445
- }
446
- function localHM(createdAt) {
447
- const iso = createdAt.endsWith("Z") ? createdAt : `${createdAt.replace(" ", "T")}Z`;
448
- return new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
449
- }
450
-
451
362
  // packages/daemon/src/lib/daemon/restart-tracker.ts
452
363
  var DEFAULT_MAX_ATTEMPTS = 5;
453
364
  var DEFAULT_BASE_DELAY = 3e3;
@@ -550,6 +461,35 @@ var MindStartupError = class extends Error {
550
461
  function mindPidPath(name) {
551
462
  return resolve2(stateDir(name), "mind.pid");
552
463
  }
464
+ async function buildPendingContextMessage(name, context) {
465
+ const parts = [];
466
+ if (context.type === "merge" || context.type === "merged") {
467
+ parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
468
+ } else if (context.type === "split") {
469
+ parts.push(await getPrompt("split_message", { name, parent: String(context.parent ?? "") }));
470
+ if (context.purpose) parts.push(`Why you were split off: ${String(context.purpose)}`);
471
+ } else if (context.type === "sprouted") {
472
+ parts.push(await getPrompt("sprout_message"));
473
+ } else if (context.type === "upgraded") {
474
+ parts.push(await getPrompt("upgrade_message"));
475
+ } else {
476
+ parts.push(await getPrompt("restart_message"));
477
+ }
478
+ if (context.summary) parts.push(`Changes: ${context.summary}`);
479
+ if (context.justification) parts.push(`Why: ${context.justification}`);
480
+ if (context.memory) parts.push(`Context: ${context.memory}`);
481
+ if (context.memoryDelta) {
482
+ parts.push(
483
+ `
484
+ Your variant's memory & journal (not merged \u2014 integrate into your own memory what you want to keep):
485
+ ${context.memoryDelta}`
486
+ );
487
+ }
488
+ if (context.farewell)
489
+ parts.push(`Your variant's parting note (this was you, winding down):
490
+ ${context.farewell}`);
491
+ return parts.join("\n");
492
+ }
553
493
  var MindManager = class {
554
494
  minds = /* @__PURE__ */ new Map();
555
495
  stopping = /* @__PURE__ */ new Set();
@@ -642,7 +582,7 @@ var MindManager = class {
642
582
  if (isIsolationEnabled()) {
643
583
  try {
644
584
  await chownMindDir(mindStateDir, baseName);
645
- await chownMindDir(mindTmp, baseName);
585
+ await chownMindDir(resolve2(dir, ".mind"), baseName);
646
586
  } catch (err) {
647
587
  throw new Error(
648
588
  `Cannot start mind ${name}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
@@ -808,7 +748,7 @@ var MindManager = class {
808
748
  while (recentStderr.length > 20) recentStderr.shift();
809
749
  });
810
750
  try {
811
- await new Promise((resolve9, reject) => {
751
+ await new Promise((resolve10, reject) => {
812
752
  let settled = false;
813
753
  const deadline = Date.now() + 3e4;
814
754
  const onExit = (code) => {
@@ -838,7 +778,7 @@ var MindManager = class {
838
778
  const { ok } = await checkHealth(port);
839
779
  if (settled) return;
840
780
  if (ok) {
841
- finish(() => resolve9());
781
+ finish(() => resolve10());
842
782
  return;
843
783
  }
844
784
  if (Date.now() >= deadline) {
@@ -889,22 +829,7 @@ var MindManager = class {
889
829
  const tracked = this.minds.get(name);
890
830
  if (!tracked) return;
891
831
  this.pendingContext.delete(name);
892
- const parts = [];
893
- if (context.type === "merge" || context.type === "merged") {
894
- parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
895
- } else if (context.type === "split") {
896
- parts.push(await getPrompt("split_message", { name, parent: String(context.parent ?? "") }));
897
- } else if (context.type === "sprouted") {
898
- parts.push(await getPrompt("sprout_message"));
899
- } else if (context.type === "upgraded") {
900
- parts.push(await getPrompt("upgrade_message"));
901
- } else {
902
- parts.push(await getPrompt("restart_message"));
903
- }
904
- if (context.summary) parts.push(`Changes: ${context.summary}`);
905
- if (context.justification) parts.push(`Why: ${context.justification}`);
906
- if (context.memory) parts.push(`Context: ${context.memory}`);
907
- const content = parts.join("\n");
832
+ const content = await buildPendingContextMessage(name, context);
908
833
  let conversationId;
909
834
  try {
910
835
  const result = await sendSystemMessageDirect(name, content);
@@ -938,7 +863,7 @@ var MindManager = class {
938
863
  if (this.shuttingDown || this.stopping.has(name)) return;
939
864
  mlog.error(`mind ${name} exited with code ${code}`);
940
865
  try {
941
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
866
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
942
867
  const sleepMgr = getSleepManagerIfReady2();
943
868
  const sleepState = sleepMgr?.getState(name);
944
869
  if (sleepState?.sleeping) {
@@ -968,15 +893,15 @@ var MindManager = class {
968
893
  (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
969
894
  );
970
895
  try {
971
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TN5KK2AP.js");
896
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-RBXBPVWM.js");
972
897
  getDeliveryManager2().clearMindSessions(name);
973
898
  } catch (err) {
974
899
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
975
900
  mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
976
901
  }
977
902
  }
978
- import("./mind-activity-tracker-YVZPC4A5.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
979
- import("./activity-events-MIOK3IOU.js").then(
903
+ import("./mind-activity-tracker-YMMERB6B.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
904
+ import("./activity-events-GJJAI2WT.js").then(
980
905
  ({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
981
906
  ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
982
907
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
@@ -1006,23 +931,23 @@ var MindManager = class {
1006
931
  this.stopping.add(name);
1007
932
  const { child } = tracked;
1008
933
  this.minds.delete(name);
1009
- await new Promise((resolve9) => {
934
+ await new Promise((resolve10) => {
1010
935
  const killTimer = setTimeout(() => {
1011
936
  try {
1012
937
  process.kill(-child.pid, "SIGKILL");
1013
938
  } catch {
1014
939
  }
1015
- resolve9();
940
+ resolve10();
1016
941
  }, 5e3);
1017
942
  child.on("exit", () => {
1018
943
  clearTimeout(killTimer);
1019
- resolve9();
944
+ resolve10();
1020
945
  });
1021
946
  try {
1022
947
  process.kill(-child.pid, "SIGTERM");
1023
948
  } catch {
1024
949
  clearTimeout(killTimer);
1025
- resolve9();
950
+ resolve10();
1026
951
  }
1027
952
  });
1028
953
  this.stopping.delete(name);
@@ -1034,7 +959,7 @@ var MindManager = class {
1034
959
  mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
1035
960
  }
1036
961
  try {
1037
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TN5KK2AP.js");
962
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-RBXBPVWM.js");
1038
963
  getDeliveryManager2().clearMindSessions(name);
1039
964
  } catch (err) {
1040
965
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
@@ -1042,7 +967,7 @@ var MindManager = class {
1042
967
  }
1043
968
  }
1044
969
  try {
1045
- const { clearEchoTextCache } = await import("./echo-text-SEQCLGAT.js");
970
+ const { clearEchoTextCache } = await import("./echo-text-B6NZ5FGE.js");
1046
971
  clearEchoTextCache(name);
1047
972
  } catch (err) {
1048
973
  mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
@@ -1126,17 +1051,30 @@ function tryGetMindManager() {
1126
1051
  return instance;
1127
1052
  }
1128
1053
 
1054
+ // packages/daemon/src/lib/daemon/mind-service.ts
1055
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1056
+ import { resolve as resolve7 } from "path";
1057
+
1129
1058
  // packages/daemon/src/lib/chat/system-channel.ts
1130
1059
  var SYSTEM_CHANNEL_NAME = "system";
1060
+ var SYSTEM_CHANNEL_DESCRIPTION = "The commons \u2014 a shared room for every mind and the spirit on this system. Think out loud, check in, riff, coordinate.";
1131
1061
  var cachedChannelId = null;
1132
1062
  async function ensureSystemChannel() {
1133
1063
  if (cachedChannelId) return cachedChannelId;
1134
1064
  const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
1135
1065
  if (existing) {
1136
1066
  cachedChannelId = existing.id;
1067
+ const settings = await getChannelSettings(SYSTEM_CHANNEL_NAME);
1068
+ if (settings && settings.description == null) {
1069
+ await updateChannelSettings(SYSTEM_CHANNEL_NAME, {
1070
+ description: SYSTEM_CHANNEL_DESCRIPTION
1071
+ });
1072
+ }
1137
1073
  return existing.id;
1138
1074
  }
1139
- const conv = await createChannel(SYSTEM_CHANNEL_NAME);
1075
+ const conv = await createChannel(SYSTEM_CHANNEL_NAME, void 0, {
1076
+ description: SYSTEM_CHANNEL_DESCRIPTION
1077
+ });
1140
1078
  cachedChannelId = conv.id;
1141
1079
  logger_default.info("created #system channel");
1142
1080
  return conv.id;
@@ -1149,6 +1087,25 @@ async function joinSystemChannelForMind(mindName) {
1149
1087
  const user = await getOrCreateMindUser(mindName);
1150
1088
  await joinSystemChannel(user.id);
1151
1089
  }
1090
+ async function joinSystemChannelForSpirit() {
1091
+ const user = await getOrCreateSystemUser();
1092
+ await joinSystemChannel(user.id);
1093
+ }
1094
+ async function backfillSystemChannelMembers() {
1095
+ const entries = await readRegistry();
1096
+ for (const entry of entries) {
1097
+ if (entry.stage === "seed") continue;
1098
+ try {
1099
+ if (entry.mindType === "spirit") {
1100
+ await joinSystemChannelForSpirit();
1101
+ } else {
1102
+ await joinSystemChannelForMind(entry.name);
1103
+ }
1104
+ } catch (err) {
1105
+ logger_default.error(`failed to backfill ${entry.name} into #system`, logger_default.errorData(err));
1106
+ }
1107
+ }
1108
+ }
1152
1109
  async function announceToSystem(text) {
1153
1110
  const channelId = await ensureSystemChannel();
1154
1111
  const systemUser = await getOrCreateSystemUser();
@@ -1178,7 +1135,7 @@ import { dirname, resolve as resolve4 } from "path";
1178
1135
 
1179
1136
  // packages/daemon/src/web/middleware/auth.ts
1180
1137
  import { timingSafeEqual } from "crypto";
1181
- import { eq as eq2, lt } from "drizzle-orm";
1138
+ import { eq, lt } from "drizzle-orm";
1182
1139
  import { getCookie } from "hono/cookie";
1183
1140
  import { createMiddleware } from "hono/factory";
1184
1141
  var MIND_USER_CACHE_TTL = 5 * 60 * 1e3;
@@ -1206,14 +1163,14 @@ async function createSession(userId) {
1206
1163
  async function deleteSession(sessionId) {
1207
1164
  sessionCache.delete(sessionId);
1208
1165
  const db = await getDb();
1209
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
1166
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
1210
1167
  }
1211
1168
  async function getSessionUserId(sessionId) {
1212
1169
  const db = await getDb();
1213
- const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
1170
+ const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
1214
1171
  if (!row) return void 0;
1215
1172
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
1216
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
1173
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
1217
1174
  return void 0;
1218
1175
  }
1219
1176
  return row.userId;
@@ -1317,7 +1274,7 @@ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
1317
1274
  if (user.role !== "admin" && user.role !== "system") {
1318
1275
  const target = c.req.param(paramName) ?? "";
1319
1276
  const baseName = await getBaseName(target);
1320
- if (user.username !== baseName) {
1277
+ if (user.username !== baseName && await getBaseName(user.username) !== baseName) {
1321
1278
  return c.json({ error: "Forbidden" }, 403);
1322
1279
  }
1323
1280
  }
@@ -1734,7 +1691,7 @@ function resolveSkillsDir(manifest) {
1734
1691
  async function discoverBuiltinExtensions(disabledIds) {
1735
1692
  const builtins = [
1736
1693
  { id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
1737
- { id: "pages", load: async () => (await import("./src-OJHARHTF.js")).default },
1694
+ { id: "pages", load: async () => (await import("./src-ZN4LXMPI.js")).default },
1738
1695
  { id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
1739
1696
  ];
1740
1697
  const results = [];
@@ -1984,7 +1941,7 @@ async function installNpmExtension(pkg) {
1984
1941
  throw new Error(`Extension "${pkg}" is already installed`);
1985
1942
  }
1986
1943
  const dir = ensureExtensionsNpmDir();
1987
- const { exec: exec2 } = await import("./exec-QIFYZG4C.js");
1944
+ const { exec: exec2 } = await import("./exec-FBXA3CM4.js");
1988
1945
  try {
1989
1946
  await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
1990
1947
  } catch (err) {
@@ -2005,7 +1962,7 @@ async function uninstallNpmExtension(pkg) {
2005
1962
  packages.splice(idx, 1);
2006
1963
  writeExtensionsConfig(packages);
2007
1964
  try {
2008
- const { exec: exec2 } = await import("./exec-QIFYZG4C.js");
1965
+ const { exec: exec2 } = await import("./exec-FBXA3CM4.js");
2009
1966
  await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
2010
1967
  } catch (err) {
2011
1968
  logger_default.warn(
@@ -2179,8 +2136,7 @@ var MailPoller = class {
2179
2136
  this.reconnectAttempts = 0;
2180
2137
  this.reconnectDelay = INITIAL_RECONNECT_MS;
2181
2138
  if (this.disconnectedAt) {
2182
- this.catchUp(this.disconnectedAt);
2183
- this.disconnectedAt = null;
2139
+ void this.catchUpAndClear(this.disconnectedAt);
2184
2140
  }
2185
2141
  if (this.pingTimer) clearInterval(this.pingTimer);
2186
2142
  this.pingTimer = setInterval(() => {
@@ -2197,7 +2153,7 @@ var MailPoller = class {
2197
2153
  this.handleMessage(String(event.data));
2198
2154
  };
2199
2155
  this.ws.onclose = () => {
2200
- mlog2.warn("disconnected");
2156
+ mlog2.info("disconnected");
2201
2157
  if (!this.disconnectedAt) {
2202
2158
  this.disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
2203
2159
  }
@@ -2205,7 +2161,7 @@ var MailPoller = class {
2205
2161
  this.scheduleReconnect();
2206
2162
  };
2207
2163
  this.ws.onerror = (err) => {
2208
- mlog2.warn("WebSocket error", logger_default.errorData(err));
2164
+ mlog2.info("WebSocket error", logger_default.errorData(err));
2209
2165
  };
2210
2166
  }
2211
2167
  cleanup() {
@@ -2228,26 +2184,38 @@ var MailPoller = class {
2228
2184
  }, this.reconnectDelay);
2229
2185
  this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_MS);
2230
2186
  }
2231
- /** Fetch emails that arrived while disconnected */
2232
- catchUp(since) {
2233
- if (!this.config) return;
2187
+ /**
2188
+ * Run catch-up and advance the watermark only on full success. On any failure
2189
+ * (fetch error, non-OK response, delivery throw) the watermark is retained so
2190
+ * the next reconnect re-fetches the gap — at-least-once, never dropped.
2191
+ */
2192
+ async catchUpAndClear(since) {
2193
+ try {
2194
+ await this.catchUp(since);
2195
+ } catch (err) {
2196
+ mlog2.info("catch-up failed \u2014 will retry on next reconnect", logger_default.errorData(err));
2197
+ return;
2198
+ }
2199
+ if (this.disconnectedAt === since && this.ws?.readyState === WebSocket.OPEN) {
2200
+ this.disconnectedAt = null;
2201
+ }
2202
+ }
2203
+ /** Fetch and deliver emails that arrived while disconnected. Throws on failure. */
2204
+ async catchUp(since) {
2205
+ if (!this.config) throw new Error("systems config missing");
2234
2206
  const url = `${this.config.apiUrl}/api/mail/system/poll?since=${encodeURIComponent(since)}`;
2235
- fetch(url, {
2207
+ const res = await fetch(url, {
2236
2208
  headers: { Authorization: `Bearer ${this.config.apiKey}` }
2237
- }).then(async (res) => {
2238
- if (!res.ok) {
2239
- mlog2.warn(`catch-up poll failed: HTTP ${res.status}`);
2240
- return;
2241
- }
2242
- const data = await res.json();
2243
- if (!Array.isArray(data.emails) || data.emails.length === 0) return;
2244
- mlog2.info(`catching up on ${data.emails.length} missed emails`);
2245
- for (const email of data.emails) {
2246
- await this.deliver(email.mind, email);
2247
- }
2248
- }).catch((err) => {
2249
- mlog2.warn("catch-up error", logger_default.errorData(err));
2250
2209
  });
2210
+ if (!res.ok) {
2211
+ throw new Error(`catch-up poll failed: HTTP ${res.status}`);
2212
+ }
2213
+ const data = await res.json();
2214
+ if (!Array.isArray(data.emails) || data.emails.length === 0) return;
2215
+ mlog2.info(`catching up on ${data.emails.length} missed emails`);
2216
+ for (const email of data.emails) {
2217
+ await this.deliver(email.mind, email);
2218
+ }
2251
2219
  }
2252
2220
  handleMessage(data) {
2253
2221
  if (data === "pong") return;
@@ -2284,24 +2252,22 @@ var MailPoller = class {
2284
2252
  await this.deliver(mind, { ...email, mind });
2285
2253
  }
2286
2254
  async deliver(mind, email) {
2287
- const entry = await findMind(mind);
2288
- if (!entry || !entry.running) {
2289
- mlog2.warn(`skipping delivery to ${mind}: ${!entry ? "not found" : "not running"}`);
2255
+ if (!await findMind(mind)) {
2256
+ mlog2.warn(`skipping delivery to ${mind}: mind not found`);
2290
2257
  return;
2291
2258
  }
2292
2259
  const text = formatEmailContent(email);
2293
- try {
2294
- await deliverMessage(mind, {
2295
- content: [{ type: "text", text }],
2296
- channel: `mail:${email.from.address}`,
2297
- sender: email.from.name || email.from.address,
2298
- platform: "Email",
2299
- isDM: true
2300
- });
2301
- mlog2.info(`delivered email from ${email.from.address} to ${mind}`);
2302
- } catch (err) {
2303
- mlog2.warn(`failed to deliver to ${mind}`, logger_default.errorData(err));
2260
+ const delivered = await deliverMessage(mind, {
2261
+ content: [{ type: "text", text }],
2262
+ channel: `mail:${email.from.address}`,
2263
+ sender: email.from.name || email.from.address,
2264
+ platform: "Email",
2265
+ isDM: true
2266
+ });
2267
+ if (!delivered) {
2268
+ throw new Error(`delivery to ${mind} failed`);
2304
2269
  }
2270
+ mlog2.info(`delivered email from ${email.from.address} to ${mind}`);
2305
2271
  }
2306
2272
  };
2307
2273
  var instance2 = null;
@@ -2467,10 +2433,17 @@ var Scheduler = class {
2467
2433
  ${stderr}` : ""}`;
2468
2434
  slog2.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
2469
2435
  }
2436
+ } else if (Array.isArray(schedule.messages) && schedule.messages.length > 0) {
2437
+ const pick = schedule.messages[Math.floor(Math.random() * schedule.messages.length)];
2438
+ if (typeof pick !== "string" || !pick.trim()) {
2439
+ slog2.warn(`schedule "${schedule.id}" for ${mindName} has a malformed messages pool`);
2440
+ return;
2441
+ }
2442
+ text = pick;
2470
2443
  } else if (schedule.message) {
2471
2444
  text = schedule.message;
2472
2445
  } else {
2473
- slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
2446
+ slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message, messages, or script`);
2474
2447
  return;
2475
2448
  }
2476
2449
  await this.deliverSystem(mindName, `[${schedule.id}] ${text}`, {
@@ -2790,6 +2763,9 @@ async function startMindFull(name) {
2790
2763
  summary: `${name} started`
2791
2764
  }).catch((err) => logger_default.error("failed to publish mind_started activity", logger_default.errorData(err)));
2792
2765
  if (entry?.parent) return;
2766
+ recordMissingCredentialsNotice(baseName, entry?.template).catch(
2767
+ (err) => logger_default.error(`failed to check credentials for ${baseName}`, logger_default.errorData(err))
2768
+ );
2793
2769
  if (!entry || entry.stage === "seed") {
2794
2770
  if (entry?.stage === "seed") {
2795
2771
  sendSeedOrientation(baseName, entry.createdBy).catch(
@@ -2823,7 +2799,7 @@ async function startMindFull(name) {
2823
2799
  );
2824
2800
  }
2825
2801
  joinSystemChannelForMind(baseName).catch(
2826
- (err) => logger_default.error(`failed to join #system for ${baseName}`, logger_default.errorData(err))
2802
+ (err) => logger_default.warn(`failed to join #system for ${baseName}`, logger_default.errorData(err))
2827
2803
  );
2828
2804
  if (config?.tokenBudget) {
2829
2805
  getTokenBudget().setBudget(
@@ -2834,6 +2810,31 @@ async function startMindFull(name) {
2834
2810
  }
2835
2811
  notifyExtensionsMindStart(baseName);
2836
2812
  }
2813
+ async function recordMissingCredentialsNotice(mind, template) {
2814
+ const { hasUndeliveredNotice, recordNotice: recordNotice2, MIND_LEVEL_SESSION: MIND_LEVEL_SESSION2 } = await import("./notices-N6BCGHFV.js");
2815
+ const warning = await missingCredentialWarning(template, readMindModel(mind, template), mind);
2816
+ if (!warning) return;
2817
+ if (await hasUndeliveredNotice(mind, "no_credentials")) return;
2818
+ await recordNotice2({
2819
+ mind,
2820
+ session: MIND_LEVEL_SESSION2,
2821
+ kind: "startup",
2822
+ reason: "no_credentials",
2823
+ detail: warning
2824
+ });
2825
+ }
2826
+ function readMindModel(mind, template) {
2827
+ if (template !== "pi") return void 0;
2828
+ try {
2829
+ const configPath2 = resolve7(mindDir(mind), "home/.config/config.json");
2830
+ if (!existsSync8(configPath2)) return void 0;
2831
+ const model = JSON.parse(readFileSync7(configPath2, "utf-8")).model;
2832
+ return typeof model === "string" ? model : void 0;
2833
+ } catch (err) {
2834
+ logger_default.warn(`failed to read model for ${mind}`, logger_default.errorData(err));
2835
+ return void 0;
2836
+ }
2837
+ }
2837
2838
  async function sleepMind(name) {
2838
2839
  markIdle(name);
2839
2840
  await getMindManager().stopMind(name);
@@ -2854,10 +2855,13 @@ async function wakeMind(name) {
2854
2855
  async function startSpiritFull(name) {
2855
2856
  const entry = await findMind(name);
2856
2857
  if (entry?.dir) {
2857
- const { registerMindDir } = await import("./delivery-router-3QFG2ZEX.js");
2858
+ const { registerMindDir } = await import("./delivery-router-VUOL5YSA.js");
2858
2859
  registerMindDir(name, entry.dir);
2859
2860
  }
2860
2861
  await getMindManager().startMind(name);
2862
+ joinSystemChannelForSpirit().catch(
2863
+ (err) => logger_default.error(`failed to join #system for ${name}`, logger_default.errorData(err))
2864
+ );
2861
2865
  getScheduler().loadSchedules(name, entry?.dir ?? spiritDir());
2862
2866
  publish({
2863
2867
  type: "mind_started",
@@ -2884,14 +2888,14 @@ function buildSeedOrientation(createdBy, creatorType) {
2884
2888
  async function sendSeedOrientation(mindName, createdBy) {
2885
2889
  let creatorType;
2886
2890
  if (createdBy) {
2887
- const { getUserByUsername: getUserByUsername2 } = await import("./auth-A4VGHADI.js");
2891
+ const { getUserByUsername: getUserByUsername2 } = await import("./auth-ABYLOEZ7.js");
2888
2892
  creatorType = (await getUserByUsername2(createdBy))?.user_type;
2889
2893
  }
2890
2894
  await sendSystemMessage(mindName, buildSeedOrientation(createdBy, creatorType));
2891
2895
  }
2892
2896
  async function ensureCreatorDM(mindName, creatorUsername) {
2893
- const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-A4VGHADI.js");
2894
- const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-C7IKYSHD.js");
2897
+ const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-ABYLOEZ7.js");
2898
+ const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-TUM5PJJ4.js");
2895
2899
  const mindUser = await getOrCreateMindUser2(mindName);
2896
2900
  const creatorUser = await getUserByUsername2(creatorUsername);
2897
2901
  if (!creatorUser) {
@@ -2987,7 +2991,7 @@ var SleepManager = class {
2987
2991
  transitioning = /* @__PURE__ */ new Set();
2988
2992
  sleepConfigs = /* @__PURE__ */ new Map();
2989
2993
  get statePath() {
2990
- return resolve7(voluteSystemDir(), "sleep-state.json");
2994
+ return resolve8(voluteSystemDir(), "sleep-state.json");
2991
2995
  }
2992
2996
  start() {
2993
2997
  this.loadState();
@@ -3003,8 +3007,8 @@ var SleepManager = class {
3003
3007
  // --- State persistence ---
3004
3008
  loadState() {
3005
3009
  try {
3006
- if (existsSync8(this.statePath)) {
3007
- const data = JSON.parse(readFileSync7(this.statePath, "utf-8"));
3010
+ if (existsSync9(this.statePath)) {
3011
+ const data = JSON.parse(readFileSync8(this.statePath, "utf-8"));
3008
3012
  for (const [name, state2] of Object.entries(data)) {
3009
3013
  state2.triggerWakeHistory ??= [];
3010
3014
  state2.wakeFailures ??= 0;
@@ -3295,7 +3299,7 @@ var SleepManager = class {
3295
3299
  async flushQueuedMessages(name) {
3296
3300
  try {
3297
3301
  const db = await getDb();
3298
- const rows = await db.select().from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3302
+ const rows = await db.select().from(deliveryQueue).where(and(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
3299
3303
  if (rows.length === 0) return 0;
3300
3304
  const groups = /* @__PURE__ */ new Map();
3301
3305
  for (const row of rows) {
@@ -3307,7 +3311,7 @@ var SleepManager = class {
3307
3311
  `dropping unparseable queued message ${row.id} for ${name}`,
3308
3312
  logger_default.errorData(err)
3309
3313
  );
3310
- await db.delete(deliveryQueue).where(eq3(deliveryQueue.id, row.id));
3314
+ await db.delete(deliveryQueue).where(eq2(deliveryQueue.id, row.id));
3311
3315
  continue;
3312
3316
  }
3313
3317
  const channel = payload.channel ?? "unknown";
@@ -3350,7 +3354,7 @@ var SleepManager = class {
3350
3354
  * the real delivery stack. Returns whether the batch was handed off to the mind.
3351
3355
  */
3352
3356
  async deliverQueuedBatch(name, payloads) {
3353
- const { deliverBatch: deliverBatch2 } = await import("./message-delivery-ZE3YGJDD.js");
3357
+ const { deliverBatch: deliverBatch2 } = await import("./message-delivery-ABPV76EA.js");
3354
3358
  return deliverBatch2(name, payloads);
3355
3359
  }
3356
3360
  // --- Wake failure handling (#419) ---
@@ -3528,17 +3532,17 @@ var SleepManager = class {
3528
3532
  }
3529
3533
  }
3530
3534
  async waitForIdle(name, timeoutMs) {
3531
- return new Promise((resolve9) => {
3535
+ return new Promise((resolve10) => {
3532
3536
  const timeout = setTimeout(() => {
3533
3537
  unsub();
3534
- resolve9();
3538
+ resolve10();
3535
3539
  }, timeoutMs);
3536
3540
  const unsub = subscribe((event) => {
3537
3541
  if (event.mind !== name) return;
3538
3542
  if (event.type === "mind_done" || event.type === "mind_idle") {
3539
3543
  clearTimeout(timeout);
3540
3544
  unsub();
3541
- resolve9();
3545
+ resolve10();
3542
3546
  }
3543
3547
  });
3544
3548
  });
@@ -3546,15 +3550,15 @@ var SleepManager = class {
3546
3550
  async archiveSessions(name) {
3547
3551
  const dir = mindDir(name);
3548
3552
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
3549
- const sessionsDir = resolve7(dir, ".mind", "sessions");
3550
- if (existsSync8(sessionsDir)) {
3551
- const archiveDir = resolve7(sessionsDir, "archive");
3553
+ const sessionsDir = resolve8(dir, ".mind", "sessions");
3554
+ if (existsSync9(sessionsDir)) {
3555
+ const archiveDir = resolve8(sessionsDir, "archive");
3552
3556
  mkdirSync5(archiveDir, { recursive: true });
3553
3557
  for (const file of readdirSync2(sessionsDir)) {
3554
3558
  if (file === "archive" || !file.endsWith(".json")) continue;
3555
- const src = resolve7(sessionsDir, file);
3559
+ const src = resolve8(sessionsDir, file);
3556
3560
  const base = file.replace(/\.json$/, "");
3557
- const dest = resolve7(archiveDir, `${base}-${timestamp}.json`);
3561
+ const dest = resolve8(archiveDir, `${base}-${timestamp}.json`);
3558
3562
  try {
3559
3563
  renameSync(src, dest);
3560
3564
  } catch (err) {
@@ -3562,14 +3566,14 @@ var SleepManager = class {
3562
3566
  }
3563
3567
  }
3564
3568
  }
3565
- const piSessionsDir = resolve7(dir, ".mind", "pi-sessions");
3566
- if (existsSync8(piSessionsDir)) {
3567
- const archiveDir = resolve7(piSessionsDir, "archive");
3569
+ const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
3570
+ if (existsSync9(piSessionsDir)) {
3571
+ const archiveDir = resolve8(piSessionsDir, "archive");
3568
3572
  mkdirSync5(archiveDir, { recursive: true });
3569
3573
  for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
3570
3574
  if (entry.name === "archive" || !entry.isDirectory()) continue;
3571
- const src = resolve7(piSessionsDir, entry.name);
3572
- const dest = resolve7(archiveDir, `${entry.name}-${timestamp}`);
3575
+ const src = resolve8(piSessionsDir, entry.name);
3576
+ const dest = resolve8(archiveDir, `${entry.name}-${timestamp}`);
3573
3577
  try {
3574
3578
  renameSync(src, dest);
3575
3579
  } catch (err) {
@@ -3579,8 +3583,8 @@ var SleepManager = class {
3579
3583
  }
3580
3584
  }
3581
3585
  async runWakeContextScript(name, sleepingSince, duration) {
3582
- const scriptPath = resolve7(mindDir(name), "home", ".local", "hooks", "wake-context.sh");
3583
- if (!existsSync8(scriptPath)) return "";
3586
+ const scriptPath = resolve8(mindDir(name), "home", ".local", "hooks", "wake-context.sh");
3587
+ if (!existsSync9(scriptPath)) return "";
3584
3588
  const input = JSON.stringify({
3585
3589
  sleepingSince,
3586
3590
  duration,
@@ -3630,7 +3634,7 @@ var SleepManager = class {
3630
3634
  async buildQueuedSummary(name) {
3631
3635
  try {
3632
3636
  const db = await getDb();
3633
- const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3637
+ const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
3634
3638
  if (rows.length === 0) return "No messages arrived while you slept.";
3635
3639
  const channelCounts = /* @__PURE__ */ new Map();
3636
3640
  const senders = /* @__PURE__ */ new Set();
@@ -3677,7 +3681,7 @@ var SleepManager = class {
3677
3681
  } catch {
3678
3682
  try {
3679
3683
  const portHex = port.toString(16).toUpperCase().padStart(4, "0");
3680
- const tcp6 = readFileSync7("/proc/net/tcp6", "utf-8");
3684
+ const tcp6 = readFileSync8("/proc/net/tcp6", "utf-8");
3681
3685
  for (const line of tcp6.split("\n")) {
3682
3686
  if (!line.includes(`:${portHex} `)) continue;
3683
3687
  const fields = line.trim().split(/\s+/);
@@ -3746,8 +3750,8 @@ function getSleepManagerIfReady() {
3746
3750
 
3747
3751
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3748
3752
  import { realpath } from "fs/promises";
3749
- import { resolve as resolve8 } from "path";
3750
- import { and as and4, eq as eq5, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3753
+ import { resolve as resolve9 } from "path";
3754
+ import { and as and3, eq as eq4, inArray as inArray2, sql } from "drizzle-orm";
3751
3755
 
3752
3756
  // packages/daemon/src/lib/chat/typing.ts
3753
3757
  var DEFAULT_TTL_MS = 1e4;
@@ -3841,7 +3845,7 @@ function publishTypingForChannels(channels, map) {
3841
3845
  }
3842
3846
 
3843
3847
  // packages/daemon/src/lib/delivery/send-gate.ts
3844
- import { and as and3, asc as asc2, desc, eq as eq4, gt } from "drizzle-orm";
3848
+ import { and as and2, asc, desc, eq as eq3, gt } from "drizzle-orm";
3845
3849
  var state = /* @__PURE__ */ new Map();
3846
3850
  function extractText(raw) {
3847
3851
  if (!raw) return "";
@@ -3856,7 +3860,7 @@ function extractText(raw) {
3856
3860
  }
3857
3861
  async function maxMessageId(conversationId) {
3858
3862
  const db = await getDb();
3859
- const rows = await db.select({ id: messages.id }).from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(1);
3863
+ const rows = await db.select({ id: messages.id }).from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(1);
3860
3864
  return rows[0]?.id ?? 0;
3861
3865
  }
3862
3866
  async function onDeliveredToMind(mindBase, conversationId) {
@@ -3875,7 +3879,7 @@ async function checkStaleSend(mindBase, conversationId, excludeSenders) {
3875
3879
  const entry = state.get(mindBase)?.get(conversationId);
3876
3880
  if (!entry?.turnOpen || entry.heldThisTurn) return { held: false };
3877
3881
  const db = await getDb();
3878
- const rows = await db.select({ id: messages.id, sender: messages.sender_name, content: messages.content }).from(messages).where(and3(eq4(messages.conversation_id, conversationId), gt(messages.id, entry.baselineId))).orderBy(asc2(messages.id));
3882
+ const rows = await db.select({ id: messages.id, sender: messages.sender_name, content: messages.content }).from(messages).where(and2(eq3(messages.conversation_id, conversationId), gt(messages.id, entry.baselineId))).orderBy(asc(messages.id));
3879
3883
  const excluded = new Set(excludeSenders.filter(Boolean));
3880
3884
  const unseen = rows.filter((r) => r.sender && !excluded.has(r.sender));
3881
3885
  if (unseen.length === 0) return { held: false };
@@ -3936,7 +3940,7 @@ var DeliveryManager = class {
3936
3940
  isMindRunning = (name) => tryGetMindManager()?.isRunning(name) ?? false;
3937
3941
  /** Sends a system message to a mind (invites, release summaries); overridable in tests. */
3938
3942
  notify = async (mindName, text) => {
3939
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-N2F7HVY3.js");
3943
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-M2XOBKEM.js");
3940
3944
  await sendSystemMessage2(mindName, text);
3941
3945
  };
3942
3946
  constructor() {
@@ -3990,7 +3994,7 @@ var DeliveryManager = class {
3990
3994
  if (route.destination === "file") {
3991
3995
  return { routed: true, session: route.path, destination: "file", mode: "immediate" };
3992
3996
  }
3993
- if (!route.matched && config.gateUnmatched !== false) {
3997
+ if (shouldGate(config, route)) {
3994
3998
  dlog.debug(`gating unmatched channel ${payload.channel} for ${mindName}`);
3995
3999
  await this.gateMessage(mindName, route.session, payload);
3996
4000
  return { routed: true, session: route.session, destination: "mind", mode: "gated" };
@@ -4070,9 +4074,9 @@ var DeliveryManager = class {
4070
4074
  try {
4071
4075
  const db = await getDb();
4072
4076
  rows = await db.select().from(deliveryQueue).where(
4073
- and4(
4074
- eq5(deliveryQueue.status, "pending"),
4075
- sql2`(${deliveryQueue.next_attempt_at} IS NULL OR ${deliveryQueue.next_attempt_at} <= datetime('now'))`
4077
+ and3(
4078
+ eq4(deliveryQueue.status, "pending"),
4079
+ sql`(${deliveryQueue.next_attempt_at} IS NULL OR ${deliveryQueue.next_attempt_at} <= datetime('now'))`
4076
4080
  )
4077
4081
  ).orderBy(deliveryQueue.id).limit(REDRIVE_BATCH_LIMIT);
4078
4082
  } catch (err) {
@@ -4132,7 +4136,7 @@ var DeliveryManager = class {
4132
4136
  let rows;
4133
4137
  try {
4134
4138
  const db = await getDb();
4135
- rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, baseName), eq5(deliveryQueue.status, "gated")));
4139
+ rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, baseName), eq4(deliveryQueue.status, "gated")));
4136
4140
  } catch (err) {
4137
4141
  dlog.warn(`failed to read gated rows for ${baseName}`, logger_default.errorData(err));
4138
4142
  return;
@@ -4164,7 +4168,13 @@ var DeliveryManager = class {
4164
4168
  }
4165
4169
  const channel = row.channel ?? payload.channel ?? "unknown";
4166
4170
  const list = byChannel.get(channel) ?? [];
4167
- list.push({ id: row.id, session });
4171
+ list.push({
4172
+ id: row.id,
4173
+ session,
4174
+ channel,
4175
+ sender: payload.sender ?? row.sender ?? null,
4176
+ content: extractTextContent(payload.content)
4177
+ });
4168
4178
  byChannel.set(channel, list);
4169
4179
  }
4170
4180
  const promote = [];
@@ -4199,16 +4209,38 @@ var DeliveryManager = class {
4199
4209
  if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
4200
4210
  return;
4201
4211
  }
4212
+ const orderedPromote = [...promote].sort((a, b) => a.id - b.id);
4202
4213
  try {
4203
4214
  const db = await getDb();
4204
- for (const p of promote) {
4205
- await db.update(deliveryQueue).set({ status: "pending", session: p.session, attempts: 0, next_attempt_at: null }).where(eq5(deliveryQueue.id, p.id));
4215
+ for (const p of orderedPromote) {
4216
+ await db.transaction(async (tx) => {
4217
+ await tx.insert(mindHistory).values({
4218
+ mind: baseName,
4219
+ type: "inbound",
4220
+ channel: p.channel,
4221
+ sender: p.sender,
4222
+ content: p.content
4223
+ });
4224
+ await tx.update(deliveryQueue).set({ status: "pending", session: p.session, attempts: 0, next_attempt_at: null }).where(eq4(deliveryQueue.id, p.id));
4225
+ });
4206
4226
  }
4207
4227
  dlog.info(`released ${promote.length} gated message(s) for ${baseName} after route change`);
4208
4228
  } catch (err) {
4209
- dlog.warn(`failed to promote gated rows for ${baseName}`, logger_default.errorData(err));
4229
+ dlog.error(
4230
+ `failed to record+promote gated rows for ${baseName} (channels: ${[...byChannel.keys()].join(", ")})`,
4231
+ logger_default.errorData(err)
4232
+ );
4210
4233
  return;
4211
4234
  }
4235
+ for (const p of orderedPromote) {
4236
+ publish3(baseName, {
4237
+ mind: baseName,
4238
+ type: "inbound",
4239
+ channel: p.channel,
4240
+ content: p.content ?? void 0,
4241
+ sender: p.sender ?? void 0
4242
+ });
4243
+ }
4212
4244
  if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
4213
4245
  await this.redrive();
4214
4246
  }
@@ -4220,7 +4252,7 @@ var DeliveryManager = class {
4220
4252
  if (!channel) return false;
4221
4253
  try {
4222
4254
  const db = await getDb();
4223
- const rows = await db.select({ state: channelGates.state }).from(channelGates).where(and4(eq5(channelGates.mind, baseName), eq5(channelGates.channel, channel)));
4255
+ const rows = await db.select({ state: channelGates.state }).from(channelGates).where(and3(eq4(channelGates.mind, baseName), eq4(channelGates.channel, channel)));
4224
4256
  return rows[0]?.state === "declined";
4225
4257
  } catch (err) {
4226
4258
  dlog.warn(`failed to read gate state for ${baseName}/${channel}`, logger_default.errorData(err));
@@ -4237,13 +4269,13 @@ var DeliveryManager = class {
4237
4269
  const db = await getDb();
4238
4270
  await db.insert(channelGates).values({ mind: baseName, channel, state: "declined" }).onConflictDoUpdate({
4239
4271
  target: [channelGates.mind, channelGates.channel],
4240
- set: { state: "declined", updated_at: sql2`(datetime('now'))` }
4272
+ set: { state: "declined", updated_at: sql`(datetime('now'))` }
4241
4273
  });
4242
4274
  const archived = await db.update(deliveryQueue).set({ status: "archived" }).where(
4243
- and4(
4244
- eq5(deliveryQueue.mind, baseName),
4245
- eq5(deliveryQueue.channel, channel),
4246
- eq5(deliveryQueue.status, "gated")
4275
+ and3(
4276
+ eq4(deliveryQueue.mind, baseName),
4277
+ eq4(deliveryQueue.channel, channel),
4278
+ eq4(deliveryQueue.status, "gated")
4247
4279
  )
4248
4280
  ).returning({ id: deliveryQueue.id });
4249
4281
  dlog.info(
@@ -4273,7 +4305,7 @@ var DeliveryManager = class {
4273
4305
  */
4274
4306
  async getPending(mindName) {
4275
4307
  const db = await getDb();
4276
- const rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, mindName), eq5(deliveryQueue.status, "gated")));
4308
+ const rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, mindName), eq4(deliveryQueue.status, "gated")));
4277
4309
  const byChannel = /* @__PURE__ */ new Map();
4278
4310
  for (const row of rows) {
4279
4311
  const ch = row.channel ?? "unknown";
@@ -4443,7 +4475,7 @@ var DeliveryManager = class {
4443
4475
  const backoffSec = Math.round(
4444
4476
  Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
4445
4477
  );
4446
- await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql2`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq5(deliveryQueue.id, row.id));
4478
+ await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq4(deliveryQueue.id, row.id));
4447
4479
  }
4448
4480
  } catch (err) {
4449
4481
  dlog.warn("failed to schedule delivery retry", logger_default.errorData(err));
@@ -4672,10 +4704,10 @@ var DeliveryManager = class {
4672
4704
  if (declined) return;
4673
4705
  try {
4674
4706
  const db = await getDb();
4675
- const rows = await db.select({ count: sql2`count(*)` }).from(deliveryQueue).where(
4676
- and4(
4677
- eq5(deliveryQueue.mind, baseName),
4678
- eq5(deliveryQueue.channel, payload.channel),
4707
+ const rows = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
4708
+ and3(
4709
+ eq4(deliveryQueue.mind, baseName),
4710
+ eq4(deliveryQueue.channel, payload.channel),
4679
4711
  inArray2(deliveryQueue.status, ["gated", "archived"])
4680
4712
  )
4681
4713
  );
@@ -4692,18 +4724,22 @@ var DeliveryManager = class {
4692
4724
  const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;
4693
4725
  const channel = payload.channel ?? "unknown";
4694
4726
  const heldLine = gatedCount > 1 ? `${gatedCount} messages from this channel are being held, unrouted \u2014 you've not routed it yet.` : `Someone new is reaching out \u2014 you don't have a route for this channel yet.`;
4695
- const notification = [
4696
- `[New channel: ${channel}]`,
4697
- heldLine,
4698
- `Sender: ${payload.sender ?? "unknown"}`,
4727
+ const detailLines = [
4699
4728
  payload.platform ? `Platform: ${payload.platform}` : null,
4700
- payload.participantCount ? `Participants: ${payload.participantCount}` : null,
4701
- "",
4702
- `Preview: ${preview}`,
4703
- "",
4704
- `To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json \u2014 only the ${GATED_RELEASE_LIMIT_PER_CHANNEL} most recent held messages are replayed; older ones stay in the channel history (volute chat read ${channel}).`,
4705
- `To stop hearing about it, decline the channel: volute chat channels decline ${channel}`
4706
- ].filter((line) => line !== null).join("\n");
4729
+ payload.participantCount ? `Participants: ${payload.participantCount}` : null
4730
+ ].filter((l) => l !== null);
4731
+ const details = detailLines.length > 0 ? `${detailLines.join("\n")}
4732
+
4733
+ ` : "\n";
4734
+ const { getPrompt: getPrompt2 } = await import("./prompts-MDHDLWEX.js");
4735
+ const notification = await getPrompt2("channel_invite", {
4736
+ channel,
4737
+ heldLine,
4738
+ sender: payload.sender ?? "unknown",
4739
+ details,
4740
+ preview,
4741
+ limit: String(GATED_RELEASE_LIMIT_PER_CHANNEL)
4742
+ });
4707
4743
  await this.notify(mindName, notification);
4708
4744
  }
4709
4745
  /**
@@ -4777,8 +4813,8 @@ var DeliveryManager = class {
4777
4813
  const dir = mindDir(p.username);
4778
4814
  const config = readVoluteConfig(dir);
4779
4815
  if (!config?.profile?.avatar) continue;
4780
- filePath = resolve8(dir, "home", config.profile.avatar);
4781
- const homeDir = resolve8(dir, "home");
4816
+ filePath = resolve9(dir, "home", config.profile.avatar);
4817
+ const homeDir = resolve9(dir, "home");
4782
4818
  if (!filePath.startsWith(`${homeDir}/`)) {
4783
4819
  dlog.warn(`avatar path for ${p.username} escapes home directory, skipping`);
4784
4820
  continue;
@@ -4797,7 +4833,7 @@ var DeliveryManager = class {
4797
4833
  throw err;
4798
4834
  }
4799
4835
  } else {
4800
- filePath = resolve8(voluteHome(), "avatars", p.avatar);
4836
+ filePath = resolve9(voluteHome(), "avatars", p.avatar);
4801
4837
  }
4802
4838
  const rendered = await renderAvatarBlock(filePath, p.username);
4803
4839
  if (rendered) blocks.push(...rendered);
@@ -4894,6 +4930,15 @@ async function recordInbound(mind, channel, sender, content) {
4894
4930
  });
4895
4931
  return insertedId;
4896
4932
  }
4933
+ async function deliverSproutedNotice(mind) {
4934
+ await recordInbound(mind, "system", "system", "[seed has sprouted]");
4935
+ const convs = await listConversationsForMind(mind);
4936
+ for (const conv of convs) {
4937
+ await addMessage(conv.id, "assistant", "system", [
4938
+ { type: "text", text: "[seed has sprouted]" }
4939
+ ]);
4940
+ }
4941
+ }
4897
4942
  async function recordOutbound(mind, channel, content, opts = {}) {
4898
4943
  try {
4899
4944
  const db = await getDb();
@@ -4925,7 +4970,7 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4925
4970
  content: mindHistory.content,
4926
4971
  message_id: mindHistory.message_id,
4927
4972
  turn_id: mindHistory.turn_id
4928
- }).from(mindHistory).where(and5(eq6(mindHistory.id, outboundId), eq6(mindHistory.mind, mind))).limit(1);
4973
+ }).from(mindHistory).where(and4(eq5(mindHistory.id, outboundId), eq5(mindHistory.mind, mind))).limit(1);
4929
4974
  const row = rows[0];
4930
4975
  if (!row) {
4931
4976
  dlog2.warn(`outbound marker references missing record: mind=${mind} id=${outboundId}`);
@@ -4933,13 +4978,13 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4933
4978
  }
4934
4979
  const alreadyTagged = row.turn_id != null;
4935
4980
  if (!alreadyTagged) {
4936
- await db.update(mindHistory).set({ turn_id: turnId }).where(eq6(mindHistory.id, outboundId));
4981
+ await db.update(mindHistory).set({ turn_id: turnId }).where(eq5(mindHistory.id, outboundId));
4937
4982
  }
4938
4983
  if (row.message_id) {
4939
4984
  await db.update(messages).set({
4940
4985
  turn_id: turnId,
4941
4986
  ...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
4942
- }).where(eq6(messages.id, Number(row.message_id)));
4987
+ }).where(eq5(messages.id, Number(row.message_id)));
4943
4988
  }
4944
4989
  if (!alreadyTagged) {
4945
4990
  publish3(mind, {
@@ -4988,6 +5033,17 @@ function resolveSleepAction(sleepBehavior, wokenByTrigger, wakeTriggerMatches) {
4988
5033
  if (!sleepBehavior && wakeTriggerMatches) return "queue-and-wake";
4989
5034
  return "queue";
4990
5035
  }
5036
+ function willGate(baseName, payload) {
5037
+ if (payload.session) return false;
5038
+ const config = getRoutingConfig(baseName);
5039
+ const route = resolveRoute(config, {
5040
+ channel: payload.channel,
5041
+ sender: payload.sender ?? void 0,
5042
+ isDM: payload.isDM,
5043
+ participantCount: payload.participantCount
5044
+ });
5045
+ return shouldGate(config, route);
5046
+ }
4991
5047
  async function deliverMessage(mindName, payload, opts = {}) {
4992
5048
  try {
4993
5049
  const baseName = await getBaseName(mindName);
@@ -4998,9 +5054,9 @@ async function deliverMessage(mindName, payload, opts = {}) {
4998
5054
  }
4999
5055
  if (!opts.isFlush) {
5000
5056
  const textContent = extractTextContent(payload.content);
5001
- await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
5002
5057
  const sleepManager = getSleepManagerIfReady();
5003
5058
  if (sleepManager?.isSleeping(baseName)) {
5059
+ await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
5004
5060
  const sleepState = sleepManager.getState(baseName);
5005
5061
  const action = resolveSleepAction(
5006
5062
  payload.whileSleeping,
@@ -5019,6 +5075,9 @@ async function deliverMessage(mindName, payload, opts = {}) {
5019
5075
  }
5020
5076
  return true;
5021
5077
  }
5078
+ if (!willGate(baseName, payload)) {
5079
+ await recordInbound(baseName, payload.channel, payload.sender ?? null, textContent);
5080
+ }
5022
5081
  }
5023
5082
  const manager = getDeliveryManager();
5024
5083
  await manager.routeAndDeliver(mindName, payload);
@@ -5128,7 +5187,7 @@ async function spiritWillHandle() {
5128
5187
  const spiritEntry = await findMind(SPIRIT_NAME);
5129
5188
  if (spiritEntry?.running && spiritEntry.mindType === "spirit") return true;
5130
5189
  try {
5131
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5190
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5132
5191
  const sm = getSleepManagerIfReady2();
5133
5192
  if (sm?.isSleeping(SPIRIT_NAME)) return true;
5134
5193
  } catch (err) {
@@ -5156,7 +5215,7 @@ async function generateSystemFallbackReply(conversationId, mindName, message) {
5156
5215
  if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
5157
5216
  }
5158
5217
  try {
5159
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-3NA7ZJP3.js");
5218
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-DSPXFHGX.js");
5160
5219
  const sm = getSleepManagerIfReady2();
5161
5220
  if (sm) {
5162
5221
  const state2 = sm.getState(mindName);
@@ -5210,10 +5269,6 @@ export {
5210
5269
  checkHealth,
5211
5270
  RotatingLog,
5212
5271
  syncProviderToMinds,
5213
- recordNotice,
5214
- drainNotices,
5215
- clearDeliveredNotices,
5216
- formatNotices,
5217
5272
  RestartTracker,
5218
5273
  getTypingMap,
5219
5274
  isConversationId,
@@ -5227,6 +5282,7 @@ export {
5227
5282
  MindStartupError,
5228
5283
  initMindManager,
5229
5284
  getMindManager,
5285
+ tryGetMindManager,
5230
5286
  invalidateMindUserCache,
5231
5287
  SESSION_MAX_AGE,
5232
5288
  invalidateSessionCache,
@@ -5262,6 +5318,7 @@ export {
5262
5318
  initTokenBudget,
5263
5319
  getTokenBudget,
5264
5320
  startMindFull,
5321
+ recordMissingCredentialsNotice,
5265
5322
  sleepMind,
5266
5323
  wakeMind,
5267
5324
  startSpiritFull,
@@ -5274,6 +5331,7 @@ export {
5274
5331
  getSleepManager,
5275
5332
  getSleepManagerIfReady,
5276
5333
  recordInbound,
5334
+ deliverSproutedNotice,
5277
5335
  recordOutbound,
5278
5336
  linkToolResultToTurn,
5279
5337
  resolveSleepAction,
@@ -5281,5 +5339,7 @@ export {
5281
5339
  deliverBatch,
5282
5340
  ensureSystemChannel,
5283
5341
  joinSystemChannel,
5342
+ joinSystemChannelForMind,
5343
+ backfillSystemChannelMembers,
5284
5344
  announceToSystem
5285
5345
  };