volute 0.33.0 → 0.34.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 (182) hide show
  1. package/dist/{accept-D5VBM7JW.js → accept-TW6V4WI4.js} +6 -6
  2. package/dist/{activity-events-XJO3P4RR.js → activity-events-BN7V6KCC.js} +4 -4
  3. package/dist/{ai-service-SBY2WG7O.js → ai-service-PSILB5WD.js} +5 -5
  4. package/dist/{api-client-YPKOZP2O.js → api-client-XUXOB7LI.js} +1 -1
  5. package/dist/api.d.ts +426 -3
  6. package/dist/{archive-INXYFVCW.js → archive-C2VEMQOR.js} +4 -4
  7. package/dist/{auth-GKCDSO4T.js → auth-ZFZXJZDQ.js} +5 -5
  8. package/dist/{bridge-TXWWPPOJ.js → bridge-O753D5F4.js} +6 -6
  9. package/dist/{chat-U5ZOME3O.js → chat-BHYX7DJ4.js} +9 -9
  10. package/dist/{chunk-M7UL5S3Q.js → chunk-2IOP6PHB.js} +1 -1
  11. package/dist/{chunk-NPKSDYA2.js → chunk-47XDEWWV.js} +5 -5
  12. package/dist/{chunk-RSX4OPZY.js → chunk-47ZPNLF4.js} +7 -7
  13. package/dist/{chunk-RPZZSXV3.js → chunk-4JSR7YO7.js} +20 -1
  14. package/dist/{chunk-N432I7QH.js → chunk-6OWJXUAR.js} +1 -1
  15. package/dist/{chunk-I5KY25PQ.js → chunk-6WAWMWR5.js} +1 -1
  16. package/dist/{chunk-NNB4WIG7.js → chunk-7F2SW2KD.js} +2 -2
  17. package/dist/chunk-7KJOFUNN.js +22 -0
  18. package/dist/{chunk-7J3HEVR7.js → chunk-B2BVAIZ4.js} +15 -9
  19. package/dist/{chunk-VH33ZWMW.js → chunk-BDK73LK6.js} +1 -1
  20. package/dist/{chunk-QTUVYI7W.js → chunk-BFWHBQK4.js} +1 -1
  21. package/dist/{chunk-JYVGHWEJ.js → chunk-BM474GX6.js} +3 -3
  22. package/dist/{chunk-LOEJ4HPQ.js → chunk-BTWAGDV5.js} +1 -1
  23. package/dist/{chunk-A2A4KLFE.js → chunk-CVL5IGIR.js} +596 -40
  24. package/dist/{chunk-RVGLDGMI.js → chunk-E5C7OWZ2.js} +20 -22
  25. package/dist/chunk-FYCALD4Q.js +23 -0
  26. package/dist/{chunk-SKLSMHXO.js → chunk-IS7WJ56Q.js} +1 -1
  27. package/dist/{chunk-2NGTS5UU.js → chunk-M3K5AARV.js} +1 -1
  28. package/dist/{chunk-ALEF47VT.js → chunk-MLOQKQNB.js} +1 -1
  29. package/dist/{chunk-C7I35G4R.js → chunk-N3DNFPVA.js} +41 -5
  30. package/dist/{chunk-LRCG2JLP.js → chunk-N7BLAHNE.js} +5 -1
  31. package/dist/{chunk-UKVWJRKN.js → chunk-PLDWHR4D.js} +1 -1
  32. package/dist/{chunk-3Z2DPESO.js → chunk-TAHX36HZ.js} +126 -81
  33. package/dist/{chunk-KIEPMIM5.js → chunk-U5BTYSAL.js} +1 -1
  34. package/dist/{chunk-GY5HBI7A.js → chunk-V45JXOWY.js} +2 -2
  35. package/dist/{chunk-JUKK7FPS.js → chunk-V6ZCNULL.js} +2 -2
  36. package/dist/{chunk-KVK2DLWI.js → chunk-XWXBJQBE.js} +2 -2
  37. package/dist/cli.js +23 -23
  38. package/dist/{clock-BVH3V6E3.js → clock-3X4DSC2N.js} +40 -25
  39. package/dist/{cloud-sync-4NWLMFVH.js → cloud-sync-TG3TIX5H.js} +21 -21
  40. package/dist/{config-H2H4UIF7.js → config-OROA5DUA.js} +4 -4
  41. package/dist/connectors/discord-bridge.js +1 -1
  42. package/dist/connectors/slack-bridge.js +1 -1
  43. package/dist/connectors/telegram-bridge.js +1 -1
  44. package/dist/{conversations-AWI5SZW2.js → conversations-HL2JP5GI.js} +5 -5
  45. package/dist/{create-YWD2TIP4.js → create-3SEKKI6P.js} +6 -6
  46. package/dist/{create-2FK7Z46Y.js → create-UOSOQ2HN.js} +4 -4
  47. package/dist/daemon-client-WOAQXXBM.js +12 -0
  48. package/dist/{daemon-restart-GOBUKLX7.js → daemon-restart-5ABHNXJZ.js} +9 -9
  49. package/dist/daemon.js +1747 -688
  50. package/dist/{db-RA45JBFG.js → db-PLEDCBHZ.js} +1 -1
  51. package/dist/db-RYX3SS2W.js +9 -0
  52. package/dist/{delete-QTGWEDBI.js → delete-KYOVWR23.js} +3 -3
  53. package/dist/delivery-manager-2BR5NZKF.js +32 -0
  54. package/dist/{delivery-router-FL45JL7N.js → delivery-router-D5ELDMS2.js} +4 -4
  55. package/dist/down-QVFN4UPK.js +15 -0
  56. package/dist/{env-JCOF2222.js → env-R34DT7XL.js} +12 -8
  57. package/dist/exec-DVLXKRIO.js +17 -0
  58. package/dist/{export-SUYRLI5Q.js → export-6ZXAXATG.js} +6 -6
  59. package/dist/extension-PM42QCID.js +97 -0
  60. package/dist/extensions-BBGVL5JC.js +38 -0
  61. package/dist/{files-65PMW5IK.js → files-VQV2VZQO.js} +7 -7
  62. package/dist/{import-DDUFE7AY.js → import-MK2I2T6F.js} +5 -5
  63. package/dist/{isolation-LLAYQYDY.js → isolation-62MKDZN3.js} +4 -4
  64. package/dist/{join-I5QEE3LG.js → join-DGYHTJUH.js} +3 -3
  65. package/dist/lib-DYEZMGW7.js +6588 -0
  66. package/dist/{list-JQ463EDA.js → list-C644WTHV.js} +18 -10
  67. package/dist/{login-D7ETSU4R.js → login-IIGEQPHL.js} +6 -6
  68. package/dist/{login-RIJF2F4G.js → login-KZQLMAWE.js} +4 -4
  69. package/dist/{logout-5MLHZALK.js → logout-AGTZVRGP.js} +4 -4
  70. package/dist/{logout-UZJRGY4Z.js → logout-KD6GXIJJ.js} +4 -4
  71. package/dist/message-delivery-V3R6NXJP.js +42 -0
  72. package/dist/{mind-IOJFLEM5.js → mind-BI4EPBVZ.js} +19 -19
  73. package/dist/{mind-activity-tracker-F6O4Q2SL.js → mind-activity-tracker-2ACNHA7B.js} +5 -5
  74. package/dist/mind-history-WOYFLQAI.js +264 -0
  75. package/dist/{mind-list-WUPMQDYQ.js → mind-list-6VPM7GUQ.js} +4 -4
  76. package/dist/mind-manager-MWW3BTS4.js +32 -0
  77. package/dist/{mind-profile-P67FEHOY.js → mind-profile-WPG42U5Y.js} +2 -2
  78. package/dist/mind-service-VIKZJK2M.js +38 -0
  79. package/dist/{mind-sleep-WW2IX7JT.js → mind-sleep-XDISJY74.js} +6 -6
  80. package/dist/{mind-status-L3EFFRPR.js → mind-status-7FTZWPZF.js} +4 -4
  81. package/dist/{mind-wake-VSSGW465.js → mind-wake-KIIKEI3A.js} +6 -6
  82. package/dist/{package-U3VFO273.js → package-V2WHWVG6.js} +8 -5
  83. package/dist/{read-EBY56C33.js → read-H5C26YO7.js} +20 -10
  84. package/dist/{read-stdin-HQJ7774D.js → read-stdin-PIRM6A2Y.js} +1 -1
  85. package/dist/{register-HD74C4TT.js → register-J27WP33N.js} +6 -6
  86. package/dist/{registry-PJ4S5PHQ.js → registry-UYV5S6QT.js} +3 -3
  87. package/dist/{reject-UJKFBHRO.js → reject-OEANJYIA.js} +6 -6
  88. package/dist/{restart-3UCMRUVC.js → restart-V5EGYBJG.js} +4 -4
  89. package/dist/{sandbox-GJOK4QLQ.js → sandbox-SI5HMBP3.js} +5 -5
  90. package/dist/scheduler-AGG3L2FO.js +32 -0
  91. package/dist/{schema-PA3M5ZKH.js → schema-ETMABTW4.js} +4 -2
  92. package/dist/{seed-QDYVLG74.js → seed-WNGI6PNW.js} +2 -2
  93. package/dist/{seed-check-S2IX25RL.js → seed-check-PXTH7YXS.js} +2 -2
  94. package/dist/{seed-cmd-DKOUFEAU.js → seed-cmd-VENFTGS3.js} +4 -4
  95. package/dist/{seed-create-4XBBOLRH.js → seed-create-663ALOKH.js} +6 -6
  96. package/dist/{seed-sprout-GQEIIQRT.js → seed-sprout-EH3AGKAI.js} +12 -12
  97. package/dist/{send-QIV2INHB.js → send-7FUUUZZH.js} +23 -10
  98. package/dist/{setup-TISPCO22.js → setup-GGMKENLN.js} +4 -4
  99. package/dist/{setup-XMCBE3LF.js → setup-Z3DEVWV7.js} +11 -11
  100. package/dist/{skill-PSQGRRJX.js → skill-DKNYJS4P.js} +14 -10
  101. package/dist/skills/plan-coordinator/SKILL.md +60 -0
  102. package/dist/skills/volute-mind/SKILL.md +7 -221
  103. package/dist/skills/volute-mind/references/extensions.md +37 -0
  104. package/dist/skills/volute-mind/references/integrations.md +48 -0
  105. package/dist/skills/volute-mind/references/routing.md +86 -0
  106. package/dist/skills/volute-mind/references/sleep.md +33 -0
  107. package/dist/skills/volute-mind/references/variants.md +31 -0
  108. package/dist/{skills-7FV7EJTE.js → skills-Q6VZ2UGD.js} +11 -7
  109. package/dist/sleep-manager-BJK2ROPX.js +36 -0
  110. package/dist/spirit-4JP4TY4C.js +23 -0
  111. package/dist/{split-STOROBYJ.js → split-3YPMS2CL.js} +3 -3
  112. package/dist/{sprout-WKLZXUIQ.js → sprout-E3HJIV2Z.js} +2 -2
  113. package/dist/{start-K2NCUUCG.js → start-W3TPKX4D.js} +4 -4
  114. package/dist/{status-3JBTFSMI.js → status-4OVFXFEJ.js} +7 -7
  115. package/dist/{stop-H26JZDXF.js → stop-GTT6YWYO.js} +4 -4
  116. package/dist/system-channel-DXD2JBOU.js +36 -0
  117. package/dist/system-chat-TYLOL7SX.js +36 -0
  118. package/dist/{systems-XRI52VCH.js → systems-AYLO727G.js} +7 -7
  119. package/dist/{tailscale-XHQBZROW.js → tailscale-ZEUK7GKZ.js} +3 -3
  120. package/dist/{template-hash-A6VVKOXJ.js → template-hash-EJRTKE36.js} +1 -1
  121. package/dist/up-PA7F2CXE.js +18 -0
  122. package/dist/{update-UD543CXX.js → update-HG4LCUSG.js} +7 -7
  123. package/dist/{update-check-ZD6OOIYQ.js → update-check-X3YG4WVP.js} +4 -4
  124. package/dist/{upgrade-O4Q7WJM3.js → upgrade-YGNIDICG.js} +3 -3
  125. package/dist/{variant-7TGZHOU3.js → variant-MZUMRTQO.js} +1 -1
  126. package/dist/{version-notify-NBI2MTJO.js → version-notify-YCH4UVQ2.js} +19 -19
  127. package/dist/{volute-config-HD7WWUQC.js → volute-config-WBKYJGYQ.js} +1 -1
  128. package/dist/web-assets/assets/index-DiiwC-CZ.css +1 -0
  129. package/dist/web-assets/assets/index-d6y5b9Ij.js +75 -0
  130. package/dist/web-assets/ext-theme.css +48 -9
  131. package/dist/web-assets/index.html +2 -2
  132. package/drizzle/0005_meta_summaries.sql +15 -0
  133. package/drizzle/meta/0005_snapshot.json +7 -0
  134. package/drizzle/meta/_journal.json +7 -0
  135. package/package.json +7 -4
  136. package/packages/extensions/plan/dist/ui/assets/index-CJj2gZnZ.css +1 -0
  137. package/packages/extensions/plan/dist/ui/assets/index-FMEJmvQz.js +61 -0
  138. package/packages/extensions/plan/dist/ui/index.html +14 -0
  139. package/packages/extensions/plan/skills/plan/SKILL.md +43 -0
  140. package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +37 -0
  141. package/templates/_base/home/VOLUTE.md +12 -19
  142. package/templates/_base/src/lib/context-breakdown.ts +450 -0
  143. package/templates/_base/src/lib/format-prefix.ts +17 -0
  144. package/templates/_base/src/lib/hook-loader.ts +8 -2
  145. package/templates/_base/src/lib/router.ts +75 -33
  146. package/templates/_base/src/lib/routing.ts +4 -1
  147. package/templates/_base/src/lib/startup.ts +16 -8
  148. package/templates/_base/src/lib/types.ts +2 -1
  149. package/templates/_base/src/lib/volute-server.ts +69 -8
  150. package/templates/claude/.init/CLAUDE.md +4 -10
  151. package/templates/claude/package.json.tmpl +1 -0
  152. package/templates/claude/src/agent.ts +100 -32
  153. package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
  154. package/templates/claude/src/lib/stream-consumer.ts +2 -2
  155. package/templates/claude/src/server.ts +1 -0
  156. package/templates/codex/package.json.tmpl +1 -0
  157. package/templates/codex/src/agent.ts +80 -8
  158. package/templates/codex/src/server.ts +1 -4
  159. package/templates/pi/package.json.tmpl +1 -0
  160. package/templates/pi/src/agent.ts +115 -36
  161. package/templates/pi/src/lib/event-handler.ts +22 -7
  162. package/templates/pi/src/lib/reply-instructions-extension.ts +23 -4
  163. package/templates/pi/src/lib/subagents.ts +20 -17
  164. package/templates/pi/src/server.ts +2 -5
  165. package/dist/chunk-K3NQKI34.js +0 -10
  166. package/dist/daemon-client-6QXHZ7US.js +0 -12
  167. package/dist/db-F34YLV7D.js +0 -9
  168. package/dist/delivery-manager-PFAKEJTC.js +0 -32
  169. package/dist/down-FWWTEKXM.js +0 -15
  170. package/dist/extension-OBTGKQQD.js +0 -175
  171. package/dist/extensions-KYNTVTMO.js +0 -30
  172. package/dist/history-DKCDI3JO.js +0 -128
  173. package/dist/message-delivery-DFF5SJRM.js +0 -42
  174. package/dist/mind-manager-NBJF5D26.js +0 -32
  175. package/dist/mind-service-2MQ6UK5N.js +0 -38
  176. package/dist/scheduler-ZZ7XGQG6.js +0 -32
  177. package/dist/sleep-manager-JTXSN7NV.js +0 -36
  178. package/dist/spirit-VRONKFMF.js +0 -23
  179. package/dist/system-chat-JAPOJ3KE.js +0 -36
  180. package/dist/up-M5AS6SBV.js +0 -18
  181. package/dist/web-assets/assets/index-CWJrVveV.css +0 -1
  182. package/dist/web-assets/assets/index-DJt14FRI.js +0 -75
package/dist/daemon.js CHANGED
@@ -3,10 +3,18 @@ import {
3
3
  checkForUpdate,
4
4
  checkForUpdateCached,
5
5
  getCurrentVersion
6
- } from "./chunk-M7UL5S3Q.js";
6
+ } from "./chunk-2IOP6PHB.js";
7
7
  import {
8
8
  computeTemplateHash
9
9
  } from "./chunk-PVY5W6QN.js";
10
+ import {
11
+ acceptPending,
12
+ formatFileSize,
13
+ listPending,
14
+ rejectPending,
15
+ stageFile,
16
+ validateFilePath
17
+ } from "./chunk-MLOQKQNB.js";
10
18
  import {
11
19
  PROMPT_DEFAULTS,
12
20
  PROMPT_KEYS,
@@ -45,7 +53,6 @@ import {
45
53
  recordInbound,
46
54
  recordOutbound,
47
55
  resolveMindToken,
48
- setSummaryEventId,
49
56
  startMindFull,
50
57
  stopMindFull,
51
58
  subscribe as subscribe3,
@@ -54,53 +61,22 @@ import {
54
61
  tagUntaggedInbound,
55
62
  tagUntaggedOutbound,
56
63
  trackToolUse
57
- } from "./chunk-3Z2DPESO.js";
58
- import {
59
- extractTextContent
60
- } from "./chunk-SKLSMHXO.js";
61
- import {
62
- getActiveMinds,
63
- onMindEvent,
64
- stopAll
65
- } from "./chunk-LOEJ4HPQ.js";
64
+ } from "./chunk-TAHX36HZ.js";
66
65
  import {
67
- addMessage,
68
- createChannel,
69
- createConversation,
70
- deleteConversationForUser,
71
- findDMConversation,
72
- fireWebhook,
73
- getChannelByName,
74
- getConversation,
75
- getMessages,
76
- getMessagesPaginated,
77
- getParticipants,
78
- getUnreadCounts,
79
- initWebhook,
80
- isConversationForMind,
81
- isParticipant,
82
- isParticipantOrOwner,
83
- joinChannel,
84
- leaveChannel,
85
- listChannels,
86
- listConversationsForMind,
87
- listConversationsForUser,
88
- listConversationsWithParticipants,
89
- markConversationRead,
90
- publish,
91
- setConversationPrivate,
92
- subscribe as subscribe2
93
- } from "./chunk-RVGLDGMI.js";
94
- import "./chunk-GY5HBI7A.js";
95
- import {
96
- acceptPending,
97
- formatFileSize,
98
- listPending,
99
- rejectPending,
100
- stageFile,
101
- validateFilePath
102
- } from "./chunk-ALEF47VT.js";
103
- import "./chunk-7J3HEVR7.js";
66
+ deleteSystemsConfig,
67
+ getAllDiscoveredExtensions,
68
+ getExtensionStandardSkills,
69
+ getLoadedExtensions,
70
+ installNpmExtension,
71
+ loadAllExtensions,
72
+ notifyExtensionsDaemonStart,
73
+ notifyExtensionsDaemonStop,
74
+ readSystemsConfig,
75
+ setExtensionEnabled,
76
+ uninstallNpmExtension,
77
+ writeSystemsConfig
78
+ } from "./chunk-CVL5IGIR.js";
79
+ import "./chunk-B2BVAIZ4.js";
104
80
  import {
105
81
  applyInitFiles,
106
82
  composeTemplate,
@@ -108,10 +84,39 @@ import {
108
84
  findTemplatesRoot,
109
85
  listFiles
110
86
  } from "./chunk-G53F3JA4.js";
87
+ import {
88
+ SEED_SKILLS,
89
+ STANDARD_SKILLS,
90
+ autoUpdateMindSkills,
91
+ getSharedSkill,
92
+ getStandardSkillsWithExtensions,
93
+ importSkillFromDir,
94
+ initDefaultSkills,
95
+ installSkill,
96
+ isAutoUpdateSkillsEnabled,
97
+ listFilesRecursive,
98
+ listMindSkills,
99
+ listSharedSkills,
100
+ publishSkill,
101
+ removeSharedSkill,
102
+ sharedSkillsDir,
103
+ syncBuiltinSkills,
104
+ uninstallSkill,
105
+ updateSkill
106
+ } from "./chunk-N3DNFPVA.js";
111
107
  import {
112
108
  readVoluteConfig,
113
109
  writeVoluteConfig
114
110
  } from "./chunk-OYAKCAVY.js";
111
+ import "./chunk-V45JXOWY.js";
112
+ import {
113
+ extractTextContent
114
+ } from "./chunk-IS7WJ56Q.js";
115
+ import {
116
+ getActiveMinds,
117
+ onMindEvent,
118
+ stopAll
119
+ } from "./chunk-BTWAGDV5.js";
115
120
  import {
116
121
  findBridgeForChannel,
117
122
  findOpenClawSession,
@@ -125,27 +130,17 @@ import {
125
130
  resolveChannelMapping,
126
131
  setBridgeConfig,
127
132
  setChannelMapping
128
- } from "./chunk-RSX4OPZY.js";
133
+ } from "./chunk-47ZPNLF4.js";
129
134
  import {
130
135
  loadMergedEnv,
131
136
  mindEnvPath,
132
137
  readEnv,
133
138
  sharedEnvPath,
134
139
  writeEnv
135
- } from "./chunk-2NGTS5UU.js";
140
+ } from "./chunk-M3K5AARV.js";
136
141
  import {
137
142
  isHomeOnlyArchive
138
- } from "./chunk-I5KY25PQ.js";
139
- import {
140
- deleteSystemsConfig,
141
- getExtensionStandardSkills,
142
- getLoadedExtensions,
143
- loadAllExtensions,
144
- notifyExtensionsDaemonStart,
145
- notifyExtensionsDaemonStop,
146
- readSystemsConfig,
147
- writeSystemsConfig
148
- } from "./chunk-A2A4KLFE.js";
143
+ } from "./chunk-6WAWMWR5.js";
149
144
  import "./chunk-PB65JZK2.js";
150
145
  import {
151
146
  approveUser,
@@ -162,31 +157,42 @@ import {
162
157
  listUsers,
163
158
  listUsersByType,
164
159
  setUserRole,
160
+ syncMindProfile,
165
161
  updateUserProfile,
166
162
  verifyUser
167
- } from "./chunk-JYVGHWEJ.js";
163
+ } from "./chunk-BM474GX6.js";
164
+ import {
165
+ addMessage,
166
+ createChannel,
167
+ createConversation,
168
+ deleteConversationForUser,
169
+ findDMConversation,
170
+ fireWebhook,
171
+ getChannelByName,
172
+ getConversation,
173
+ getMessages,
174
+ getMessagesPaginated,
175
+ getParticipants,
176
+ getUnreadCounts,
177
+ initWebhook,
178
+ isConversationForMind,
179
+ isParticipant,
180
+ isParticipantOrOwner,
181
+ joinChannel,
182
+ leaveChannel,
183
+ listChannels,
184
+ listConversationsForMind,
185
+ listConversationsForUser,
186
+ listConversationsWithParticipants,
187
+ markConversationRead,
188
+ publish,
189
+ setConversationPrivate,
190
+ subscribe as subscribe2
191
+ } from "./chunk-E5C7OWZ2.js";
168
192
  import {
169
193
  broadcast,
170
194
  subscribe
171
- } from "./chunk-KVK2DLWI.js";
172
- import {
173
- SEED_SKILLS,
174
- STANDARD_SKILLS,
175
- getSharedSkill,
176
- getStandardSkillsWithExtensions,
177
- importSkillFromDir,
178
- initDefaultSkills,
179
- installSkill,
180
- listFilesRecursive,
181
- listMindSkills,
182
- listSharedSkills,
183
- publishSkill,
184
- removeSharedSkill,
185
- sharedSkillsDir,
186
- syncBuiltinSkills,
187
- uninstallSkill,
188
- updateSkill
189
- } from "./chunk-C7I35G4R.js";
195
+ } from "./chunk-XWXBJQBE.js";
190
196
  import {
191
197
  aiCompleteUtility,
192
198
  getAiConfig,
@@ -203,16 +209,17 @@ import {
203
209
  setEnabledModels,
204
210
  setUtilityModel,
205
211
  unqualifyModelId
206
- } from "./chunk-QTUVYI7W.js";
212
+ } from "./chunk-BFWHBQK4.js";
207
213
  import {
208
214
  logBuffer,
209
215
  logger_default
210
216
  } from "./chunk-YUIHSKR6.js";
217
+ import "./chunk-D424ZQGI.js";
211
218
  import {
212
219
  exec,
213
220
  gitExec,
214
221
  resolveVoluteBin
215
- } from "./chunk-KIEPMIM5.js";
222
+ } from "./chunk-U5BTYSAL.js";
216
223
  import {
217
224
  chownMindDir,
218
225
  createMindUser,
@@ -221,16 +228,15 @@ import {
221
228
  isIsolationEnabled,
222
229
  mindUserName,
223
230
  wrapForIsolation
224
- } from "./chunk-VH33ZWMW.js";
231
+ } from "./chunk-BDK73LK6.js";
225
232
  import {
226
233
  isSetupComplete,
227
234
  readGlobalConfig,
228
235
  writeGlobalConfig
229
- } from "./chunk-N432I7QH.js";
236
+ } from "./chunk-6OWJXUAR.js";
230
237
  import {
231
238
  readSessionFile
232
- } from "./chunk-UKVWJRKN.js";
233
- import "./chunk-D424ZQGI.js";
239
+ } from "./chunk-PLDWHR4D.js";
234
240
  import {
235
241
  buildVoluteSlug,
236
242
  slugify
@@ -257,24 +263,26 @@ import {
257
263
  validateMindName,
258
264
  voluteHome,
259
265
  voluteSystemDir
260
- } from "./chunk-LRCG2JLP.js";
266
+ } from "./chunk-N7BLAHNE.js";
261
267
  import {
262
268
  activity,
269
+ conversationParticipants,
263
270
  conversations,
264
271
  messages,
265
272
  mindHistory,
266
273
  sessions,
274
+ summaries,
267
275
  systemPrompts,
268
276
  turns,
269
277
  users
270
- } from "./chunk-RPZZSXV3.js";
278
+ } from "./chunk-4JSR7YO7.js";
271
279
  import {
272
280
  __export
273
- } from "./chunk-K3NQKI34.js";
281
+ } from "./chunk-7KJOFUNN.js";
274
282
 
275
283
  // src/daemon.ts
276
284
  import { randomBytes } from "crypto";
277
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
285
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
278
286
  import { homedir as homedir3 } from "os";
279
287
  import { resolve as resolve19 } from "path";
280
288
  import { format } from "util";
@@ -450,160 +458,923 @@ var BridgeManager = class {
450
458
  logStream.write(chunk);
451
459
  lastStderr = chunk.toString().trim();
452
460
  });
453
- if (child.pid) {
454
- this.saveBridgePid(platform, child.pid);
455
- }
456
- this.bridges.set(platform, { child, platform });
457
- this.restartTracker.reset(platform);
458
- child.on("exit", (code) => {
459
- const tracked = this.bridges.get(platform);
460
- if (tracked?.child === child) {
461
- this.bridges.delete(platform);
462
- }
463
- if (this.shuttingDown) return;
464
- if (this.stopping.has(platform)) return;
465
- blog.error(`bridge ${platform} exited with code ${code}`);
466
- if (lastStderr) blog.warn(`bridge ${platform} last output: ${lastStderr}`);
467
- const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(platform);
468
- if (!shouldRestart) {
469
- blog.error(`bridge ${platform} crashed ${attempt} times \u2014 giving up`);
470
- return;
471
- }
472
- blog.info(
473
- `restarting bridge ${platform} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, in ${delay}ms`
461
+ if (child.pid) {
462
+ this.saveBridgePid(platform, child.pid);
463
+ }
464
+ this.bridges.set(platform, { child, platform });
465
+ this.restartTracker.reset(platform);
466
+ child.on("exit", (code) => {
467
+ const tracked = this.bridges.get(platform);
468
+ if (tracked?.child === child) {
469
+ this.bridges.delete(platform);
470
+ }
471
+ if (this.shuttingDown) return;
472
+ if (this.stopping.has(platform)) return;
473
+ blog.error(`bridge ${platform} exited with code ${code}`);
474
+ if (lastStderr) blog.warn(`bridge ${platform} last output: ${lastStderr}`);
475
+ const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(platform);
476
+ if (!shouldRestart) {
477
+ blog.error(`bridge ${platform} crashed ${attempt} times \u2014 giving up`);
478
+ return;
479
+ }
480
+ blog.info(
481
+ `restarting bridge ${platform} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, in ${delay}ms`
482
+ );
483
+ setTimeout(() => {
484
+ if (this.shuttingDown || this.stopping.has(platform)) return;
485
+ this.startBridge(platform, daemonPort).catch((err) => {
486
+ blog.error(`failed to restart bridge ${platform}`, logger_default.errorData(err));
487
+ });
488
+ }, delay);
489
+ });
490
+ blog.info(`started bridge ${platform}`);
491
+ }
492
+ async stopBridge(platform) {
493
+ const tracked = this.bridges.get(platform);
494
+ if (!tracked) return;
495
+ this.stopping.add(platform);
496
+ this.bridges.delete(platform);
497
+ await new Promise((resolve20) => {
498
+ tracked.child.on("exit", () => resolve20());
499
+ try {
500
+ if (tracked.child.pid) {
501
+ process.kill(-tracked.child.pid, "SIGTERM");
502
+ } else {
503
+ tracked.child.kill("SIGTERM");
504
+ }
505
+ } catch (err) {
506
+ if (err instanceof Error && err.code !== "ESRCH") {
507
+ blog.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
508
+ }
509
+ resolve20();
510
+ }
511
+ setTimeout(() => {
512
+ try {
513
+ if (tracked.child.pid) {
514
+ process.kill(-tracked.child.pid, "SIGKILL");
515
+ } else {
516
+ tracked.child.kill("SIGKILL");
517
+ }
518
+ } catch {
519
+ }
520
+ resolve20();
521
+ }, 5e3);
522
+ });
523
+ this.stopping.delete(platform);
524
+ this.restartTracker.reset(platform);
525
+ try {
526
+ this.removeBridgePid(platform);
527
+ } catch (err) {
528
+ blog.warn(`failed to remove PID file for bridge ${platform}`, logger_default.errorData(err));
529
+ }
530
+ blog.info(`stopped bridge ${platform}`);
531
+ }
532
+ async stopAll() {
533
+ this.shuttingDown = true;
534
+ const platforms = [...this.bridges.keys()];
535
+ await Promise.all(platforms.map((p) => this.stopBridge(p)));
536
+ }
537
+ getBridgeStatus() {
538
+ return [...this.bridges.entries()].map(([platform, tracked]) => ({
539
+ platform,
540
+ running: !tracked.child.killed
541
+ }));
542
+ }
543
+ isRunning(platform) {
544
+ const tracked = this.bridges.get(platform);
545
+ return tracked != null && !tracked.child.killed;
546
+ }
547
+ bridgePidPath(platform) {
548
+ return resolve2(voluteSystemDir(), "bridges", `${platform}.pid`);
549
+ }
550
+ saveBridgePid(platform, pid) {
551
+ const pidPath = this.bridgePidPath(platform);
552
+ mkdirSync(dirname(pidPath), { recursive: true });
553
+ writeFileSync(pidPath, String(pid));
554
+ }
555
+ removeBridgePid(platform) {
556
+ try {
557
+ unlinkSync(this.bridgePidPath(platform));
558
+ } catch (err) {
559
+ if (err instanceof Error && err.code !== "ENOENT") {
560
+ blog.warn(`failed to remove PID file for bridge ${platform}`, logger_default.errorData(err));
561
+ }
562
+ }
563
+ }
564
+ killOrphanBridge(platform) {
565
+ const pidPath = this.bridgePidPath(platform);
566
+ if (!existsSync2(pidPath)) return;
567
+ try {
568
+ const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
569
+ if (pid > 0) {
570
+ try {
571
+ process.kill(-pid, "SIGTERM");
572
+ } catch {
573
+ try {
574
+ process.kill(pid, "SIGTERM");
575
+ } catch {
576
+ }
577
+ }
578
+ blog.warn(`killed orphan bridge ${platform} (pid ${pid})`);
579
+ }
580
+ } catch (err) {
581
+ if (err instanceof Error && err.code !== "ESRCH") {
582
+ blog.debug(`orphan bridge ${platform} cleanup: ${err}`);
583
+ }
584
+ }
585
+ try {
586
+ unlinkSync(pidPath);
587
+ } catch (err) {
588
+ if (err instanceof Error && err.code !== "ENOENT") {
589
+ blog.warn(`failed to clean up PID file for orphan bridge ${platform}`, logger_default.errorData(err));
590
+ }
591
+ }
592
+ }
593
+ resolveBuiltinBridge(platform) {
594
+ return searchUpwards("connectors", `${platform}-bridge.js`);
595
+ }
596
+ };
597
+ var instance = null;
598
+ function initBridgeManager() {
599
+ if (instance) throw new Error("BridgeManager already initialized");
600
+ instance = new BridgeManager();
601
+ return instance;
602
+ }
603
+ function getBridgeManager() {
604
+ if (!instance) throw new Error("BridgeManager not initialized \u2014 call initBridgeManager() first");
605
+ return instance;
606
+ }
607
+
608
+ // src/lib/daemon/summarizer.ts
609
+ import { and, desc, eq, gte, like, lt, sql } from "drizzle-orm";
610
+
611
+ // src/lib/format-tool.ts
612
+ function summarizeTool(name, input) {
613
+ if (input && typeof input === "object") {
614
+ const args = input;
615
+ const val = args.path ?? args.command ?? args.query ?? args.url;
616
+ if (typeof val === "string") {
617
+ const brief = val.length > 60 ? `${val.slice(0, 57)}...` : val;
618
+ return `[${name} ${brief}]`;
619
+ }
620
+ }
621
+ return `[${name}]`;
622
+ }
623
+
624
+ // src/lib/daemon/summarizer.ts
625
+ var sLog = logger_default.child("summarizer");
626
+ var SYSTEM_MIND = "_system";
627
+ function getPeriodKey(date, period) {
628
+ switch (period) {
629
+ case "hour": {
630
+ const y = date.getFullYear();
631
+ const m = String(date.getMonth() + 1).padStart(2, "0");
632
+ const d = String(date.getDate()).padStart(2, "0");
633
+ const h = String(date.getHours()).padStart(2, "0");
634
+ return `${y}-${m}-${d}T${h}`;
635
+ }
636
+ case "day": {
637
+ const y = date.getFullYear();
638
+ const m = String(date.getMonth() + 1).padStart(2, "0");
639
+ const d = String(date.getDate()).padStart(2, "0");
640
+ return `${y}-${m}-${d}`;
641
+ }
642
+ case "week":
643
+ return getISOWeekKey(date);
644
+ case "month": {
645
+ const y = date.getFullYear();
646
+ const m = String(date.getMonth() + 1).padStart(2, "0");
647
+ return `${y}-${m}`;
648
+ }
649
+ }
650
+ }
651
+ function getISOWeekKey(date) {
652
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
653
+ d.setDate(d.getDate() + 4 - (d.getDay() || 7));
654
+ const yearStart = new Date(d.getFullYear(), 0, 1);
655
+ const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
656
+ return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
657
+ }
658
+ function getPreviousPeriodKey(key, period) {
659
+ switch (period) {
660
+ case "hour": {
661
+ const d = /* @__PURE__ */ new Date(`${key.slice(0, 10)}T${key.slice(11)}:00:00`);
662
+ d.setHours(d.getHours() - 1);
663
+ return getPeriodKey(d, "hour");
664
+ }
665
+ case "day": {
666
+ const d = /* @__PURE__ */ new Date(`${key}T00:00:00`);
667
+ d.setDate(d.getDate() - 1);
668
+ return getPeriodKey(d, "day");
669
+ }
670
+ case "week": {
671
+ const d = isoWeekToDate(key);
672
+ d.setDate(d.getDate() - 7);
673
+ return getPeriodKey(d, "week");
674
+ }
675
+ case "month": {
676
+ const [y, m] = key.split("-").map(Number);
677
+ const d = new Date(y, m - 2, 1);
678
+ return getPeriodKey(d, "month");
679
+ }
680
+ }
681
+ }
682
+ function isoWeekToDate(weekKey) {
683
+ const [yearStr, weekStr] = weekKey.split("-W");
684
+ const year = parseInt(yearStr, 10);
685
+ const week = parseInt(weekStr, 10);
686
+ const jan4 = new Date(year, 0, 4);
687
+ const dayOfWeek = jan4.getDay() || 7;
688
+ const monday = new Date(jan4);
689
+ monday.setDate(jan4.getDate() - dayOfWeek + 1 + (week - 1) * 7);
690
+ return monday;
691
+ }
692
+ function localDateStr(d) {
693
+ const y = d.getFullYear();
694
+ const m = String(d.getMonth() + 1).padStart(2, "0");
695
+ const day = String(d.getDate()).padStart(2, "0");
696
+ return `${y}-${m}-${day}`;
697
+ }
698
+ function utcDateTimeStr(d) {
699
+ return d.toISOString().replace("T", " ").slice(0, 19);
700
+ }
701
+ function getTimeRange(periodKey, period) {
702
+ switch (period) {
703
+ case "hour": {
704
+ const d = /* @__PURE__ */ new Date(`${periodKey.slice(0, 10)}T${periodKey.slice(11)}:00:00`);
705
+ const dEnd = new Date(d.getTime() + 36e5);
706
+ return { start: utcDateTimeStr(d), end: utcDateTimeStr(dEnd) };
707
+ }
708
+ case "day":
709
+ return { start: `${periodKey} 00:00:00`, end: `${periodKey} 23:59:59` };
710
+ case "week": {
711
+ const monday = isoWeekToDate(periodKey);
712
+ const sunday = new Date(monday);
713
+ sunday.setDate(monday.getDate() + 6);
714
+ return {
715
+ start: `${localDateStr(monday)} 00:00:00`,
716
+ end: `${localDateStr(sunday)} 23:59:59`
717
+ };
718
+ }
719
+ case "month": {
720
+ const [y, m] = periodKey.split("-").map(Number);
721
+ const lastDay = new Date(y, m, 0).getDate();
722
+ return {
723
+ start: `${periodKey}-01 00:00:00`,
724
+ end: `${periodKey}-${String(lastDay).padStart(2, "0")} 23:59:59`
725
+ };
726
+ }
727
+ }
728
+ }
729
+ async function gatherTurnEvents(mind, session, doneId) {
730
+ const db = await getDb();
731
+ const conditions = [
732
+ eq(mindHistory.mind, mind),
733
+ eq(mindHistory.type, "done"),
734
+ lt(mindHistory.id, doneId)
735
+ ];
736
+ if (session) {
737
+ conditions.push(eq(mindHistory.session, session));
738
+ }
739
+ const prevDone = await db.select({ id: mindHistory.id }).from(mindHistory).where(and(...conditions)).orderBy(desc(mindHistory.id)).limit(1);
740
+ const prevDoneId = prevDone.length > 0 ? prevDone[0].id : 0;
741
+ const turnConditions = [
742
+ eq(mindHistory.mind, mind),
743
+ sql`${mindHistory.id} > ${prevDoneId}`,
744
+ sql`${mindHistory.id} <= ${doneId}`
745
+ ];
746
+ if (session) {
747
+ turnConditions.push(eq(mindHistory.session, session));
748
+ }
749
+ const events = await db.select({
750
+ id: mindHistory.id,
751
+ type: mindHistory.type,
752
+ channel: mindHistory.channel,
753
+ session: mindHistory.session,
754
+ content: mindHistory.content,
755
+ metadata: mindHistory.metadata,
756
+ created_at: mindHistory.created_at
757
+ }).from(mindHistory).where(and(...turnConditions)).orderBy(mindHistory.id);
758
+ return {
759
+ events,
760
+ fromId: events.length > 0 ? events[0].id : doneId,
761
+ toId: doneId
762
+ };
763
+ }
764
+ async function gatherTurnEventsByTurnId(turnId) {
765
+ const db = await getDb();
766
+ const events = await db.select({
767
+ id: mindHistory.id,
768
+ type: mindHistory.type,
769
+ channel: mindHistory.channel,
770
+ session: mindHistory.session,
771
+ content: mindHistory.content,
772
+ metadata: mindHistory.metadata,
773
+ created_at: mindHistory.created_at
774
+ }).from(mindHistory).where(eq(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
775
+ return {
776
+ events,
777
+ fromId: events.length > 0 ? events[0].id : 0,
778
+ toId: events.length > 0 ? events[events.length - 1].id : 0
779
+ };
780
+ }
781
+ function buildTurnDeterministicSummary(events) {
782
+ const channels = /* @__PURE__ */ new Set();
783
+ const tools = [];
784
+ let hasInbound = false;
785
+ let hasOutbound = false;
786
+ for (const ev of events) {
787
+ if (ev.type === "inbound") {
788
+ hasInbound = true;
789
+ if (ev.channel) channels.add(ev.channel);
790
+ }
791
+ if (ev.type === "outbound" || ev.type === "text") {
792
+ hasOutbound = true;
793
+ }
794
+ if (ev.type === "tool_use" && ev.metadata) {
795
+ try {
796
+ const meta = JSON.parse(ev.metadata);
797
+ if (meta.name) tools.push(meta.name);
798
+ } catch (err) {
799
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
800
+ }
801
+ }
802
+ }
803
+ const parts = [];
804
+ if (hasInbound) {
805
+ const channelList = [...channels];
806
+ parts.push(
807
+ channelList.length > 0 ? `Received message on ${channelList.join(", ")}` : "Received message"
808
+ );
809
+ }
810
+ if (tools.length > 0) {
811
+ const unique = [...new Set(tools)];
812
+ parts.push(`Used ${unique.join(", ")}`);
813
+ }
814
+ if (hasOutbound) {
815
+ parts.push("Sent response");
816
+ }
817
+ return parts.length > 0 ? `${parts.join(". ")}.` : "Turn completed.";
818
+ }
819
+ function buildTranscript(events) {
820
+ const lines = [];
821
+ for (const ev of events) {
822
+ switch (ev.type) {
823
+ case "inbound":
824
+ lines.push(`[inbound${ev.channel ? ` ${ev.channel}` : ""}] ${ev.content ?? ""}`);
825
+ break;
826
+ case "outbound":
827
+ case "text":
828
+ lines.push(`[response] ${(ev.content ?? "").slice(0, 500)}`);
829
+ break;
830
+ case "tool_use": {
831
+ let toolInfo = "tool";
832
+ if (ev.metadata) {
833
+ try {
834
+ const meta = JSON.parse(ev.metadata);
835
+ toolInfo = summarizeTool(meta.name ?? "tool", meta.input ?? {});
836
+ } catch (err) {
837
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
838
+ }
839
+ }
840
+ lines.push(toolInfo);
841
+ break;
842
+ }
843
+ case "tool_result": {
844
+ const content = ev.content ?? "";
845
+ let isError = false;
846
+ if (ev.metadata) {
847
+ try {
848
+ const meta = JSON.parse(ev.metadata);
849
+ isError = !!meta.is_error;
850
+ } catch (err) {
851
+ sLog.debug(
852
+ `failed to parse tool_result metadata for event ${ev.id}`,
853
+ logger_default.errorData(err)
854
+ );
855
+ }
856
+ }
857
+ lines.push(isError ? "[result error]" : `[result] ${content.slice(0, 200)}`);
858
+ break;
859
+ }
860
+ case "thinking":
861
+ lines.push(`[thinking] ${(ev.content ?? "").slice(0, 300)}`);
862
+ break;
863
+ }
864
+ }
865
+ return lines.join("\n");
866
+ }
867
+ async function summarizeTurn(mind, session, channel, doneId, turnId) {
868
+ const { events, fromId, toId } = turnId ? await gatherTurnEventsByTurnId(turnId) : await gatherTurnEvents(mind, session, doneId);
869
+ if (events.length === 0) return;
870
+ const substantiveTypes = /* @__PURE__ */ new Set(["text", "outbound", "tool_use", "tool_result", "thinking"]);
871
+ const hasSubstantiveOutput = events.some((ev) => substantiveTypes.has(ev.type));
872
+ if (!hasSubstantiveOutput) {
873
+ sLog.info(
874
+ `skipping summary for interrupted turn ${turnId ?? "(no turn)"} (no substantive output)`
875
+ );
876
+ if (turnId) {
877
+ try {
878
+ const db2 = await getDb();
879
+ await db2.update(mindHistory).set({ turn_id: null }).where(and(eq(mindHistory.turn_id, turnId), eq(mindHistory.type, "inbound")));
880
+ await db2.update(messages).set({ turn_id: null }).where(eq(messages.turn_id, turnId));
881
+ } catch (err) {
882
+ sLog.error(`failed to un-tag events for interrupted turn ${turnId}`, logger_default.errorData(err));
883
+ }
884
+ }
885
+ return;
886
+ }
887
+ const tools = [];
888
+ for (const ev of events) {
889
+ if (ev.type === "tool_use" && ev.metadata) {
890
+ try {
891
+ const meta = JSON.parse(ev.metadata);
892
+ if (meta.name) tools.push(meta.name);
893
+ } catch (err) {
894
+ sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
895
+ }
896
+ }
897
+ }
898
+ const fromTime = events[0].created_at;
899
+ const toTime = events[events.length - 1].created_at;
900
+ let summaryText;
901
+ let deterministic;
902
+ const transcript = buildTranscript(events);
903
+ if (transcript.trim()) {
904
+ const summaryPrompt = await getPrompt("turn_summary");
905
+ const aiResult = await aiCompleteUtility(summaryPrompt, transcript);
906
+ if (aiResult) {
907
+ summaryText = aiResult;
908
+ deterministic = false;
909
+ } else {
910
+ summaryText = buildTurnDeterministicSummary(events);
911
+ deterministic = true;
912
+ }
913
+ } else {
914
+ summaryText = buildTurnDeterministicSummary(events);
915
+ deterministic = true;
916
+ }
917
+ const metadata = {
918
+ deterministic,
919
+ tool_count: tools.length,
920
+ tools: [...new Set(tools)],
921
+ from_id: fromId,
922
+ to_id: toId,
923
+ from_time: fromTime,
924
+ to_time: toTime
925
+ };
926
+ const periodKey = turnId ?? `${mind}-${doneId}`;
927
+ const db = await getDb();
928
+ let summaryId;
929
+ try {
930
+ const result = await db.insert(summaries).values({
931
+ mind,
932
+ period: "turn",
933
+ period_key: periodKey,
934
+ content: summaryText,
935
+ metadata: JSON.stringify(metadata)
936
+ }).onConflictDoNothing().returning({ id: summaries.id });
937
+ summaryId = result[0]?.id;
938
+ if (summaryId == null) {
939
+ const existing = await db.select({ id: summaries.id }).from(summaries).where(
940
+ and(
941
+ eq(summaries.mind, mind),
942
+ eq(summaries.period, "turn"),
943
+ eq(summaries.period_key, periodKey)
944
+ )
945
+ ).get();
946
+ summaryId = existing?.id;
947
+ }
948
+ } catch (err) {
949
+ sLog.error(
950
+ `failed to persist turn summary for ${mind} (events ${fromId}-${toId})`,
951
+ logger_default.errorData(err)
952
+ );
953
+ return;
954
+ }
955
+ if (turnId && summaryId != null) {
956
+ setSummaryId(turnId, summaryId).catch((err) => {
957
+ sLog.error(`failed to link summary to turn ${turnId}`, logger_default.errorData(err));
958
+ });
959
+ }
960
+ publish2(mind, {
961
+ mind,
962
+ type: "summary",
963
+ session,
964
+ channel,
965
+ content: summaryText,
966
+ metadata,
967
+ turnId
968
+ });
969
+ }
970
+ async function setSummaryId(turnId, summaryId) {
971
+ const db = await getDb();
972
+ await db.update(turns).set({ summary_id: summaryId }).where(eq(turns.id, turnId));
973
+ }
974
+ function getChildPeriod(period) {
975
+ switch (period) {
976
+ case "hour":
977
+ return "turn";
978
+ case "day":
979
+ return "hour";
980
+ case "week":
981
+ case "month":
982
+ return "day";
983
+ }
984
+ }
985
+ function getScopeInstruction(mind) {
986
+ if (mind === SYSTEM_MIND) {
987
+ return 'Write in third person, describing what the minds in the system did (e.g. "Alice explored...", "The system saw activity in..."). Reference minds by name.';
988
+ }
989
+ return 'Write in first person as the mind who performed the actions (e.g. "I explored...", "I worked on...").';
990
+ }
991
+ function buildPeriodicDeterministicSummary(sources, period, periodKey) {
992
+ if (sources.length === 0) return "";
993
+ switch (period) {
994
+ case "hour":
995
+ return `Activity during ${periodKey.slice(11)}:00: ${sources.join(" ")}`;
996
+ case "day":
997
+ return `Activity on ${periodKey}:
998
+
999
+ ${sources.join("\n\n")}`;
1000
+ case "week":
1001
+ return `Week ${periodKey} summary:
1002
+
1003
+ ${sources.join("\n\n")}`;
1004
+ case "month":
1005
+ return `${periodKey} summary:
1006
+
1007
+ ${sources.join("\n\n")}`;
1008
+ }
1009
+ }
1010
+ async function gatherChildSummaries(mind, period, periodKey) {
1011
+ const db = await getDb();
1012
+ const childPeriod = getChildPeriod(period);
1013
+ if (period === "hour") {
1014
+ const { start: start2, end: end2 } = getTimeRange(periodKey, "hour");
1015
+ const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
1016
+ and(
1017
+ eq(summaries.mind, mind),
1018
+ eq(summaries.period, childPeriod),
1019
+ gte(summaries.created_at, start2),
1020
+ lt(summaries.created_at, end2)
1021
+ )
1022
+ ).orderBy(summaries.created_at);
1023
+ return {
1024
+ texts: rows2.map((r) => r.content),
1025
+ sourceIds: rows2.map((r) => r.id)
1026
+ };
1027
+ }
1028
+ if (period === "day") {
1029
+ const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
1030
+ and(
1031
+ eq(summaries.mind, mind),
1032
+ eq(summaries.period, childPeriod),
1033
+ like(summaries.period_key, `${periodKey}%`)
1034
+ )
1035
+ ).orderBy(summaries.period_key);
1036
+ return {
1037
+ texts: rows2.map((r) => r.content),
1038
+ sourceIds: rows2.map((r) => r.id)
1039
+ };
1040
+ }
1041
+ const { start, end } = getTimeRange(periodKey, period);
1042
+ const startKey = start.slice(0, 10);
1043
+ const endKey = end.slice(0, 10);
1044
+ const rows = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
1045
+ and(
1046
+ eq(summaries.mind, mind),
1047
+ eq(summaries.period, childPeriod),
1048
+ gte(summaries.period_key, startKey),
1049
+ sql`${summaries.period_key} <= ${endKey}`
1050
+ )
1051
+ ).orderBy(summaries.period_key);
1052
+ return {
1053
+ texts: rows.map((r) => r.content),
1054
+ sourceIds: rows.map((r) => r.id)
1055
+ };
1056
+ }
1057
+ async function summarizePeriod(mind, period, periodKey) {
1058
+ if (await summaryExists(mind, period, periodKey)) return false;
1059
+ const db = await getDb();
1060
+ const sources = await gatherChildSummaries(mind, period, periodKey);
1061
+ if (sources.texts.length === 0) return false;
1062
+ if (sources.texts.length === 1) {
1063
+ try {
1064
+ await db.insert(summaries).values({
1065
+ mind,
1066
+ period,
1067
+ period_key: periodKey,
1068
+ content: sources.texts[0],
1069
+ metadata: JSON.stringify({
1070
+ deterministic: false,
1071
+ promoted: true,
1072
+ source_count: 1,
1073
+ source_ids: sources.sourceIds
1074
+ })
1075
+ }).onConflictDoNothing();
1076
+ } catch (err) {
1077
+ sLog.error(
1078
+ `failed to persist promoted ${period} summary for ${mind} (${periodKey})`,
1079
+ logger_default.errorData(err)
474
1080
  );
475
- setTimeout(() => {
476
- if (this.shuttingDown || this.stopping.has(platform)) return;
477
- this.startBridge(platform, daemonPort).catch((err) => {
478
- blog.error(`failed to restart bridge ${platform}`, logger_default.errorData(err));
479
- });
480
- }, delay);
481
- });
482
- blog.info(`started bridge ${platform}`);
1081
+ return false;
1082
+ }
1083
+ sLog.info(`promoted single-child ${period} summary for ${mind} (${periodKey})`);
1084
+ return true;
483
1085
  }
484
- async stopBridge(platform) {
485
- const tracked = this.bridges.get(platform);
486
- if (!tracked) return;
487
- this.stopping.add(platform);
488
- this.bridges.delete(platform);
489
- await new Promise((resolve20) => {
490
- tracked.child.on("exit", () => resolve20());
491
- try {
492
- if (tracked.child.pid) {
493
- process.kill(-tracked.child.pid, "SIGTERM");
494
- } else {
495
- tracked.child.kill("SIGTERM");
496
- }
497
- } catch (err) {
498
- if (err instanceof Error && err.code !== "ESRCH") {
499
- blog.warn(`failed to stop bridge ${platform}`, logger_default.errorData(err));
500
- }
501
- resolve20();
502
- }
503
- setTimeout(() => {
504
- try {
505
- if (tracked.child.pid) {
506
- process.kill(-tracked.child.pid, "SIGKILL");
507
- } else {
508
- tracked.child.kill("SIGKILL");
509
- }
510
- } catch {
511
- }
512
- resolve20();
513
- }, 5e3);
514
- });
515
- this.stopping.delete(platform);
516
- this.restartTracker.reset(platform);
1086
+ const promptKey = `meta_summary_${period}`;
1087
+ const scopeInstruction = getScopeInstruction(mind);
1088
+ const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
1089
+ const userMessage = sources.texts.join("\n\n---\n\n");
1090
+ let content;
1091
+ let deterministic;
1092
+ const aiResult = await aiCompleteUtility(systemPrompt, userMessage);
1093
+ if (aiResult) {
1094
+ content = aiResult;
1095
+ deterministic = false;
1096
+ } else {
1097
+ content = buildPeriodicDeterministicSummary(sources.texts, period, periodKey);
1098
+ deterministic = true;
1099
+ }
1100
+ const metadata = {
1101
+ deterministic,
1102
+ source_count: sources.texts.length,
1103
+ source_ids: sources.sourceIds
1104
+ };
1105
+ try {
1106
+ await db.insert(summaries).values({
1107
+ mind,
1108
+ period,
1109
+ period_key: periodKey,
1110
+ content,
1111
+ metadata: JSON.stringify(metadata)
1112
+ }).onConflictDoNothing();
1113
+ } catch (err) {
1114
+ sLog.error(
1115
+ `failed to persist ${period} summary for ${mind} (${periodKey})`,
1116
+ logger_default.errorData(err)
1117
+ );
1118
+ return false;
1119
+ }
1120
+ sLog.info(
1121
+ `generated ${period} summary for ${mind} (${periodKey})${deterministic ? " [deterministic]" : ""}`
1122
+ );
1123
+ return true;
1124
+ }
1125
+ async function summarizeSystem(period, periodKey) {
1126
+ if (await summaryExists(SYSTEM_MIND, period, periodKey)) return;
1127
+ const db = await getDb();
1128
+ const rows = await db.select({ mind: summaries.mind, content: summaries.content }).from(summaries).where(
1129
+ and(
1130
+ eq(summaries.period, period),
1131
+ eq(summaries.period_key, periodKey),
1132
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
1133
+ )
1134
+ ).orderBy(summaries.mind);
1135
+ if (rows.length === 0) return;
1136
+ const minds = [...new Set(rows.map((r) => r.mind))];
1137
+ const texts = rows.map((r) => `[${r.mind}] ${r.content}`);
1138
+ const promptKey = `meta_summary_${period}`;
1139
+ const scopeInstruction = getScopeInstruction(SYSTEM_MIND);
1140
+ const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
1141
+ const userMessage = texts.join("\n\n---\n\n");
1142
+ let content;
1143
+ let deterministic;
1144
+ const aiResult = await aiCompleteUtility(systemPrompt, userMessage);
1145
+ if (aiResult) {
1146
+ content = aiResult;
1147
+ deterministic = false;
1148
+ } else {
1149
+ content = buildPeriodicDeterministicSummary(texts, period, periodKey);
1150
+ deterministic = true;
1151
+ }
1152
+ try {
1153
+ await db.insert(summaries).values({
1154
+ mind: SYSTEM_MIND,
1155
+ period,
1156
+ period_key: periodKey,
1157
+ content,
1158
+ metadata: JSON.stringify({ deterministic, minds, source_count: rows.length })
1159
+ }).onConflictDoNothing();
1160
+ } catch (err) {
1161
+ sLog.error(`failed to persist system ${period} summary (${periodKey})`, logger_default.errorData(err));
1162
+ }
1163
+ }
1164
+ async function mindsWithTurnSummaries(start, end) {
1165
+ const db = await getDb();
1166
+ const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
1167
+ and(
1168
+ eq(summaries.period, "turn"),
1169
+ gte(summaries.created_at, start),
1170
+ lt(summaries.created_at, end),
1171
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
1172
+ )
1173
+ ).groupBy(summaries.mind);
1174
+ return rows.map((r) => r.mind);
1175
+ }
1176
+ async function mindsWithSummaries(period, keyPattern) {
1177
+ const db = await getDb();
1178
+ const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
1179
+ and(
1180
+ eq(summaries.period, period),
1181
+ like(summaries.period_key, keyPattern),
1182
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
1183
+ )
1184
+ ).groupBy(summaries.mind);
1185
+ return rows.map((r) => r.mind);
1186
+ }
1187
+ async function mindsWithDailySummariesInRange(startKey, endKey) {
1188
+ const db = await getDb();
1189
+ const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
1190
+ and(
1191
+ eq(summaries.period, "day"),
1192
+ gte(summaries.period_key, startKey),
1193
+ sql`${summaries.period_key} <= ${endKey}`,
1194
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
1195
+ )
1196
+ ).groupBy(summaries.mind);
1197
+ return rows.map((r) => r.mind);
1198
+ }
1199
+ async function processHour(periodKey) {
1200
+ const { start, end } = getTimeRange(periodKey, "hour");
1201
+ const minds = await mindsWithTurnSummaries(start, end);
1202
+ for (const mind of minds) {
517
1203
  try {
518
- this.removeBridgePid(platform);
1204
+ await summarizePeriod(mind, "hour", periodKey);
519
1205
  } catch (err) {
520
- blog.warn(`failed to remove PID file for bridge ${platform}`, logger_default.errorData(err));
1206
+ sLog.error(`failed to summarize hour for ${mind} (${periodKey})`, logger_default.errorData(err));
521
1207
  }
522
- blog.info(`stopped bridge ${platform}`);
523
1208
  }
524
- async stopAll() {
525
- this.shuttingDown = true;
526
- const platforms = [...this.bridges.keys()];
527
- await Promise.all(platforms.map((p) => this.stopBridge(p)));
1209
+ if (minds.length > 0) {
1210
+ await summarizeSystem("hour", periodKey);
528
1211
  }
529
- getBridgeStatus() {
530
- return [...this.bridges.entries()].map(([platform, tracked]) => ({
531
- platform,
532
- running: !tracked.child.killed
533
- }));
1212
+ }
1213
+ async function processDay(periodKey) {
1214
+ const minds = await mindsWithSummaries("hour", `${periodKey}%`);
1215
+ for (const mind of minds) {
1216
+ try {
1217
+ await summarizePeriod(mind, "day", periodKey);
1218
+ } catch (err) {
1219
+ sLog.error(`failed to summarize day for ${mind} (${periodKey})`, logger_default.errorData(err));
1220
+ }
534
1221
  }
535
- isRunning(platform) {
536
- const tracked = this.bridges.get(platform);
537
- return tracked != null && !tracked.child.killed;
1222
+ if (minds.length > 0) {
1223
+ await summarizeSystem("day", periodKey);
538
1224
  }
539
- bridgePidPath(platform) {
540
- return resolve2(voluteSystemDir(), "bridges", `${platform}.pid`);
1225
+ }
1226
+ async function processWeek(periodKey) {
1227
+ const { start, end } = getTimeRange(periodKey, "week");
1228
+ const startKey = start.slice(0, 10);
1229
+ const endKey = end.slice(0, 10);
1230
+ const minds = await mindsWithDailySummariesInRange(startKey, endKey);
1231
+ for (const mind of minds) {
1232
+ try {
1233
+ await summarizePeriod(mind, "week", periodKey);
1234
+ } catch (err) {
1235
+ sLog.error(`failed to summarize week for ${mind} (${periodKey})`, logger_default.errorData(err));
1236
+ }
541
1237
  }
542
- saveBridgePid(platform, pid) {
543
- const pidPath = this.bridgePidPath(platform);
544
- mkdirSync(dirname(pidPath), { recursive: true });
545
- writeFileSync(pidPath, String(pid));
1238
+ if (minds.length > 0) {
1239
+ await summarizeSystem("week", periodKey);
546
1240
  }
547
- removeBridgePid(platform) {
1241
+ }
1242
+ async function processMonth(periodKey) {
1243
+ const minds = await mindsWithSummaries("day", `${periodKey}%`);
1244
+ for (const mind of minds) {
548
1245
  try {
549
- unlinkSync(this.bridgePidPath(platform));
1246
+ await summarizePeriod(mind, "month", periodKey);
550
1247
  } catch (err) {
551
- if (err instanceof Error && err.code !== "ENOENT") {
552
- blog.warn(`failed to remove PID file for bridge ${platform}`, logger_default.errorData(err));
553
- }
1248
+ sLog.error(`failed to summarize month for ${mind} (${periodKey})`, logger_default.errorData(err));
554
1249
  }
555
1250
  }
556
- killOrphanBridge(platform) {
557
- const pidPath = this.bridgePidPath(platform);
558
- if (!existsSync2(pidPath)) return;
1251
+ if (minds.length > 0) {
1252
+ await summarizeSystem("month", periodKey);
1253
+ }
1254
+ }
1255
+ async function summaryExists(mind, period, periodKey) {
1256
+ const db = await getDb();
1257
+ const row = await db.select({ id: summaries.id }).from(summaries).where(
1258
+ and(
1259
+ eq(summaries.mind, mind),
1260
+ eq(summaries.period, period),
1261
+ eq(summaries.period_key, periodKey)
1262
+ )
1263
+ ).get();
1264
+ return !!row;
1265
+ }
1266
+ async function backfill() {
1267
+ const now = /* @__PURE__ */ new Date();
1268
+ for (let i = 1; i <= 48; i++) {
559
1269
  try {
560
- const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
561
- if (pid > 0) {
562
- try {
563
- process.kill(-pid, "SIGTERM");
564
- } catch {
565
- try {
566
- process.kill(pid, "SIGTERM");
567
- } catch {
568
- }
569
- }
570
- blog.warn(`killed orphan bridge ${platform} (pid ${pid})`);
1270
+ const d = new Date(now);
1271
+ d.setHours(d.getHours() - i);
1272
+ const key = getPeriodKey(d, "hour");
1273
+ if (!await summaryExists(SYSTEM_MIND, "hour", key)) {
1274
+ await processHour(key);
571
1275
  }
572
1276
  } catch (err) {
573
- if (err instanceof Error && err.code !== "ESRCH") {
574
- blog.debug(`orphan bridge ${platform} cleanup: ${err}`);
1277
+ sLog.error(`backfill failed for hour -${i}`, logger_default.errorData(err));
1278
+ }
1279
+ }
1280
+ for (let i = 1; i <= 7; i++) {
1281
+ try {
1282
+ const d = new Date(now);
1283
+ d.setDate(d.getDate() - i);
1284
+ const key = getPeriodKey(d, "day");
1285
+ if (!await summaryExists(SYSTEM_MIND, "day", key)) {
1286
+ await processDay(key);
575
1287
  }
1288
+ } catch (err) {
1289
+ sLog.error(`backfill failed for day -${i}`, logger_default.errorData(err));
576
1290
  }
1291
+ }
1292
+ for (let i = 1; i <= 4; i++) {
577
1293
  try {
578
- unlinkSync(pidPath);
1294
+ const d = new Date(now);
1295
+ d.setDate(d.getDate() - i * 7);
1296
+ const key = getPeriodKey(d, "week");
1297
+ if (!await summaryExists(SYSTEM_MIND, "week", key)) {
1298
+ await processWeek(key);
1299
+ }
579
1300
  } catch (err) {
580
- if (err instanceof Error && err.code !== "ENOENT") {
581
- blog.warn(`failed to clean up PID file for orphan bridge ${platform}`, logger_default.errorData(err));
1301
+ sLog.error(`backfill failed for week -${i}`, logger_default.errorData(err));
1302
+ }
1303
+ }
1304
+ for (let i = 1; i <= 3; i++) {
1305
+ try {
1306
+ const d = new Date(now);
1307
+ d.setMonth(d.getMonth() - i);
1308
+ const key = getPeriodKey(d, "month");
1309
+ if (!await summaryExists(SYSTEM_MIND, "month", key)) {
1310
+ await processMonth(key);
582
1311
  }
1312
+ } catch (err) {
1313
+ sLog.error(`backfill failed for month -${i}`, logger_default.errorData(err));
583
1314
  }
584
1315
  }
585
- resolveBuiltinBridge(platform) {
586
- return searchUpwards("connectors", `${platform}-bridge.js`);
1316
+ }
1317
+ var Summarizer = class {
1318
+ interval = null;
1319
+ lastHourKey = null;
1320
+ hasBackfilled = false;
1321
+ start() {
1322
+ this.interval = setInterval(() => this.tick(), 5 * 6e4);
1323
+ this.tick();
1324
+ }
1325
+ stop() {
1326
+ if (this.interval) {
1327
+ clearInterval(this.interval);
1328
+ this.interval = null;
1329
+ }
1330
+ }
1331
+ async tick() {
1332
+ try {
1333
+ if (!this.hasBackfilled) {
1334
+ await backfill();
1335
+ this.hasBackfilled = true;
1336
+ }
1337
+ const now = /* @__PURE__ */ new Date();
1338
+ const currentHourKey = getPeriodKey(now, "hour");
1339
+ if (this.lastHourKey && this.lastHourKey !== currentHourKey) {
1340
+ await processHour(this.lastHourKey);
1341
+ }
1342
+ this.lastHourKey = currentHourKey;
1343
+ const yesterday = new Date(now);
1344
+ yesterday.setDate(yesterday.getDate() - 1);
1345
+ const yesterdayKey = getPeriodKey(yesterday, "day");
1346
+ if (!await summaryExists(SYSTEM_MIND, "day", yesterdayKey)) {
1347
+ await processDay(yesterdayKey);
1348
+ }
1349
+ const currentWeekKey = getPeriodKey(now, "week");
1350
+ const prevWeekKey = getPreviousPeriodKey(currentWeekKey, "week");
1351
+ if (!await summaryExists(SYSTEM_MIND, "week", prevWeekKey)) {
1352
+ await processWeek(prevWeekKey);
1353
+ }
1354
+ const currentMonthKey = getPeriodKey(now, "month");
1355
+ const prevMonthKey = getPreviousPeriodKey(currentMonthKey, "month");
1356
+ if (!await summaryExists(SYSTEM_MIND, "month", prevMonthKey)) {
1357
+ await processMonth(prevMonthKey);
1358
+ }
1359
+ } catch (err) {
1360
+ sLog.error("tick failed", logger_default.errorData(err));
1361
+ }
587
1362
  }
588
1363
  };
589
- var instance = null;
590
- function initBridgeManager() {
591
- if (instance) throw new Error("BridgeManager already initialized");
592
- instance = new BridgeManager();
593
- return instance;
594
- }
595
- function getBridgeManager() {
596
- if (!instance) throw new Error("BridgeManager not initialized \u2014 call initBridgeManager() first");
597
- return instance;
1364
+ var instance2 = null;
1365
+ function initSummarizer() {
1366
+ if (instance2) throw new Error("Summarizer already initialized");
1367
+ instance2 = new Summarizer();
1368
+ return instance2;
598
1369
  }
599
1370
 
600
1371
  // src/lib/history-cleanup.ts
601
- import { and, eq, lt } from "drizzle-orm";
1372
+ import { and as and2, eq as eq2, lt as lt2 } from "drizzle-orm";
602
1373
  var LOG_RETENTION_MS = 24 * 60 * 60 * 1e3;
603
1374
  async function cleanExpiredLogs() {
604
1375
  const db = await getDb();
605
1376
  const cutoff = new Date(Date.now() - LOG_RETENTION_MS).toISOString().replace("T", " ").slice(0, 19);
606
- await db.delete(mindHistory).where(and(eq(mindHistory.type, "log"), lt(mindHistory.created_at, cutoff)));
1377
+ await db.delete(mindHistory).where(and2(eq2(mindHistory.type, "log"), lt2(mindHistory.created_at, cutoff)));
607
1378
  }
608
1379
 
609
1380
  // src/lib/shared.ts
@@ -977,7 +1748,7 @@ async function generateImage(model, prompt) {
977
1748
 
978
1749
  // src/web/middleware/auth.ts
979
1750
  import { timingSafeEqual } from "crypto";
980
- import { eq as eq2, lt as lt2 } from "drizzle-orm";
1751
+ import { eq as eq3, lt as lt3 } from "drizzle-orm";
981
1752
  import { getCookie } from "hono/cookie";
982
1753
  import { createMiddleware } from "hono/factory";
983
1754
  function isValidDaemonToken(token) {
@@ -1000,14 +1771,14 @@ async function createSession(userId) {
1000
1771
  async function deleteSession(sessionId) {
1001
1772
  sessionCache.delete(sessionId);
1002
1773
  const db = await getDb();
1003
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
1774
+ await db.delete(sessions).where(eq3(sessions.id, sessionId));
1004
1775
  }
1005
1776
  async function getSessionUserId(sessionId) {
1006
1777
  const db = await getDb();
1007
- const row = await db.select().from(sessions).where(eq2(sessions.id, sessionId)).get();
1778
+ const row = await db.select().from(sessions).where(eq3(sessions.id, sessionId)).get();
1008
1779
  if (!row) return void 0;
1009
1780
  if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
1010
- await db.delete(sessions).where(eq2(sessions.id, sessionId));
1781
+ await db.delete(sessions).where(eq3(sessions.id, sessionId));
1011
1782
  return void 0;
1012
1783
  }
1013
1784
  return row.userId;
@@ -1015,7 +1786,7 @@ async function getSessionUserId(sessionId) {
1015
1786
  async function cleanExpiredSessions() {
1016
1787
  const db = await getDb();
1017
1788
  const cutoff = Date.now() - SESSION_MAX_AGE;
1018
- await db.delete(sessions).where(lt2(sessions.createdAt, cutoff));
1789
+ await db.delete(sessions).where(lt3(sessions.createdAt, cutoff));
1019
1790
  }
1020
1791
  var requireAdmin = createMiddleware(async (c, next) => {
1021
1792
  const user = c.get("user");
@@ -1136,7 +1907,14 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1136
1907
  });
1137
1908
  }).get("/info", (c) => {
1138
1909
  const config2 = readSystemsConfig();
1139
- return c.json({ system: config2?.system ?? null });
1910
+ const globalConfig = readGlobalConfig();
1911
+ return c.json({ system: config2?.system ?? null, name: globalConfig.name ?? null });
1912
+ }).put("/info", requireAdmin, zValidator("json", z.object({ name: z.string() })), (c) => {
1913
+ const { name } = c.req.valid("json");
1914
+ const config2 = readGlobalConfig();
1915
+ config2.name = name.trim() || void 0;
1916
+ writeGlobalConfig(config2);
1917
+ return c.json({ name: config2.name ?? null });
1140
1918
  }).post(
1141
1919
  "/register",
1142
1920
  requireAdmin,
@@ -1382,15 +2160,17 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1382
2160
  zValidator(
1383
2161
  "json",
1384
2162
  z.object({
1385
- spiritModel: z.string().nullable(),
2163
+ spiritModel: z.string().nullable().optional(),
1386
2164
  utilityModel: z.string().nullable()
1387
2165
  })
1388
2166
  ),
1389
2167
  (c) => {
1390
2168
  const { spiritModel, utilityModel } = c.req.valid("json");
1391
2169
  const config2 = readGlobalConfig();
1392
- config2.spiritModel = spiritModel ?? void 0;
1393
- writeGlobalConfig(config2);
2170
+ if (spiritModel !== void 0) {
2171
+ config2.spiritModel = spiritModel ?? void 0;
2172
+ writeGlobalConfig(config2);
2173
+ }
1394
2174
  setUtilityModel(utilityModel ?? void 0);
1395
2175
  return c.json({ ok: true });
1396
2176
  }
@@ -1505,7 +2285,54 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1505
2285
  setTimeout(() => oauthFlows.delete(flowId), 3e4);
1506
2286
  }
1507
2287
  return c.json(result);
1508
- });
2288
+ }).get("/mind-defaults", requireAdmin, (c) => {
2289
+ const config2 = readGlobalConfig();
2290
+ return c.json(config2.mindDefaults ?? {});
2291
+ }).put(
2292
+ "/mind-defaults",
2293
+ requireAdmin,
2294
+ zValidator(
2295
+ "json",
2296
+ z.object({
2297
+ cognition: z.object({
2298
+ model: z.string().optional(),
2299
+ thinkingLevel: z.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
2300
+ maxThinkingTokens: z.number().nonnegative().optional(),
2301
+ tokenBudget: z.number().nonnegative().optional(),
2302
+ tokenBudgetPeriodMinutes: z.number().positive().optional(),
2303
+ compaction: z.object({ maxContextTokens: z.number().positive().optional() }).optional()
2304
+ }).optional(),
2305
+ sleep: z.object({
2306
+ enabled: z.boolean().optional(),
2307
+ schedule: z.object({ sleep: z.string(), wake: z.string() }).optional(),
2308
+ wakeTriggers: z.object({
2309
+ mentions: z.boolean().optional(),
2310
+ dms: z.boolean().optional(),
2311
+ channels: z.array(z.string()).optional(),
2312
+ senders: z.array(z.string()).optional()
2313
+ }).optional()
2314
+ }).optional(),
2315
+ schedules: z.array(
2316
+ z.object({
2317
+ id: z.string().min(1),
2318
+ cron: z.string().optional(),
2319
+ message: z.string().optional(),
2320
+ script: z.string().optional(),
2321
+ session: z.string().optional(),
2322
+ enabled: z.boolean(),
2323
+ whileSleeping: z.enum(["skip", "queue", "trigger-wake"]).optional()
2324
+ })
2325
+ ).optional()
2326
+ })
2327
+ ),
2328
+ (c) => {
2329
+ const mindDefaults = c.req.valid("json");
2330
+ const config2 = readGlobalConfig();
2331
+ config2.mindDefaults = mindDefaults;
2332
+ writeGlobalConfig(config2);
2333
+ return c.json({ ok: true });
2334
+ }
2335
+ );
1509
2336
  var oauthFlows = /* @__PURE__ */ new Map();
1510
2337
  var OAUTH_FLOW_TTL_MS = 10 * 60 * 1e3;
1511
2338
  function cleanupOAuthFlows() {
@@ -1563,7 +2390,7 @@ import { csrf } from "hono/csrf";
1563
2390
  import { HTTPException } from "hono/http-exception";
1564
2391
 
1565
2392
  // src/web/api/activity.ts
1566
- import { desc } from "drizzle-orm";
2393
+ import { desc as desc2 } from "drizzle-orm";
1567
2394
  import { Hono as Hono2 } from "hono";
1568
2395
  import { streamSSE as streamSSE2 } from "hono/streaming";
1569
2396
  var app2 = new Hono2().get("/events", async (c) => {
@@ -1574,7 +2401,7 @@ var app2 = new Hono2().get("/events", async (c) => {
1574
2401
  let recentActivity = [];
1575
2402
  try {
1576
2403
  const db = await getDb();
1577
- recentActivity = await db.select().from(activity).orderBy(desc(activity.created_at)).limit(50);
2404
+ recentActivity = await db.select().from(activity).orderBy(desc2(activity.created_at)).limit(50);
1578
2405
  recentActivity = recentActivity.map((row) => ({
1579
2406
  ...row,
1580
2407
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -1900,7 +2727,7 @@ import { Hono as Hono4 } from "hono";
1900
2727
  import { z as z3 } from "zod";
1901
2728
 
1902
2729
  // src/lib/puppets.ts
1903
- import { and as and2, eq as eq3 } from "drizzle-orm";
2730
+ import { and as and3, eq as eq4 } from "drizzle-orm";
1904
2731
  async function findOrCreatePuppet(platform, platformId, displayName) {
1905
2732
  const username = `${platform}:${platformId}`;
1906
2733
  const db = await getDb();
@@ -1909,10 +2736,10 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
1909
2736
  username: users.username,
1910
2737
  display_name: users.display_name,
1911
2738
  avatar: users.avatar
1912
- }).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
2739
+ }).from(users).where(and3(eq4(users.username, username), eq4(users.user_type, "puppet"))).get();
1913
2740
  if (existing) {
1914
2741
  if (existing.display_name !== displayName) {
1915
- await db.update(users).set({ display_name: displayName }).where(eq3(users.id, existing.id));
2742
+ await db.update(users).set({ display_name: displayName }).where(eq4(users.id, existing.id));
1916
2743
  existing.display_name = displayName;
1917
2744
  }
1918
2745
  return existing;
@@ -1938,7 +2765,7 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
1938
2765
  username: users.username,
1939
2766
  display_name: users.display_name,
1940
2767
  avatar: users.avatar
1941
- }).from(users).where(and2(eq3(users.username, username), eq3(users.user_type, "puppet"))).get();
2768
+ }).from(users).where(and3(eq4(users.username, username), eq4(users.user_type, "puppet"))).get();
1942
2769
  if (retried) return retried;
1943
2770
  }
1944
2771
  throw err;
@@ -2020,7 +2847,7 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
2020
2847
  }
2021
2848
  const participants = await getParticipants(channel.id);
2022
2849
  if (!participants.some((p) => p.userId === puppet.id)) {
2023
- const { addParticipant } = await import("./conversations-AWI5SZW2.js");
2850
+ const { addParticipant } = await import("./conversations-HL2JP5GI.js");
2024
2851
  await addParticipant(channel.id, puppet.id);
2025
2852
  }
2026
2853
  const contentBlocks = body.content;
@@ -2113,8 +2940,8 @@ async function fanOutToBridgedMinds(opts) {
2113
2940
  const participants = await getParticipants(opts.conversationId);
2114
2941
  const mindParticipants = participants.filter((p) => p.userType === "mind");
2115
2942
  const participantNames = participants.map((p) => p.username);
2116
- const { getMindManager: getMindManager2 } = await import("./mind-manager-NBJF5D26.js");
2117
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
2943
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-MWW3BTS4.js");
2944
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
2118
2945
  const manager = getMindManager2();
2119
2946
  const sm = getSleepManagerIfReady2();
2120
2947
  const targetMinds = mindParticipants.filter((ap) => {
@@ -2916,6 +3743,44 @@ var env_default = app6;
2916
3743
  import { Hono as Hono8 } from "hono";
2917
3744
  var app7 = new Hono8().get("/", (c) => {
2918
3745
  return c.json(getLoadedExtensions());
3746
+ }).get("/all", (c) => {
3747
+ return c.json(getAllDiscoveredExtensions());
3748
+ }).put("/:id/enabled", async (c) => {
3749
+ const { id } = c.req.param();
3750
+ const body = await c.req.json().catch(() => null);
3751
+ if (!body || typeof body.enabled !== "boolean") {
3752
+ return c.json({ error: "enabled must be a boolean" }, 400);
3753
+ }
3754
+ try {
3755
+ setExtensionEnabled(id, body.enabled);
3756
+ } catch (err) {
3757
+ return c.json({ error: err.message }, 404);
3758
+ }
3759
+ return c.json({ ok: true, requiresRestart: true });
3760
+ }).post("/install", async (c) => {
3761
+ const body = await c.req.json();
3762
+ const pkg = body.package?.trim();
3763
+ if (!pkg) {
3764
+ return c.json({ error: "package is required" }, 400);
3765
+ }
3766
+ try {
3767
+ await installNpmExtension(pkg);
3768
+ return c.json({ ok: true, requiresRestart: true });
3769
+ } catch (err) {
3770
+ const message = err.message;
3771
+ const isValidation = message.includes("already installed") || message.includes("Invalid package");
3772
+ return c.json({ error: message }, isValidation ? 400 : 500);
3773
+ }
3774
+ }).delete("/uninstall/:package", async (c) => {
3775
+ const pkg = c.req.param("package");
3776
+ try {
3777
+ await uninstallNpmExtension(pkg);
3778
+ return c.json({ ok: true, requiresRestart: true });
3779
+ } catch (err) {
3780
+ const message = err.message;
3781
+ const isValidation = message.includes("not installed");
3782
+ return c.json({ error: message }, isValidation ? 400 : 500);
3783
+ }
2919
3784
  });
2920
3785
  var extensions_default = app7;
2921
3786
 
@@ -2927,7 +3792,7 @@ async function notifyMind(mindName, message) {
2927
3792
  const entry = await findMind(mindName);
2928
3793
  if (!entry) return;
2929
3794
  try {
2930
- const { sendSystemMessage } = await import("./system-chat-JAPOJ3KE.js");
3795
+ const { sendSystemMessage } = await import("./system-chat-TYLOL7SX.js");
2931
3796
  await sendSystemMessage(mindName, message);
2932
3797
  } catch (err) {
2933
3798
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -3050,6 +3915,7 @@ var app8 = new Hono9().post("/:name/files/send", requireSelf(), async (c) => {
3050
3915
  var file_sharing_default = app8;
3051
3916
 
3052
3917
  // src/web/api/files.ts
3918
+ import { mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3053
3919
  import { readFile, realpath, stat } from "fs/promises";
3054
3920
  import { extname as extname2, resolve as resolve7 } from "path";
3055
3921
  import { Hono as Hono10 } from "hono";
@@ -3061,11 +3927,46 @@ var AVATAR_MIME2 = {
3061
3927
  ".webp": "image/webp"
3062
3928
  };
3063
3929
  var MAX_AVATAR_SIZE2 = 2 * 1024 * 1024;
3064
- var app9 = new Hono10().get("/:name/avatar", async (c) => {
3930
+ var app9 = new Hono10().post("/:name/avatar", requireSelf(), async (c) => {
3065
3931
  const name = c.req.param("name");
3066
3932
  const entry = await findMind(name);
3067
3933
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3068
- const dir = mindDir(name);
3934
+ const body = await c.req.parseBody();
3935
+ const file = body.file;
3936
+ if (!(file instanceof File)) {
3937
+ return c.json({ error: "No file uploaded" }, 400);
3938
+ }
3939
+ if (file.size > MAX_AVATAR_SIZE2) {
3940
+ return c.json({ error: "File too large (max 2MB)" }, 400);
3941
+ }
3942
+ const ext = extname2(file.name).toLowerCase();
3943
+ if (!AVATAR_MIME2[ext]) {
3944
+ return c.json({ error: "Invalid file type (png, jpg, gif, webp only)" }, 400);
3945
+ }
3946
+ const dir = entry.dir ?? mindDir(name);
3947
+ const homeDir = resolve7(dir, "home");
3948
+ const filename = `avatar${ext}`;
3949
+ const avatarPath = resolve7(homeDir, filename);
3950
+ const config2 = readVoluteConfig(dir) ?? {};
3951
+ const oldAvatar = config2.profile?.avatar;
3952
+ if (oldAvatar && oldAvatar !== filename) {
3953
+ rmSync3(resolve7(homeDir, oldAvatar), { force: true });
3954
+ }
3955
+ const buffer2 = Buffer.from(await file.arrayBuffer());
3956
+ mkdirSync4(homeDir, { recursive: true });
3957
+ writeFileSync4(avatarPath, buffer2);
3958
+ const profile = config2.profile ?? {};
3959
+ profile.avatar = filename;
3960
+ config2.profile = profile;
3961
+ writeVoluteConfig(dir, config2);
3962
+ await syncMindProfile(name, profile);
3963
+ broadcast({ type: "profile_updated", mind: name, summary: `${name} avatar updated` });
3964
+ return c.json({ ok: true, avatar: filename });
3965
+ }).get("/:name/avatar", async (c) => {
3966
+ const name = c.req.param("name");
3967
+ const entry = await findMind(name);
3968
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
3969
+ const dir = entry.dir ?? mindDir(name);
3069
3970
  const config2 = readVoluteConfig(dir);
3070
3971
  if (!config2?.profile?.avatar) return c.json({ error: "No avatar configured" }, 404);
3071
3972
  const ext = extname2(config2.profile.avatar).toLowerCase();
@@ -3100,25 +4001,29 @@ var app9 = new Hono10().get("/:name/avatar", async (c) => {
3100
4001
  var files_default = app9;
3101
4002
 
3102
4003
  // src/web/api/history.ts
3103
- import { and as and3, desc as desc2, eq as eq4, inArray, sql } from "drizzle-orm";
4004
+ import { and as and4, desc as desc3, eq as eq5, gte as gte2, inArray, sql as sql2 } from "drizzle-orm";
3104
4005
  import { Hono as Hono11 } from "hono";
3105
4006
  var history = new Hono11().get("/turns", async (c) => {
3106
4007
  const mindFilter = c.req.query("mind");
3107
4008
  const turnIdFilter = c.req.query("turnId");
4009
+ const turnIdsFilter = c.req.query("turnIds");
3108
4010
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
3109
4011
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
3110
4012
  const db = await getDb();
3111
4013
  const conditions = [];
3112
- if (mindFilter) conditions.push(eq4(turns.mind, mindFilter));
3113
- if (turnIdFilter) conditions.push(eq4(turns.id, turnIdFilter));
3114
- const turnRows = await db.select().from(turns).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc2(turns.created_at)).limit(limit).offset(offset);
4014
+ if (mindFilter) conditions.push(eq5(turns.mind, mindFilter));
4015
+ if (turnIdFilter) conditions.push(eq5(turns.id, turnIdFilter));
4016
+ if (turnIdsFilter) {
4017
+ const ids = turnIdsFilter.split(",").filter(Boolean);
4018
+ if (ids.length > 0) conditions.push(inArray(turns.id, ids));
4019
+ }
4020
+ const turnRows = await db.select().from(turns).where(conditions.length > 0 ? and4(...conditions) : void 0).orderBy(desc3(turns.created_at)).limit(limit).offset(offset);
3115
4021
  if (turnRows.length === 0) return c.json([]);
3116
4022
  const turnIds = turnRows.map((t) => t.id);
3117
- const summaryRows = await db.select().from(mindHistory).where(and3(eq4(mindHistory.type, "summary"), inArray(mindHistory.turn_id, turnIds)));
4023
+ const summaryRows = await db.select().from(summaries).where(and4(eq5(summaries.period, "turn"), inArray(summaries.period_key, turnIds)));
3118
4024
  const summaryByTurn = /* @__PURE__ */ new Map();
3119
4025
  for (const s of summaryRows) {
3120
- if (s.turn_id)
3121
- summaryByTurn.set(s.turn_id, { content: s.content ?? "", metadata: s.metadata });
4026
+ summaryByTurn.set(s.period_key, { content: s.content, metadata: s.metadata });
3122
4027
  }
3123
4028
  const historyMsgRows = await db.select({
3124
4029
  id: mindHistory.id,
@@ -3130,9 +4035,9 @@ var history = new Hono11().get("/turns", async (c) => {
3130
4035
  turn_id: mindHistory.turn_id,
3131
4036
  created_at: mindHistory.created_at
3132
4037
  }).from(mindHistory).where(
3133
- and3(
4038
+ and4(
3134
4039
  inArray(mindHistory.turn_id, turnIds),
3135
- sql`${mindHistory.type} IN ('inbound', 'outbound')`
4040
+ sql2`${mindHistory.type} IN ('inbound', 'outbound')`
3136
4041
  )
3137
4042
  ).orderBy(mindHistory.created_at);
3138
4043
  const msgsByTurnChannel = /* @__PURE__ */ new Map();
@@ -3191,13 +4096,92 @@ var history = new Hono11().get("/turns", async (c) => {
3191
4096
  }).from(mindHistory).where(inArray(mindHistory.id, triggerIds));
3192
4097
  for (const r of triggerRows) triggerMap.set(r.id, r);
3193
4098
  }
4099
+ const allChannelSlugs = /* @__PURE__ */ new Set();
4100
+ for (const [, channels] of msgsByTurnChannel) {
4101
+ for (const ch of channels.keys()) allChannelSlugs.add(ch);
4102
+ }
4103
+ const dmSlugMinds = /* @__PURE__ */ new Map();
4104
+ for (const [turnId, channels] of msgsByTurnChannel) {
4105
+ const mindName = turnMindMap.get(turnId);
4106
+ if (!mindName) continue;
4107
+ for (const ch of channels.keys()) {
4108
+ if (ch.startsWith("@")) {
4109
+ let minds = dmSlugMinds.get(ch);
4110
+ if (!minds) {
4111
+ minds = /* @__PURE__ */ new Set();
4112
+ dmSlugMinds.set(ch, minds);
4113
+ }
4114
+ minds.add(mindName);
4115
+ }
4116
+ }
4117
+ }
4118
+ const channelIdMap = /* @__PURE__ */ new Map();
4119
+ try {
4120
+ if (allChannelSlugs.size > 0) {
4121
+ const channelNames = [...allChannelSlugs].filter((s) => !s.startsWith("@")).map((s) => {
4122
+ let name = s;
4123
+ if (name.startsWith("#")) name = name.slice(1);
4124
+ const colonIdx = name.indexOf(":");
4125
+ if (colonIdx >= 0) name = name.substring(colonIdx + 1);
4126
+ return { slug: s, name };
4127
+ });
4128
+ if (channelNames.length > 0) {
4129
+ const channelRows = await db.select({ id: conversations.id, name: conversations.name }).from(conversations).where(
4130
+ and4(
4131
+ eq5(conversations.type, "channel"),
4132
+ inArray(
4133
+ conversations.name,
4134
+ channelNames.map((c2) => c2.name)
4135
+ )
4136
+ )
4137
+ );
4138
+ const nameToId = new Map(channelRows.map((r) => [r.name, r.id]));
4139
+ for (const { slug, name } of channelNames) {
4140
+ const id = nameToId.get(name);
4141
+ if (id) channelIdMap.set(slug, id);
4142
+ }
4143
+ }
4144
+ const dmSlugs = [...dmSlugMinds.keys()];
4145
+ if (dmSlugs.length > 0) {
4146
+ const targetNames = dmSlugs.map((s) => s.slice(1));
4147
+ const cp2 = db.$with("cp2").as(
4148
+ db.select({
4149
+ conversation_id: conversationParticipants.conversation_id,
4150
+ username: users.username
4151
+ }).from(conversationParticipants).innerJoin(users, eq5(conversationParticipants.user_id, users.id))
4152
+ );
4153
+ const dmRows = await db.with(cp2).select({
4154
+ id: conversations.id,
4155
+ targetUsername: users.username
4156
+ }).from(conversations).innerJoin(
4157
+ conversationParticipants,
4158
+ eq5(conversations.id, conversationParticipants.conversation_id)
4159
+ ).innerJoin(users, eq5(conversationParticipants.user_id, users.id)).where(and4(eq5(conversations.type, "dm"), inArray(users.username, targetNames)));
4160
+ for (const row of dmRows) {
4161
+ const slug = `@${row.targetUsername}`;
4162
+ const mindNames = dmSlugMinds.get(slug);
4163
+ if (mindNames && !channelIdMap.has(slug)) {
4164
+ const mindCheck = await db.select({ id: conversationParticipants.user_id }).from(conversationParticipants).innerJoin(users, eq5(conversationParticipants.user_id, users.id)).where(
4165
+ and4(
4166
+ eq5(conversationParticipants.conversation_id, row.id),
4167
+ inArray(users.username, [...mindNames])
4168
+ )
4169
+ ).get();
4170
+ if (mindCheck) channelIdMap.set(slug, row.id);
4171
+ }
4172
+ }
4173
+ }
4174
+ }
4175
+ } catch (err) {
4176
+ logger_default.warn("Failed to resolve channel slugs to conversation IDs", logger_default.errorData(err));
4177
+ }
3194
4178
  const result = turnRows.map((t) => {
3195
4179
  const summary = summaryByTurn.get(t.id);
3196
4180
  const turnChannels = msgsByTurnChannel.get(t.id) ?? /* @__PURE__ */ new Map();
3197
4181
  const convEntries = [...turnChannels.entries()].map(([channel, evts]) => {
3198
4182
  const { label, type } = getChannelLabel(channel);
3199
4183
  return {
3200
- id: channel,
4184
+ id: channelIdMap.get(channel) ?? channel,
3201
4185
  label,
3202
4186
  type,
3203
4187
  messages: evts.map((m) => ({
@@ -3305,6 +4289,59 @@ var history = new Hono11().get("/turns", async (c) => {
3305
4289
  Connection: "keep-alive"
3306
4290
  }
3307
4291
  });
4292
+ }).get("/summaries", async (c) => {
4293
+ const mind = c.req.query("mind") ?? "_system";
4294
+ const period = c.req.query("period");
4295
+ const ids = c.req.query("ids");
4296
+ const from = c.req.query("from");
4297
+ const to = c.req.query("to");
4298
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
4299
+ if (ids) {
4300
+ const db2 = await getDb();
4301
+ const idList = ids.split(",").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
4302
+ if (idList.length === 0) return c.json([]);
4303
+ const rows2 = await db2.select().from(summaries).where(inArray(summaries.id, idList));
4304
+ const result2 = rows2.map((r) => {
4305
+ let metadata = null;
4306
+ if (r.metadata) {
4307
+ try {
4308
+ metadata = JSON.parse(r.metadata);
4309
+ } catch (err) {
4310
+ logger_default.debug(`malformed summary metadata for id ${r.id}`, logger_default.errorData(err));
4311
+ }
4312
+ }
4313
+ return { ...r, metadata };
4314
+ });
4315
+ return c.json(result2);
4316
+ }
4317
+ if (!period || !["turn", "hour", "day", "week", "month"].includes(period)) {
4318
+ return c.json({ error: "period is required (turn, hour, day, week, month)" }, 400);
4319
+ }
4320
+ const db = await getDb();
4321
+ const conditions = [eq5(summaries.mind, mind), eq5(summaries.period, period)];
4322
+ if (from) conditions.push(gte2(summaries.period_key, from));
4323
+ if (to) conditions.push(sql2`${summaries.period_key} <= ${to}`);
4324
+ const rows = await db.select({
4325
+ id: summaries.id,
4326
+ mind: summaries.mind,
4327
+ period: summaries.period,
4328
+ period_key: summaries.period_key,
4329
+ content: summaries.content,
4330
+ metadata: summaries.metadata,
4331
+ created_at: summaries.created_at
4332
+ }).from(summaries).where(and4(...conditions)).orderBy(desc3(summaries.period_key)).limit(limit);
4333
+ const result = rows.map((r) => {
4334
+ let metadata = null;
4335
+ if (r.metadata) {
4336
+ try {
4337
+ metadata = JSON.parse(r.metadata);
4338
+ } catch (err) {
4339
+ logger_default.debug(`malformed meta_summary metadata for id ${r.id}`, logger_default.errorData(err));
4340
+ }
4341
+ }
4342
+ return { ...r, metadata };
4343
+ });
4344
+ return c.json(result);
3308
4345
  });
3309
4346
  var history_default = history;
3310
4347
 
@@ -3313,19 +4350,19 @@ import { Hono as Hono12 } from "hono";
3313
4350
 
3314
4351
  // src/lib/identity.ts
3315
4352
  import { createHash, generateKeyPairSync, sign, verify } from "crypto";
3316
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
4353
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3317
4354
  import { resolve as resolve8 } from "path";
3318
4355
  function generateIdentity(mindDir2) {
3319
4356
  const identityDir = resolve8(mindDir2, ".mind/identity");
3320
- mkdirSync4(identityDir, { recursive: true });
4357
+ mkdirSync5(identityDir, { recursive: true });
3321
4358
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
3322
4359
  publicKeyEncoding: { type: "spki", format: "pem" },
3323
4360
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
3324
4361
  });
3325
4362
  const privatePath = resolve8(identityDir, "private.pem");
3326
4363
  const publicPath = resolve8(identityDir, "public.pem");
3327
- writeFileSync4(privatePath, privateKey, { mode: 384 });
3328
- writeFileSync4(publicPath, publicKey, { mode: 420 });
4364
+ writeFileSync5(privatePath, privateKey, { mode: 384 });
4365
+ writeFileSync5(publicPath, publicKey, { mode: 420 });
3329
4366
  const config2 = readVoluteConfig(mindDir2) ?? {};
3330
4367
  config2.identity = {
3331
4368
  privateKey: ".mind/identity/private.pem",
@@ -3449,7 +4486,7 @@ var app12 = new Hono14().get("/:name/skills", async (c) => {
3449
4486
  const name = c.req.param("name");
3450
4487
  const entry = await findMind(name);
3451
4488
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3452
- const dir = mindDir(name);
4489
+ const dir = entry.dir ?? mindDir(name);
3453
4490
  const skills = await listMindSkills(dir);
3454
4491
  return c.json(skills);
3455
4492
  }).post(
@@ -3461,7 +4498,7 @@ var app12 = new Hono14().get("/:name/skills", async (c) => {
3461
4498
  const entry = await findMind(name);
3462
4499
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3463
4500
  const { skillId } = c.req.valid("json");
3464
- const dir = mindDir(name);
4501
+ const dir = entry.dir ?? mindDir(name);
3465
4502
  try {
3466
4503
  const result = await installSkill(name, dir, skillId);
3467
4504
  return c.json({ ok: true, ...result });
@@ -3479,7 +4516,7 @@ var app12 = new Hono14().get("/:name/skills", async (c) => {
3479
4516
  const entry = await findMind(name);
3480
4517
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3481
4518
  const { skillId } = c.req.valid("json");
3482
- const dir = mindDir(name);
4519
+ const dir = entry.dir ?? mindDir(name);
3483
4520
  try {
3484
4521
  const result = await updateSkill(name, dir, skillId);
3485
4522
  return c.json(result);
@@ -3497,7 +4534,7 @@ var app12 = new Hono14().get("/:name/skills", async (c) => {
3497
4534
  const entry = await findMind(name);
3498
4535
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3499
4536
  const { skillId } = c.req.valid("json");
3500
- const dir = mindDir(name);
4537
+ const dir = entry.dir ?? mindDir(name);
3501
4538
  try {
3502
4539
  const skill = await publishSkill(name, dir, skillId);
3503
4540
  return c.json(skill);
@@ -3511,7 +4548,7 @@ var app12 = new Hono14().get("/:name/skills", async (c) => {
3511
4548
  const skillName = c.req.param("skill");
3512
4549
  const entry = await findMind(name);
3513
4550
  if (!entry) return c.json({ error: "Mind not found" }, 404);
3514
- const dir = mindDir(name);
4551
+ const dir = entry.dir ?? mindDir(name);
3515
4552
  try {
3516
4553
  await uninstallSkill(name, dir, skillName);
3517
4554
  } catch (e) {
@@ -3526,11 +4563,11 @@ var mind_skills_default = app12;
3526
4563
  import {
3527
4564
  cpSync,
3528
4565
  existsSync as existsSync9,
3529
- mkdirSync as mkdirSync6,
4566
+ mkdirSync as mkdirSync7,
3530
4567
  readdirSync as readdirSync2,
3531
4568
  readFileSync as readFileSync10,
3532
- rmSync as rmSync4,
3533
- writeFileSync as writeFileSync7
4569
+ rmSync as rmSync5,
4570
+ writeFileSync as writeFileSync8
3534
4571
  } from "fs";
3535
4572
  import { resolve as resolve12 } from "path";
3536
4573
  import { zValidator as zValidator5 } from "@hono/zod-validator";
@@ -3539,7 +4576,7 @@ import { Hono as Hono15 } from "hono";
3539
4576
  import { z as z5 } from "zod";
3540
4577
 
3541
4578
  // src/lib/consolidate.ts
3542
- import { readdirSync, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
4579
+ import { readdirSync, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3543
4580
  import { resolve as resolve10 } from "path";
3544
4581
  async function consolidateMemory(mindDir2) {
3545
4582
  const soulPath = resolve10(mindDir2, "home/SOUL.md");
@@ -3601,7 +4638,7 @@ ${content2}`);
3601
4638
  const data = await res.json();
3602
4639
  const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
3603
4640
  if (content) {
3604
- writeFileSync5(memoryPath, `${content}
4641
+ writeFileSync6(memoryPath, `${content}
3605
4642
  `);
3606
4643
  console.log("MEMORY.md created successfully.");
3607
4644
  } else {
@@ -3611,7 +4648,7 @@ ${content2}`);
3611
4648
 
3612
4649
  // src/lib/convert-session.ts
3613
4650
  import { randomUUID } from "crypto";
3614
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
4651
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
3615
4652
  import { homedir } from "os";
3616
4653
  import { resolve as resolve11 } from "path";
3617
4654
  function convertSession(opts) {
@@ -3717,321 +4754,70 @@ function convertSession(opts) {
3717
4754
  isSidechain: false,
3718
4755
  userType: "external",
3719
4756
  type: "user",
3720
- sourceToolAssistantUUID: lastSdkUuid ?? void 0,
3721
- toolUseResult: "imported",
3722
- message: {
3723
- role: "user",
3724
- content: toolResults
3725
- }
3726
- };
3727
- sdkEvents.push(JSON.stringify(sdkEvent));
3728
- lastSdkUuid = uuid;
3729
- }
3730
- }
3731
- const projectId = opts.projectDir.replace(/\//g, "-");
3732
- const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
3733
- mkdirSync5(sdkDir, { recursive: true });
3734
- const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
3735
- writeFileSync6(sdkPath, `${sdkEvents.join("\n")}
3736
- `);
3737
- console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
3738
- return sessionId;
3739
- }
3740
- var MODEL_MAP = {
3741
- "claude-opus-4-5": "claude-opus-4-5-20251101",
3742
- "claude-sonnet-4": "claude-sonnet-4-20250514"
3743
- };
3744
- function mapModel(model) {
3745
- if (!model) return "claude-opus-4-5-20251101";
3746
- return MODEL_MAP[model] ?? model;
3747
- }
3748
- function mapStopReason(stopReason) {
3749
- if (!stopReason) return "end_turn";
3750
- const map = {
3751
- toolUse: "tool_use",
3752
- endTurn: "end_turn",
3753
- stop: "end_turn",
3754
- maxTokens: "max_tokens"
3755
- };
3756
- return map[stopReason] ?? stopReason;
3757
- }
3758
- function mapUsage(usage) {
3759
- if (!usage) return { input_tokens: 0, output_tokens: 0 };
3760
- return {
3761
- input_tokens: usage.input ?? usage.input_tokens ?? 0,
3762
- output_tokens: usage.output ?? usage.output_tokens ?? 0,
3763
- cache_read_input_tokens: usage.cacheRead ?? usage.cache_read_input_tokens ?? 0,
3764
- cache_creation_input_tokens: usage.cacheWrite ?? usage.cache_creation_input_tokens ?? 0
3765
- };
3766
- }
3767
- function convertAssistantContent(content) {
3768
- const result = [];
3769
- for (const block of content) {
3770
- if (block.type === "thinking") {
3771
- } else if (block.type === "toolCall") {
3772
- result.push({
3773
- type: "tool_use",
3774
- id: block.id,
3775
- name: block.name,
3776
- input: block.arguments ?? block.input ?? {},
3777
- caller: { type: "direct" }
3778
- });
3779
- } else {
3780
- result.push(block);
3781
- }
3782
- }
3783
- return result;
3784
- }
3785
-
3786
- // src/lib/daemon/turn-summarizer.ts
3787
- import { and as and4, desc as desc3, eq as eq5, gt, lt as lt3, sql as sql2 } from "drizzle-orm";
3788
-
3789
- // src/lib/format-tool.ts
3790
- function summarizeTool(name, input) {
3791
- if (input && typeof input === "object") {
3792
- const args = input;
3793
- const val = args.path ?? args.command ?? args.query ?? args.url;
3794
- if (typeof val === "string") {
3795
- const brief = val.length > 60 ? `${val.slice(0, 57)}...` : val;
3796
- return `[${name} ${brief}]`;
3797
- }
3798
- }
3799
- return `[${name}]`;
3800
- }
3801
-
3802
- // src/lib/daemon/turn-summarizer.ts
3803
- var sLog = logger_default.child("turn-summarizer");
3804
- async function gatherTurnEvents(mind, session, doneId) {
3805
- const db = await getDb();
3806
- const conditions = [
3807
- eq5(mindHistory.mind, mind),
3808
- eq5(mindHistory.type, "done"),
3809
- lt3(mindHistory.id, doneId)
3810
- ];
3811
- if (session) {
3812
- conditions.push(eq5(mindHistory.session, session));
3813
- }
3814
- const prevDone = await db.select({ id: mindHistory.id }).from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.id)).limit(1);
3815
- const prevDoneId = prevDone.length > 0 ? prevDone[0].id : 0;
3816
- const turnConditions = [
3817
- eq5(mindHistory.mind, mind),
3818
- gt(mindHistory.id, prevDoneId),
3819
- sql2`${mindHistory.id} <= ${doneId}`
3820
- ];
3821
- if (session) {
3822
- turnConditions.push(eq5(mindHistory.session, session));
3823
- }
3824
- const events = await db.select({
3825
- id: mindHistory.id,
3826
- type: mindHistory.type,
3827
- channel: mindHistory.channel,
3828
- session: mindHistory.session,
3829
- content: mindHistory.content,
3830
- metadata: mindHistory.metadata,
3831
- created_at: mindHistory.created_at
3832
- }).from(mindHistory).where(and4(...turnConditions)).orderBy(mindHistory.id);
3833
- return {
3834
- events,
3835
- fromId: events.length > 0 ? events[0].id : doneId,
3836
- toId: doneId
3837
- };
3838
- }
3839
- function buildDeterministicSummary(events) {
3840
- const channels = /* @__PURE__ */ new Set();
3841
- const tools = [];
3842
- let hasInbound = false;
3843
- let hasOutbound = false;
3844
- for (const ev of events) {
3845
- if (ev.type === "inbound") {
3846
- hasInbound = true;
3847
- if (ev.channel) channels.add(ev.channel);
3848
- }
3849
- if (ev.type === "outbound" || ev.type === "text") {
3850
- hasOutbound = true;
3851
- }
3852
- if (ev.type === "tool_use" && ev.metadata) {
3853
- try {
3854
- const meta = JSON.parse(ev.metadata);
3855
- if (meta.name) tools.push(meta.name);
3856
- } catch (err) {
3857
- sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3858
- }
3859
- }
3860
- }
3861
- const parts = [];
3862
- if (hasInbound) {
3863
- const channelList = [...channels];
3864
- parts.push(
3865
- channelList.length > 0 ? `Received message on ${channelList.join(", ")}` : "Received message"
3866
- );
3867
- }
3868
- if (tools.length > 0) {
3869
- const unique = [...new Set(tools)];
3870
- parts.push(`Used ${unique.join(", ")}`);
3871
- }
3872
- if (hasOutbound) {
3873
- parts.push("Sent response");
3874
- }
3875
- return parts.length > 0 ? `${parts.join(". ")}.` : "Turn completed.";
3876
- }
3877
- function buildTranscript(events) {
3878
- const lines = [];
3879
- for (const ev of events) {
3880
- switch (ev.type) {
3881
- case "inbound":
3882
- lines.push(`[inbound${ev.channel ? ` ${ev.channel}` : ""}] ${ev.content ?? ""}`);
3883
- break;
3884
- case "outbound":
3885
- case "text":
3886
- lines.push(`[response] ${(ev.content ?? "").slice(0, 500)}`);
3887
- break;
3888
- case "tool_use": {
3889
- let toolInfo = "tool";
3890
- if (ev.metadata) {
3891
- try {
3892
- const meta = JSON.parse(ev.metadata);
3893
- toolInfo = summarizeTool(meta.name ?? "tool", meta.input ?? {});
3894
- } catch (err) {
3895
- sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3896
- }
3897
- }
3898
- lines.push(toolInfo);
3899
- break;
3900
- }
3901
- case "tool_result": {
3902
- const content = ev.content ?? "";
3903
- let isError = false;
3904
- if (ev.metadata) {
3905
- try {
3906
- const meta = JSON.parse(ev.metadata);
3907
- isError = !!meta.is_error;
3908
- } catch (err) {
3909
- sLog.debug(
3910
- `failed to parse tool_result metadata for event ${ev.id}`,
3911
- logger_default.errorData(err)
3912
- );
3913
- }
4757
+ sourceToolAssistantUUID: lastSdkUuid ?? void 0,
4758
+ toolUseResult: "imported",
4759
+ message: {
4760
+ role: "user",
4761
+ content: toolResults
3914
4762
  }
3915
- lines.push(isError ? "[result error]" : `[result] ${content.slice(0, 200)}`);
3916
- break;
3917
- }
3918
- case "thinking":
3919
- lines.push(`[thinking] ${(ev.content ?? "").slice(0, 300)}`);
3920
- break;
4763
+ };
4764
+ sdkEvents.push(JSON.stringify(sdkEvent));
4765
+ lastSdkUuid = uuid;
3921
4766
  }
3922
4767
  }
3923
- return lines.join("\n");
4768
+ const projectId = opts.projectDir.replace(/\//g, "-");
4769
+ const sdkDir = resolve11(homedir(), ".claude", "projects", projectId);
4770
+ mkdirSync6(sdkDir, { recursive: true });
4771
+ const sdkPath = resolve11(sdkDir, `${sessionId}.jsonl`);
4772
+ writeFileSync7(sdkPath, `${sdkEvents.join("\n")}
4773
+ `);
4774
+ console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
4775
+ return sessionId;
3924
4776
  }
3925
- async function gatherTurnEventsByTurnId(turnId) {
3926
- const db = await getDb();
3927
- const events = await db.select({
3928
- id: mindHistory.id,
3929
- type: mindHistory.type,
3930
- channel: mindHistory.channel,
3931
- session: mindHistory.session,
3932
- content: mindHistory.content,
3933
- metadata: mindHistory.metadata,
3934
- created_at: mindHistory.created_at
3935
- }).from(mindHistory).where(eq5(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
4777
+ var MODEL_MAP = {
4778
+ "claude-opus-4-5": "claude-opus-4-5-20251101",
4779
+ "claude-sonnet-4": "claude-sonnet-4-20250514"
4780
+ };
4781
+ function mapModel(model) {
4782
+ if (!model) return "claude-opus-4-5-20251101";
4783
+ return MODEL_MAP[model] ?? model;
4784
+ }
4785
+ function mapStopReason(stopReason) {
4786
+ if (!stopReason) return "end_turn";
4787
+ const map = {
4788
+ toolUse: "tool_use",
4789
+ endTurn: "end_turn",
4790
+ stop: "end_turn",
4791
+ maxTokens: "max_tokens"
4792
+ };
4793
+ return map[stopReason] ?? stopReason;
4794
+ }
4795
+ function mapUsage(usage) {
4796
+ if (!usage) return { input_tokens: 0, output_tokens: 0 };
3936
4797
  return {
3937
- events,
3938
- fromId: events.length > 0 ? events[0].id : 0,
3939
- toId: events.length > 0 ? events[events.length - 1].id : 0
4798
+ input_tokens: usage.input ?? usage.input_tokens ?? 0,
4799
+ output_tokens: usage.output ?? usage.output_tokens ?? 0,
4800
+ cache_read_input_tokens: usage.cacheRead ?? usage.cache_read_input_tokens ?? 0,
4801
+ cache_creation_input_tokens: usage.cacheWrite ?? usage.cache_creation_input_tokens ?? 0
3940
4802
  };
3941
4803
  }
3942
- async function summarizeTurn(mind, session, channel, doneId, turnId) {
3943
- const { events, fromId, toId } = turnId ? await gatherTurnEventsByTurnId(turnId) : await gatherTurnEvents(mind, session, doneId);
3944
- if (events.length === 0) return;
3945
- const substantiveTypes = /* @__PURE__ */ new Set(["text", "outbound", "tool_use", "tool_result", "thinking"]);
3946
- const hasSubstantiveOutput = events.some((ev) => substantiveTypes.has(ev.type));
3947
- if (!hasSubstantiveOutput) {
3948
- sLog.info(
3949
- `skipping summary for interrupted turn ${turnId ?? "(no turn)"} (no substantive output)`
3950
- );
3951
- if (turnId) {
3952
- try {
3953
- const db2 = await getDb();
3954
- await db2.update(mindHistory).set({ turn_id: null }).where(and4(eq5(mindHistory.turn_id, turnId), eq5(mindHistory.type, "inbound")));
3955
- await db2.update(messages).set({ turn_id: null }).where(eq5(messages.turn_id, turnId));
3956
- } catch (err) {
3957
- sLog.error(`failed to un-tag events for interrupted turn ${turnId}`, logger_default.errorData(err));
3958
- }
3959
- }
3960
- return;
3961
- }
3962
- const tools = [];
3963
- for (const ev of events) {
3964
- if (ev.type === "tool_use" && ev.metadata) {
3965
- try {
3966
- const meta = JSON.parse(ev.metadata);
3967
- if (meta.name) tools.push(meta.name);
3968
- } catch (err) {
3969
- sLog.debug(`failed to parse tool_use metadata for event ${ev.id}`, logger_default.errorData(err));
3970
- }
3971
- }
3972
- }
3973
- const fromTime = events[0].created_at;
3974
- const toTime = events[events.length - 1].created_at;
3975
- let summaryText;
3976
- let deterministic;
3977
- const transcript = buildTranscript(events);
3978
- if (transcript.trim()) {
3979
- const summaryPrompt = await getPrompt("turn_summary");
3980
- const aiResult = await aiCompleteUtility(summaryPrompt, transcript);
3981
- if (aiResult) {
3982
- summaryText = aiResult;
3983
- deterministic = false;
4804
+ function convertAssistantContent(content) {
4805
+ const result = [];
4806
+ for (const block of content) {
4807
+ if (block.type === "thinking") {
4808
+ } else if (block.type === "toolCall") {
4809
+ result.push({
4810
+ type: "tool_use",
4811
+ id: block.id,
4812
+ name: block.name,
4813
+ input: block.arguments ?? block.input ?? {},
4814
+ caller: { type: "direct" }
4815
+ });
3984
4816
  } else {
3985
- summaryText = buildDeterministicSummary(events);
3986
- deterministic = true;
4817
+ result.push(block);
3987
4818
  }
3988
- } else {
3989
- summaryText = buildDeterministicSummary(events);
3990
- deterministic = true;
3991
- }
3992
- const metadata = {
3993
- deterministic,
3994
- tool_count: tools.length,
3995
- tools: [...new Set(tools)],
3996
- from_id: fromId,
3997
- to_id: toId,
3998
- from_time: fromTime,
3999
- to_time: toTime
4000
- };
4001
- const db = await getDb();
4002
- let summaryId;
4003
- try {
4004
- const result = await db.insert(mindHistory).values({
4005
- mind,
4006
- type: "summary",
4007
- session: session ?? null,
4008
- channel: channel ?? null,
4009
- content: summaryText,
4010
- metadata: JSON.stringify(metadata),
4011
- turn_id: turnId ?? null
4012
- }).returning({ id: mindHistory.id });
4013
- summaryId = result[0]?.id;
4014
- } catch (err) {
4015
- sLog.error(
4016
- `failed to persist summary for ${mind} (events ${fromId}-${toId})`,
4017
- logger_default.errorData(err)
4018
- );
4019
- return;
4020
- }
4021
- if (turnId && summaryId != null) {
4022
- setSummaryEventId(turnId, summaryId).catch((err) => {
4023
- sLog.error(`failed to link summary to turn ${turnId}`, logger_default.errorData(err));
4024
- });
4025
4819
  }
4026
- publish2(mind, {
4027
- mind,
4028
- type: "summary",
4029
- session,
4030
- channel,
4031
- content: summaryText,
4032
- metadata,
4033
- turnId
4034
- });
4820
+ return result;
4035
4821
  }
4036
4822
 
4037
4823
  // src/lib/health.ts
@@ -4049,7 +4835,7 @@ async function checkHealth(port) {
4049
4835
  }
4050
4836
 
4051
4837
  // src/lib/variant-cleanup.ts
4052
- import { existsSync as existsSync8, rmSync as rmSync3 } from "fs";
4838
+ import { existsSync as existsSync8, rmSync as rmSync4 } from "fs";
4053
4839
  async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
4054
4840
  if (opts?.stop) {
4055
4841
  try {
@@ -4058,14 +4844,14 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
4058
4844
  logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
4059
4845
  }
4060
4846
  }
4061
- const { findMind: findMind2 } = await import("./registry-PJ4S5PHQ.js");
4847
+ const { findMind: findMind2 } = await import("./registry-UYV5S6QT.js");
4062
4848
  const variantEntry = await findMind2(variantName);
4063
4849
  const branchName = variantEntry?.branch ?? variantName;
4064
4850
  if (existsSync8(variantPath)) {
4065
4851
  try {
4066
4852
  await gitExec(["worktree", "remove", "--force", variantPath], { cwd: projectRoot });
4067
4853
  } catch {
4068
- rmSync3(variantPath, { recursive: true, force: true });
4854
+ rmSync4(variantPath, { recursive: true, force: true });
4069
4855
  try {
4070
4856
  await gitExec(["worktree", "prune"], { cwd: projectRoot });
4071
4857
  } catch (err) {
@@ -4112,7 +4898,7 @@ async function getMindStatus(name, port) {
4112
4898
  const manager = getMindManager();
4113
4899
  let status = "stopped";
4114
4900
  try {
4115
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4901
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
4116
4902
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
4117
4903
  status = "sleeping";
4118
4904
  }
@@ -4170,7 +4956,7 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4170
4956
  } catch {
4171
4957
  }
4172
4958
  if (existsSync9(tempWorktree)) {
4173
- rmSync4(tempWorktree, { recursive: true, force: true });
4959
+ rmSync5(tempWorktree, { recursive: true, force: true });
4174
4960
  }
4175
4961
  const templatesRoot = findTemplatesRoot();
4176
4962
  const { composedDir, manifest } = composeTemplate(templatesRoot, template);
@@ -4192,13 +4978,13 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4192
4978
  copyTemplateToDir(composedDir, tempWorktree, mindName, manifest);
4193
4979
  const initDir = resolve12(tempWorktree, ".init");
4194
4980
  if (existsSync9(initDir)) {
4195
- rmSync4(initDir, { recursive: true, force: true });
4981
+ rmSync5(initDir, { recursive: true, force: true });
4196
4982
  }
4197
4983
  const homeDir = resolve12(tempWorktree, "home");
4198
4984
  if (existsSync9(homeDir)) {
4199
4985
  for (const entry of readdirSync2(homeDir)) {
4200
4986
  if (entry !== "VOLUTE.md") {
4201
- rmSync4(resolve12(homeDir, entry), { recursive: true, force: true });
4987
+ rmSync5(resolve12(homeDir, entry), { recursive: true, force: true });
4202
4988
  }
4203
4989
  }
4204
4990
  }
@@ -4214,9 +5000,9 @@ async function updateTemplateBranch(projectRoot, template, mindName) {
4214
5000
  } catch {
4215
5001
  }
4216
5002
  if (existsSync9(tempWorktree)) {
4217
- rmSync4(tempWorktree, { recursive: true, force: true });
5003
+ rmSync5(tempWorktree, { recursive: true, force: true });
4218
5004
  }
4219
- rmSync4(composedDir, { recursive: true, force: true });
5005
+ rmSync5(composedDir, { recursive: true, force: true });
4220
5006
  }
4221
5007
  }
4222
5008
  async function mergeTemplateBranch(worktreeDir) {
@@ -4316,7 +5102,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4316
5102
  generateIdentity(dest);
4317
5103
  }
4318
5104
  const state = stateDir(name);
4319
- mkdirSync6(state, { recursive: true });
5105
+ mkdirSync7(state, { recursive: true });
4320
5106
  const envJson = resolve12(tempDir, "state/env.json");
4321
5107
  if (existsSync9(envJson)) {
4322
5108
  cpSync(envJson, resolve12(state, "env.json"));
@@ -4344,20 +5130,20 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
4344
5130
  await gitExec(["commit", "-m", "import from archive"], { cwd: dest, mindName: name, env });
4345
5131
  } catch (err) {
4346
5132
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4347
- rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
5133
+ rmSync5(resolve12(dest, ".git"), { recursive: true, force: true });
4348
5134
  }
4349
5135
  }
4350
5136
  chownMindDir(dest, name);
4351
- rmSync4(tempDir, { recursive: true, force: true });
5137
+ rmSync5(tempDir, { recursive: true, force: true });
4352
5138
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4353
5139
  } catch (err) {
4354
- if (existsSync9(dest)) rmSync4(dest, { recursive: true, force: true });
5140
+ if (existsSync9(dest)) rmSync5(dest, { recursive: true, force: true });
4355
5141
  try {
4356
5142
  await removeMind(name);
4357
5143
  } catch (cleanupErr) {
4358
5144
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4359
5145
  }
4360
- rmSync4(tempDir, { recursive: true, force: true });
5146
+ rmSync5(tempDir, { recursive: true, force: true });
4361
5147
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4362
5148
  }
4363
5149
  }
@@ -4395,11 +5181,11 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4395
5181
  const promptsPath = resolve12(dest, "home/.config/prompts.json");
4396
5182
  if (!existsSync9(promptsPath)) {
4397
5183
  const mindPrompts = await getMindPromptDefaults();
4398
- writeFileSync7(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
5184
+ writeFileSync8(promptsPath, `${JSON.stringify(mindPrompts, null, 2)}
4399
5185
  `);
4400
5186
  }
4401
5187
  const state = stateDir(name);
4402
- mkdirSync6(state, { recursive: true });
5188
+ mkdirSync7(state, { recursive: true });
4403
5189
  const envJson = resolve12(tempDir, "state/env.json");
4404
5190
  if (existsSync9(envJson)) {
4405
5191
  cpSync(envJson, resolve12(state, "env.json"));
@@ -4419,7 +5205,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4419
5205
  await initTemplateBranch(dest, composedDir, templateManifest, name, env);
4420
5206
  } catch (err) {
4421
5207
  logger_default.error(`git setup failed for imported mind ${name}`, logger_default.errorData(err));
4422
- rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
5208
+ rmSync5(resolve12(dest, ".git"), { recursive: true, force: true });
4423
5209
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4424
5210
  }
4425
5211
  try {
@@ -4443,7 +5229,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4443
5229
  publishPublicKey(name, publicKeyPem).catch(
4444
5230
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
4445
5231
  );
4446
- rmSync4(tempDir, { recursive: true, force: true });
5232
+ rmSync5(tempDir, { recursive: true, force: true });
4447
5233
  return c.json({
4448
5234
  ok: true,
4449
5235
  name,
@@ -4454,16 +5240,16 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
4454
5240
  ...skillWarnings.length > 0 && { skillWarnings }
4455
5241
  });
4456
5242
  } catch (err) {
4457
- if (existsSync9(dest)) rmSync4(dest, { recursive: true, force: true });
5243
+ if (existsSync9(dest)) rmSync5(dest, { recursive: true, force: true });
4458
5244
  try {
4459
5245
  await removeMind(name);
4460
5246
  } catch (cleanupErr) {
4461
5247
  logger_default.error(`Failed to clean up registry for ${name}`, logger_default.errorData(cleanupErr));
4462
5248
  }
4463
- rmSync4(tempDir, { recursive: true, force: true });
5249
+ rmSync5(tempDir, { recursive: true, force: true });
4464
5250
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4465
5251
  } finally {
4466
- rmSync4(composedDir, { recursive: true, force: true });
5252
+ rmSync5(composedDir, { recursive: true, force: true });
4467
5253
  }
4468
5254
  }
4469
5255
  async function importHistoryFromArchive(name, tempDir) {
@@ -4511,7 +5297,7 @@ function importSessionsFromArchive(dest, tempDir) {
4511
5297
  if (!existsSync9(sessionsDir)) return;
4512
5298
  try {
4513
5299
  const destSessions = resolve12(dest, ".mind/sessions");
4514
- mkdirSync6(destSessions, { recursive: true });
5300
+ mkdirSync7(destSessions, { recursive: true });
4515
5301
  for (const file of readdirSync2(sessionsDir)) {
4516
5302
  cpSync(resolve12(sessionsDir, file), resolve12(destSessions, file));
4517
5303
  }
@@ -4556,19 +5342,21 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4556
5342
  applyInitFiles(dest);
4557
5343
  const { publicKeyPem } = generateIdentity(dest);
4558
5344
  {
5345
+ const { readGlobalConfig: readGlobal } = await import("./setup-GGMKENLN.js");
5346
+ const mindDefaults = readGlobal().mindDefaults;
4559
5347
  const config2 = readVoluteConfig(dest);
4560
5348
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
4561
5349
  if (body.description) {
4562
5350
  config2.profile = { ...config2.profile, description: body.description };
4563
5351
  }
4564
5352
  if (!config2.sleep) {
4565
- config2.sleep = {
5353
+ config2.sleep = mindDefaults?.sleep ?? {
4566
5354
  enabled: true,
4567
5355
  schedule: { sleep: "0 0 * * *", wake: "0 8 * * *" }
4568
5356
  };
4569
5357
  }
4570
5358
  if (!config2.schedules || config2.schedules.length === 0) {
4571
- config2.schedules = [
5359
+ config2.schedules = mindDefaults?.schedules ?? [
4572
5360
  {
4573
5361
  id: "heartbeat",
4574
5362
  cron: "0 12,16,20 * * *",
@@ -4578,17 +5366,34 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4578
5366
  }
4579
5367
  ];
4580
5368
  }
5369
+ const cog = mindDefaults?.cognition;
5370
+ if (cog) {
5371
+ if (cog.thinkingLevel != null && !config2.thinkingLevel)
5372
+ config2.thinkingLevel = cog.thinkingLevel;
5373
+ if (cog.maxThinkingTokens != null && config2.maxThinkingTokens == null)
5374
+ config2.maxThinkingTokens = cog.maxThinkingTokens;
5375
+ if (cog.tokenBudget != null && config2.tokenBudget == null)
5376
+ config2.tokenBudget = cog.tokenBudget;
5377
+ if (cog.tokenBudgetPeriodMinutes != null && config2.tokenBudgetPeriodMinutes == null)
5378
+ config2.tokenBudgetPeriodMinutes = cog.tokenBudgetPeriodMinutes;
5379
+ }
4581
5380
  writeVoluteConfig(dest, config2);
4582
- }
4583
- if (body.model) {
4584
- const configPath = resolve12(dest, "home/.config/config.json");
4585
- const existing = existsSync9(configPath) ? JSON.parse(readFileSync10(configPath, "utf-8")) : {};
4586
- existing.model = template === "pi" ? qualifyModelId(body.model) : unqualifyModelId(body.model);
4587
- writeFileSync7(configPath, `${JSON.stringify(existing, null, 2)}
5381
+ const modelId = body.model ?? cog?.model;
5382
+ const sdkConfigPath = resolve12(dest, "home/.config/config.json");
5383
+ if (modelId || cog?.compaction) {
5384
+ const existing = existsSync9(sdkConfigPath) ? JSON.parse(readFileSync10(sdkConfigPath, "utf-8")) : {};
5385
+ if (modelId) {
5386
+ existing.model = template === "pi" ? qualifyModelId(modelId) : unqualifyModelId(modelId);
5387
+ }
5388
+ if (cog?.compaction && !existing.compaction) {
5389
+ existing.compaction = cog.compaction;
5390
+ }
5391
+ writeFileSync8(sdkConfigPath, `${JSON.stringify(existing, null, 2)}
4588
5392
  `);
5393
+ }
4589
5394
  }
4590
5395
  const mindPrompts = await getMindPromptDefaults();
4591
- writeFileSync7(
5396
+ writeFileSync8(
4592
5397
  resolve12(dest, "home/.config/prompts.json"),
4593
5398
  `${JSON.stringify(mindPrompts, null, 2)}
4594
5399
  `
@@ -4614,7 +5419,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4614
5419
  await initTemplateBranch(dest, composedDir, manifest, name, env);
4615
5420
  } catch (err) {
4616
5421
  logger_default.error(`git setup failed for ${name}`, logger_default.errorData(err));
4617
- rmSync4(resolve12(dest, ".git"), { recursive: true, force: true });
5422
+ rmSync5(resolve12(dest, ".git"), { recursive: true, force: true });
4618
5423
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4619
5424
  }
4620
5425
  try {
@@ -4629,11 +5434,11 @@ The human who planted you described you as: "${body.description}"
4629
5434
  ` : "";
4630
5435
  const seedSoulRaw = body.seedSoul ?? await getPrompt("seed_soul", { name, description: descLine });
4631
5436
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
4632
- writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
5437
+ writeFileSync8(resolve12(dest, "home/SOUL.md"), seedSoul);
4633
5438
  }
4634
5439
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4635
5440
  if (body.stage === "seed" && !body.skills) {
4636
- const { isImagegenEnabled } = await import("./setup-TISPCO22.js");
5441
+ const { isImagegenEnabled } = await import("./setup-GGMKENLN.js");
4637
5442
  if (isImagegenEnabled()) {
4638
5443
  skillSet = [...skillSet, "imagegen"];
4639
5444
  }
@@ -4651,7 +5456,7 @@ The human who planted you described you as: "${body.description}"
4651
5456
  try {
4652
5457
  const spiritEntry = await findMind("volute");
4653
5458
  if (spiritEntry) {
4654
- const { spiritDir } = await import("./spirit-VRONKFMF.js");
5459
+ const { spiritDir } = await import("./spirit-4JP4TY4C.js");
4655
5460
  const sDir = spiritEntry.dir ?? spiritDir();
4656
5461
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4657
5462
  const schedules = spiritConfig.schedules ?? [];
@@ -4666,7 +5471,7 @@ The human who planted you described you as: "${body.description}"
4666
5471
  });
4667
5472
  spiritConfig.schedules = schedules;
4668
5473
  writeVoluteConfig(sDir, spiritConfig);
4669
- const { getScheduler: getScheduler2 } = await import("./scheduler-ZZ7XGQG6.js");
5474
+ const { getScheduler: getScheduler2 } = await import("./scheduler-AGG3L2FO.js");
4670
5475
  getScheduler2().loadSchedules("volute", sDir);
4671
5476
  }
4672
5477
  }
@@ -4677,11 +5482,11 @@ The human who planted you described you as: "${body.description}"
4677
5482
  if (body.stage !== "seed") {
4678
5483
  const customSoul = await getPromptIfCustom("default_soul");
4679
5484
  if (customSoul) {
4680
- writeFileSync7(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
5485
+ writeFileSync8(resolve12(dest, "home/SOUL.md"), customSoul.replace(/\{\{name\}\}/g, name));
4681
5486
  }
4682
5487
  const customMemory = await getPromptIfCustom("default_memory");
4683
5488
  if (customMemory) {
4684
- writeFileSync7(resolve12(dest, "home/MEMORY.md"), customMemory);
5489
+ writeFileSync8(resolve12(dest, "home/MEMORY.md"), customMemory);
4685
5490
  }
4686
5491
  }
4687
5492
  publishPublicKey(name, publicKeyPem).catch(
@@ -4710,14 +5515,14 @@ The human who planted you described you as: "${body.description}"
4710
5515
  ...skillWarnings.length > 0 && { skillWarnings }
4711
5516
  });
4712
5517
  } catch (err) {
4713
- if (existsSync9(dest)) rmSync4(dest, { recursive: true, force: true });
5518
+ if (existsSync9(dest)) rmSync5(dest, { recursive: true, force: true });
4714
5519
  try {
4715
5520
  await removeMind(name);
4716
5521
  } catch {
4717
5522
  }
4718
5523
  return c.json({ error: err instanceof Error ? err.message : "Failed to create mind" }, 500);
4719
5524
  } finally {
4720
- rmSync4(composedDir, { recursive: true, force: true });
5525
+ rmSync5(composedDir, { recursive: true, force: true });
4721
5526
  }
4722
5527
  }).post("/import", requireAdmin, async (c) => {
4723
5528
  let body;
@@ -4763,17 +5568,17 @@ ${user.trimEnd()}
4763
5568
  copyTemplateToDir(composedDir, dest, name, manifest);
4764
5569
  applyInitFiles(dest);
4765
5570
  const { publicKeyPem: importPublicKey } = generateIdentity(dest);
4766
- writeFileSync7(resolve12(dest, "home/SOUL.md"), mergedSoul);
5571
+ writeFileSync8(resolve12(dest, "home/SOUL.md"), mergedSoul);
4767
5572
  const wsMemoryPath = resolve12(wsDir, "MEMORY.md");
4768
5573
  const hasMemory = existsSync9(wsMemoryPath);
4769
5574
  if (hasMemory) {
4770
5575
  const existingMemory = readFileSync10(wsMemoryPath, "utf-8");
4771
- writeFileSync7(
5576
+ writeFileSync8(
4772
5577
  resolve12(dest, "home/MEMORY.md"),
4773
5578
  `${existingMemory.trimEnd()}${mergedMemoryExtra}`
4774
5579
  );
4775
5580
  } else if (user) {
4776
- writeFileSync7(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
5581
+ writeFileSync8(resolve12(dest, "home/MEMORY.md"), `${user.trimEnd()}
4777
5582
  `);
4778
5583
  }
4779
5584
  const wsMemoryDir = resolve12(wsDir, "memory");
@@ -4813,8 +5618,8 @@ ${user.trimEnd()}
4813
5618
  } else if (template === "claude") {
4814
5619
  const sessionId = convertSession({ sessionPath: sessionFile, projectDir: dest });
4815
5620
  const mindRuntimeDir = resolve12(dest, ".mind");
4816
- mkdirSync6(mindRuntimeDir, { recursive: true });
4817
- writeFileSync7(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
5621
+ mkdirSync7(mindRuntimeDir, { recursive: true });
5622
+ writeFileSync8(resolve12(mindRuntimeDir, "session.json"), JSON.stringify({ sessionId }));
4818
5623
  }
4819
5624
  }
4820
5625
  importOpenClawConnectors(name, dest);
@@ -4829,14 +5634,14 @@ ${user.trimEnd()}
4829
5634
  );
4830
5635
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
4831
5636
  } catch (err) {
4832
- if (existsSync9(dest)) rmSync4(dest, { recursive: true, force: true });
5637
+ if (existsSync9(dest)) rmSync5(dest, { recursive: true, force: true });
4833
5638
  try {
4834
5639
  await removeMind(name);
4835
5640
  } catch {
4836
5641
  }
4837
5642
  return c.json({ error: err instanceof Error ? err.message : "Failed to import mind" }, 500);
4838
5643
  } finally {
4839
- rmSync4(composedDir, { recursive: true, force: true });
5644
+ rmSync5(composedDir, { recursive: true, force: true });
4840
5645
  }
4841
5646
  }).get("/", async (c) => {
4842
5647
  const entries = await readRegistry();
@@ -4884,6 +5689,32 @@ ${user.trimEnd()}
4884
5689
  );
4885
5690
  const hasPages = existsSync9(resolve12(mindDir(name), "home", "public", "pages"));
4886
5691
  return c.json({ ...entry, ...mindStatus, variants: variantStatuses, hasPages });
5692
+ }).get("/:name/context", async (c) => {
5693
+ const name = c.req.param("name");
5694
+ const entry = await findMind(name);
5695
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5696
+ if (!getMindManager().isRunning(name)) {
5697
+ return c.json({ error: "Mind is not running" }, 503);
5698
+ }
5699
+ try {
5700
+ const res = await fetch(`http://127.0.0.1:${entry.port}/context`, {
5701
+ signal: AbortSignal.timeout(3e3)
5702
+ });
5703
+ if (!res.ok) {
5704
+ const status = res.status >= 500 ? 502 : 404;
5705
+ return c.json(
5706
+ {
5707
+ error: res.status >= 500 ? "Mind context handler errored" : "Context endpoint not available"
5708
+ },
5709
+ status
5710
+ );
5711
+ }
5712
+ const data = await res.json();
5713
+ return c.json(data);
5714
+ } catch (err) {
5715
+ console.error(`context proxy for ${name}:`, err);
5716
+ return c.json({ error: "Failed to reach mind" }, 503);
5717
+ }
4887
5718
  }).post("/:name/start", requireSelf(), async (c) => {
4888
5719
  const name = c.req.param("name");
4889
5720
  const entry = await findMind(name);
@@ -4929,7 +5760,7 @@ ${user.trimEnd()}
4929
5760
  const manager = getMindManager();
4930
5761
  try {
4931
5762
  if (context?.type === "reload") {
4932
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
5763
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
4933
5764
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4934
5765
  if (sleepState?.sleeping) {
4935
5766
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -5025,7 +5856,7 @@ ${user.trimEnd()}
5025
5856
  const name = c.req.param("name");
5026
5857
  const entry = await findMind(name);
5027
5858
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5028
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
5859
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
5029
5860
  const sm = getSleepManagerIfReady2();
5030
5861
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5031
5862
  return c.json(sm.getState(name));
@@ -5033,7 +5864,7 @@ ${user.trimEnd()}
5033
5864
  const name = c.req.param("name");
5034
5865
  const entry = await findMind(name);
5035
5866
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5036
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
5867
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
5037
5868
  const sm = getSleepManagerIfReady2();
5038
5869
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5039
5870
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -5053,7 +5884,7 @@ ${user.trimEnd()}
5053
5884
  const name = c.req.param("name");
5054
5885
  const entry = await findMind(name);
5055
5886
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5056
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
5887
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
5057
5888
  const sm = getSleepManagerIfReady2();
5058
5889
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5059
5890
  const sleepState = sm.getState(name);
@@ -5068,7 +5899,7 @@ ${user.trimEnd()}
5068
5899
  const name = c.req.param("name");
5069
5900
  const entry = await findMind(name);
5070
5901
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5071
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
5902
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
5072
5903
  const sm = getSleepManagerIfReady2();
5073
5904
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
5074
5905
  const flushed = await sm.flushQueuedMessages(name);
@@ -5086,8 +5917,8 @@ ${user.trimEnd()}
5086
5917
  if (body.avatar !== void 0) profile.avatar = body.avatar;
5087
5918
  config2.profile = profile;
5088
5919
  writeVoluteConfig(dir, config2);
5089
- const { syncMindProfile } = await import("./auth-GKCDSO4T.js");
5090
- await syncMindProfile(name, profile);
5920
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-ZFZXJZDQ.js");
5921
+ await syncMindProfile2(name, profile);
5091
5922
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5092
5923
  return c.json({ ok: true });
5093
5924
  }).get("/:name/seed-check", requireSelf(), async (c) => {
@@ -5133,7 +5964,7 @@ ${user.trimEnd()}
5133
5964
  const config2 = readVoluteConfig(dir);
5134
5965
  const displayNameSet = !!config2?.profile?.displayName;
5135
5966
  const avatarSet = !!config2?.profile?.avatar;
5136
- const { isImagegenEnabled } = await import("./setup-TISPCO22.js");
5967
+ const { isImagegenEnabled } = await import("./setup-GGMKENLN.js");
5137
5968
  const imagegenEnabled = isImagegenEnabled();
5138
5969
  const done = [];
5139
5970
  const remaining = [];
@@ -5171,7 +6002,7 @@ ${user.trimEnd()}
5171
6002
  try {
5172
6003
  const spiritEntry = await findMind("volute");
5173
6004
  if (spiritEntry) {
5174
- const { spiritDir } = await import("./spirit-VRONKFMF.js");
6005
+ const { spiritDir } = await import("./spirit-4JP4TY4C.js");
5175
6006
  const sDir = spiritEntry.dir ?? spiritDir();
5176
6007
  const spiritConfig = readVoluteConfig(sDir);
5177
6008
  if (spiritConfig?.schedules) {
@@ -5179,7 +6010,7 @@ ${user.trimEnd()}
5179
6010
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5180
6011
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5181
6012
  writeVoluteConfig(sDir, spiritConfig);
5182
- const { getScheduler: getScheduler2 } = await import("./scheduler-ZZ7XGQG6.js");
6013
+ const { getScheduler: getScheduler2 } = await import("./scheduler-AGG3L2FO.js");
5183
6014
  getScheduler2().loadSchedules("volute", sDir);
5184
6015
  }
5185
6016
  }
@@ -5212,10 +6043,10 @@ ${user.trimEnd()}
5212
6043
  await deleteMindUser2(name);
5213
6044
  const state = stateDir(name);
5214
6045
  if (existsSync9(state)) {
5215
- rmSync4(state, { recursive: true, force: true });
6046
+ rmSync5(state, { recursive: true, force: true });
5216
6047
  }
5217
6048
  if (force && existsSync9(dir)) {
5218
- rmSync4(dir, { recursive: true, force: true });
6049
+ rmSync5(dir, { recursive: true, force: true });
5219
6050
  deleteMindUser(name);
5220
6051
  }
5221
6052
  fireWebhook({
@@ -5363,7 +6194,7 @@ ${user.trimEnd()}
5363
6194
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
5364
6195
  chownMindDir(dir, mindName);
5365
6196
  } catch (err) {
5366
- rmSync4(resolve12(dir, ".git"), { recursive: true, force: true });
6197
+ rmSync5(resolve12(dir, ".git"), { recursive: true, force: true });
5367
6198
  return c.json(
5368
6199
  {
5369
6200
  error: `Git initialization failed: ${err instanceof Error ? err.message : String(err)}`
@@ -5390,7 +6221,7 @@ ${user.trimEnd()}
5390
6221
  await updateTemplateBranch(dir, template, mindName);
5391
6222
  const parentDir = resolve12(dir, ".variants");
5392
6223
  if (!existsSync9(parentDir)) {
5393
- mkdirSync6(parentDir, { recursive: true });
6224
+ mkdirSync7(parentDir, { recursive: true });
5394
6225
  }
5395
6226
  await gitExec(["worktree", "add", "-b", UPGRADE_BRANCH, worktreeDir], { cwd: dir });
5396
6227
  await gitExec(["rm", "-r", "--cached", "--ignore-unmatch", "home/"], {
@@ -5517,7 +6348,7 @@ ${user.trimEnd()}
5517
6348
  const name = c.req.param("name");
5518
6349
  const entry = await findMind(name);
5519
6350
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5520
- const dir = mindDir(name);
6351
+ const dir = entry.dir ?? mindDir(name);
5521
6352
  if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
5522
6353
  let config2 = readVoluteConfig(dir);
5523
6354
  if (!config2 && entry.template === "pi") {
@@ -5529,6 +6360,14 @@ ${user.trimEnd()}
5529
6360
  }
5530
6361
  }
5531
6362
  }
6363
+ let templateConfig = {};
6364
+ const configJsonPath = resolve12(dir, "home/.config/config.json");
6365
+ if (existsSync9(configJsonPath)) {
6366
+ try {
6367
+ templateConfig = JSON.parse(readFileSync10(configJsonPath, "utf-8"));
6368
+ } catch {
6369
+ }
6370
+ }
5532
6371
  return c.json({
5533
6372
  registry: {
5534
6373
  name: entry.name,
@@ -5538,10 +6377,26 @@ ${user.trimEnd()}
5538
6377
  template: entry.template
5539
6378
  },
5540
6379
  config: {
5541
- model: config2?.model ?? null,
5542
- thinkingLevel: config2?.thinkingLevel ?? null,
6380
+ model: config2?.model ?? templateConfig.model ?? null,
6381
+ thinkingLevel: (() => {
6382
+ if (config2?.thinkingLevel) return config2.thinkingLevel;
6383
+ const tc = templateConfig;
6384
+ if (tc.thinkingLevel) return tc.thinkingLevel;
6385
+ if (tc.reasoningEffort) return tc.reasoningEffort;
6386
+ const mtt = tc.maxThinkingTokens;
6387
+ if (mtt) {
6388
+ if (mtt <= 1024) return "minimal";
6389
+ if (mtt <= 4096) return "low";
6390
+ if (mtt <= 1e4) return "medium";
6391
+ if (mtt <= 32e3) return "high";
6392
+ return "xhigh";
6393
+ }
6394
+ return null;
6395
+ })(),
6396
+ maxThinkingTokens: config2?.maxThinkingTokens ?? templateConfig.maxThinkingTokens ?? null,
5543
6397
  tokenBudget: config2?.tokenBudget ?? null,
5544
- tokenBudgetPeriodMinutes: config2?.tokenBudgetPeriodMinutes ?? null
6398
+ tokenBudgetPeriodMinutes: config2?.tokenBudgetPeriodMinutes ?? null,
6399
+ compaction: templateConfig.compaction ?? null
5545
6400
  }
5546
6401
  });
5547
6402
  }).put(
@@ -5552,15 +6407,17 @@ ${user.trimEnd()}
5552
6407
  z5.object({
5553
6408
  model: z5.string().optional(),
5554
6409
  thinkingLevel: z5.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
6410
+ maxThinkingTokens: z5.number().int().positive().nullable().optional(),
5555
6411
  tokenBudget: z5.number().int().positive().nullable().optional(),
5556
- tokenBudgetPeriodMinutes: z5.number().int().positive().nullable().optional()
6412
+ tokenBudgetPeriodMinutes: z5.number().int().positive().nullable().optional(),
6413
+ compaction: z5.object({ maxContextTokens: z5.number().int().positive().nullable().optional() }).nullable().optional()
5557
6414
  })
5558
6415
  ),
5559
6416
  async (c) => {
5560
6417
  const name = c.req.param("name");
5561
6418
  const entry = await findMind(name);
5562
6419
  if (!entry) return c.json({ error: "Mind not found" }, 404);
5563
- const dir = mindDir(name);
6420
+ const dir = entry.dir ?? mindDir(name);
5564
6421
  if (!existsSync9(dir)) return c.json({ error: "Mind directory missing" }, 404);
5565
6422
  const body = c.req.valid("json");
5566
6423
  const existing = readVoluteConfig(dir) ?? {};
@@ -5582,7 +6439,96 @@ ${user.trimEnd()}
5582
6439
  existing.tokenBudgetPeriodMinutes = body.tokenBudgetPeriodMinutes;
5583
6440
  }
5584
6441
  }
6442
+ if (body.maxThinkingTokens !== void 0) {
6443
+ if (body.maxThinkingTokens === null) {
6444
+ delete existing.maxThinkingTokens;
6445
+ } else {
6446
+ existing.maxThinkingTokens = body.maxThinkingTokens;
6447
+ }
6448
+ }
5585
6449
  writeVoluteConfig(dir, existing);
6450
+ const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
6451
+ if (needsConfigJson) {
6452
+ const configJsonPath = resolve12(dir, "home/.config/config.json");
6453
+ let templateConfig = {};
6454
+ if (existsSync9(configJsonPath)) {
6455
+ try {
6456
+ templateConfig = JSON.parse(readFileSync10(configJsonPath, "utf-8"));
6457
+ } catch {
6458
+ }
6459
+ }
6460
+ if (body.model !== void 0) {
6461
+ templateConfig.model = body.model;
6462
+ }
6463
+ const tmpl = entry.template ?? "claude";
6464
+ if (body.thinkingLevel !== void 0) {
6465
+ if (tmpl === "claude") {
6466
+ const claudeThinkingTokens = {
6467
+ off: null,
6468
+ minimal: 1024,
6469
+ low: 4096,
6470
+ medium: 1e4,
6471
+ high: 32e3,
6472
+ xhigh: 128e3
6473
+ };
6474
+ const tokens = claudeThinkingTokens[body.thinkingLevel] ?? null;
6475
+ if (tokens === null) {
6476
+ delete templateConfig.maxThinkingTokens;
6477
+ } else {
6478
+ templateConfig.maxThinkingTokens = tokens;
6479
+ }
6480
+ } else if (tmpl === "codex") {
6481
+ const codexMap = {
6482
+ off: null,
6483
+ minimal: "minimal",
6484
+ low: "low",
6485
+ medium: "medium",
6486
+ high: "high",
6487
+ xhigh: "xhigh"
6488
+ };
6489
+ const effort = codexMap[body.thinkingLevel] ?? null;
6490
+ if (effort === null) {
6491
+ delete templateConfig.reasoningEffort;
6492
+ } else {
6493
+ templateConfig.reasoningEffort = effort;
6494
+ }
6495
+ } else {
6496
+ templateConfig.thinkingLevel = body.thinkingLevel;
6497
+ }
6498
+ }
6499
+ if (body.maxThinkingTokens !== void 0) {
6500
+ if (body.maxThinkingTokens === null) {
6501
+ delete templateConfig.maxThinkingTokens;
6502
+ } else {
6503
+ templateConfig.maxThinkingTokens = body.maxThinkingTokens;
6504
+ }
6505
+ }
6506
+ if (body.compaction !== void 0) {
6507
+ if (body.compaction === null) {
6508
+ delete templateConfig.compaction;
6509
+ } else {
6510
+ const comp = templateConfig.compaction ?? {};
6511
+ if (body.compaction.maxContextTokens === null) {
6512
+ delete comp.maxContextTokens;
6513
+ } else if (body.compaction.maxContextTokens !== void 0) {
6514
+ comp.maxContextTokens = body.compaction.maxContextTokens;
6515
+ }
6516
+ templateConfig.compaction = comp;
6517
+ }
6518
+ }
6519
+ writeFileSync8(configJsonPath, `${JSON.stringify(templateConfig, null, 2)}
6520
+ `);
6521
+ }
6522
+ if (entry.mindType === "spirit" && body.model !== void 0) {
6523
+ try {
6524
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-GGMKENLN.js");
6525
+ const globalConfig = readGlobalConfig2();
6526
+ globalConfig.spiritModel = body.model;
6527
+ writeGlobalConfig2(globalConfig);
6528
+ } catch (err) {
6529
+ logger_default.warn("failed to sync spirit model to global config", logger_default.errorData(err));
6530
+ }
6531
+ }
5586
6532
  return c.json({ ok: true });
5587
6533
  }
5588
6534
  ).get("/:name/delivery/pending", async (c) => {
@@ -5603,7 +6549,7 @@ ${user.trimEnd()}
5603
6549
  if (!body.systemPrompt || !body.message) {
5604
6550
  return c.json({ error: "systemPrompt and message required" }, 400);
5605
6551
  }
5606
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-SBY2WG7O.js");
6552
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-PSILB5WD.js");
5607
6553
  if (!isAiConfigured()) {
5608
6554
  return c.json({ error: "AI service not configured" }, 503);
5609
6555
  }
@@ -5905,18 +6851,18 @@ ${user.trimEnd()}
5905
6851
  sinceTimestamp = new Date(Date.now() - 36e5).toISOString().replace("T", " ").slice(0, 19);
5906
6852
  }
5907
6853
  const conditions = [
5908
- eq6(mindHistory.mind, name),
5909
- eq6(mindHistory.type, "summary"),
5910
- sql3`${mindHistory.created_at} > ${sinceTimestamp}`
6854
+ eq6(summaries.mind, name),
6855
+ eq6(summaries.period, "turn"),
6856
+ sql3`${summaries.created_at} > ${sinceTimestamp}`
5911
6857
  ];
5912
6858
  if (currentSession) {
5913
- conditions.push(sql3`${mindHistory.session} != ${currentSession}`);
6859
+ conditions.push(sql3`${turns.session} != ${currentSession}`);
5914
6860
  }
5915
6861
  const rows = await db.select({
5916
- session: mindHistory.session,
5917
- content: mindHistory.content,
5918
- created_at: mindHistory.created_at
5919
- }).from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(50);
6862
+ session: turns.session,
6863
+ content: summaries.content,
6864
+ created_at: summaries.created_at
6865
+ }).from(summaries).innerJoin(turns, eq6(turns.id, summaries.period_key)).where(and5(...conditions)).orderBy(desc4(summaries.created_at)).limit(50);
5920
6866
  if (rows.length === 0) {
5921
6867
  return c.json({ context: null });
5922
6868
  }
@@ -5944,20 +6890,64 @@ ${lines.join("\n")}` });
5944
6890
  conditions.push(eq6(mindHistory.session, session));
5945
6891
  }
5946
6892
  const effectivePreset = full ? "all" : preset;
6893
+ if (!effectivePreset || effectivePreset === "summary") {
6894
+ const sumConditions = [eq6(summaries.mind, name), eq6(summaries.period, "turn")];
6895
+ if (session) {
6896
+ sumConditions.push(eq6(turns.session, session));
6897
+ const sumRows2 = await db.select({
6898
+ id: summaries.id,
6899
+ mind: summaries.mind,
6900
+ period: summaries.period,
6901
+ period_key: summaries.period_key,
6902
+ content: summaries.content,
6903
+ metadata: summaries.metadata,
6904
+ created_at: summaries.created_at,
6905
+ session: turns.session
6906
+ }).from(summaries).innerJoin(turns, eq6(turns.id, summaries.period_key)).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
6907
+ return c.json(
6908
+ sumRows2.map((r) => ({
6909
+ id: r.id,
6910
+ mind: r.mind,
6911
+ type: "summary",
6912
+ channel: null,
6913
+ session: r.session,
6914
+ sender: null,
6915
+ message_id: null,
6916
+ content: r.content,
6917
+ metadata: r.metadata,
6918
+ turn_id: r.period_key,
6919
+ created_at: r.created_at
6920
+ }))
6921
+ );
6922
+ }
6923
+ const sumRows = await db.select().from(summaries).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
6924
+ return c.json(
6925
+ sumRows.map((r) => ({
6926
+ id: r.id,
6927
+ mind: r.mind,
6928
+ type: "summary",
6929
+ channel: null,
6930
+ session: null,
6931
+ sender: null,
6932
+ message_id: null,
6933
+ content: r.content,
6934
+ metadata: r.metadata,
6935
+ turn_id: r.period_key,
6936
+ created_at: r.created_at
6937
+ }))
6938
+ );
6939
+ }
5947
6940
  switch (effectivePreset) {
5948
6941
  case "all":
5949
6942
  break;
5950
6943
  case "conversation":
5951
- conditions.push(sql3`${mindHistory.type} IN ('summary','inbound','outbound','tool_use')`);
6944
+ conditions.push(sql3`${mindHistory.type} IN ('inbound','outbound','tool_use')`);
5952
6945
  break;
5953
6946
  case "detailed":
5954
6947
  conditions.push(
5955
- sql3`${mindHistory.type} IN ('summary','inbound','outbound','tool_use','tool_result','text','thinking')`
6948
+ sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
5956
6949
  );
5957
6950
  break;
5958
- default:
5959
- conditions.push(sql3`${mindHistory.type} IN ('summary')`);
5960
- break;
5961
6951
  }
5962
6952
  const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(limit).offset(offset);
5963
6953
  return c.json(rows);
@@ -6132,23 +7122,20 @@ var app16 = new Hono18().get("/:name/clock/status", async (c) => {
6132
7122
  const sleepConfig = sleepManager?.getSleepConfig(name) ?? null;
6133
7123
  const schedules = readSchedules(name);
6134
7124
  const now = /* @__PURE__ */ new Date();
6135
- const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
6136
7125
  const upcoming = [];
6137
7126
  const previous = [];
6138
7127
  for (const s of schedules) {
6139
7128
  if (!s.enabled) continue;
6140
7129
  if (s.fireAt) {
6141
7130
  const fireDate = new Date(s.fireAt);
6142
- if (fireDate >= now && fireDate <= in24h) {
7131
+ if (fireDate >= now) {
6143
7132
  upcoming.push({ id: s.id, at: fireDate.toISOString(), type: "timer" });
6144
7133
  }
6145
7134
  } else if (s.cron) {
6146
7135
  try {
6147
7136
  const interval = CronExpressionParser.parse(s.cron);
6148
7137
  const next = interval.next().toDate();
6149
- if (next <= in24h) {
6150
- upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
6151
- }
7138
+ upcoming.push({ id: s.id, at: next.toISOString(), type: "cron" });
6152
7139
  } catch {
6153
7140
  slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
6154
7141
  }
@@ -6160,9 +7147,61 @@ var app16 = new Hono18().get("/:name/clock/status", async (c) => {
6160
7147
  }
6161
7148
  }
6162
7149
  }
7150
+ if (sleepState?.sleeping) {
7151
+ if (sleepState.scheduledWakeAt) {
7152
+ upcoming.push({ id: "sleep", at: sleepState.scheduledWakeAt, type: "cron" });
7153
+ }
7154
+ if (sleepState.sleepingSince) {
7155
+ previous.push({ id: "sleep", at: sleepState.sleepingSince });
7156
+ }
7157
+ } else if (sleepConfig?.enabled && sleepConfig.schedule) {
7158
+ try {
7159
+ const sleepInterval = CronExpressionParser.parse(sleepConfig.schedule.sleep);
7160
+ const nextSleep = sleepInterval.next().toDate();
7161
+ upcoming.push({ id: "sleep", at: nextSleep.toISOString(), type: "cron" });
7162
+ } catch {
7163
+ }
7164
+ try {
7165
+ const prevWakeInterval = CronExpressionParser.parse(sleepConfig.schedule.wake);
7166
+ const prevWake = prevWakeInterval.prev().toDate();
7167
+ previous.push({ id: "sleep", at: prevWake.toISOString() });
7168
+ } catch {
7169
+ }
7170
+ }
6163
7171
  upcoming.sort((a, b) => a.at.localeCompare(b.at));
6164
7172
  previous.sort((a, b) => b.at.localeCompare(a.at));
6165
7173
  return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
7174
+ }).get("/:name/sleep/config", async (c) => {
7175
+ const name = c.req.param("name");
7176
+ if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
7177
+ const config2 = readVoluteConfig(mindDir(name));
7178
+ return c.json(config2?.sleep ?? { enabled: false });
7179
+ }).put("/:name/sleep/config", requireSelf(), async (c) => {
7180
+ const name = c.req.param("name");
7181
+ const entry = await findMind(name);
7182
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
7183
+ const body = await c.req.json();
7184
+ if (body.schedule) {
7185
+ for (const field of ["sleep", "wake"]) {
7186
+ if (body.schedule[field]) {
7187
+ try {
7188
+ CronExpressionParser.parse(body.schedule[field]);
7189
+ } catch {
7190
+ return c.json({ error: `Invalid ${field} cron: ${body.schedule[field]}` }, 400);
7191
+ }
7192
+ }
7193
+ }
7194
+ }
7195
+ const dir = mindDir(name);
7196
+ const config2 = readVoluteConfig(dir) ?? {};
7197
+ const sleep = config2.sleep ?? {};
7198
+ if (body.enabled !== void 0) sleep.enabled = body.enabled;
7199
+ if (body.schedule !== void 0) sleep.schedule = body.schedule;
7200
+ if (body.wakeTriggers !== void 0) sleep.wakeTriggers = body.wakeTriggers;
7201
+ config2.sleep = sleep;
7202
+ writeVoluteConfig(dir, config2);
7203
+ getSleepManagerIfReady()?.invalidateSleepConfig(name);
7204
+ return c.json({ ok: true });
6166
7205
  }).get("/:name/schedules", async (c) => {
6167
7206
  const name = c.req.param("name");
6168
7207
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
@@ -6290,7 +7329,7 @@ var app16 = new Hono18().get("/:name/clock/status", async (c) => {
6290
7329
  const body = await c.req.text();
6291
7330
  const message = `[webhook: ${event}] ${body}`;
6292
7331
  try {
6293
- const { sendSystemMessage } = await import("./system-chat-JAPOJ3KE.js");
7332
+ const { sendSystemMessage } = await import("./system-chat-TYLOL7SX.js");
6294
7333
  await sendSystemMessage(name, message);
6295
7334
  return c.json({ ok: true });
6296
7335
  } catch (err) {
@@ -6301,7 +7340,7 @@ var app16 = new Hono18().get("/:name/clock/status", async (c) => {
6301
7340
  var schedules_default = app16;
6302
7341
 
6303
7342
  // src/web/api/setup.ts
6304
- import { mkdirSync as mkdirSync7 } from "fs";
7343
+ import { mkdirSync as mkdirSync8 } from "fs";
6305
7344
  import { homedir as homedir2 } from "os";
6306
7345
  import { resolve as resolve14 } from "path";
6307
7346
  import { Hono as Hono19 } from "hono";
@@ -6311,8 +7350,8 @@ var setup = new Hono19();
6311
7350
  function writeSetupConfig(systemName, description) {
6312
7351
  const configHome = process.env.VOLUTE_HOME ?? resolve14(homedir2(), ".volute");
6313
7352
  const mindsDir = resolve14(configHome, "minds");
6314
- mkdirSync7(configHome, { recursive: true });
6315
- mkdirSync7(mindsDir, { recursive: true });
7353
+ mkdirSync8(configHome, { recursive: true });
7354
+ mkdirSync8(mindsDir, { recursive: true });
6316
7355
  const existingConfig = readGlobalConfig();
6317
7356
  const setupConfig = {
6318
7357
  type: "local",
@@ -6343,7 +7382,7 @@ setup.get("/status", async (c) => {
6343
7382
  let hasAccount = false;
6344
7383
  if (hasSystem) {
6345
7384
  try {
6346
- const { listUsersByType: listUsersByType2 } = await import("./auth-GKCDSO4T.js");
7385
+ const { listUsersByType: listUsersByType2 } = await import("./auth-ZFZXJZDQ.js");
6347
7386
  const brains = await listUsersByType2("brain");
6348
7387
  hasAccount = brains.length > 0;
6349
7388
  } catch (err) {
@@ -6528,7 +7567,7 @@ setup.post("/account", async (c) => {
6528
7567
  }
6529
7568
  }
6530
7569
  try {
6531
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-GKCDSO4T.js");
7570
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-ZFZXJZDQ.js");
6532
7571
  const user = await createUser2(body.username.trim(), body.password);
6533
7572
  if (body.displayName?.trim()) {
6534
7573
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6574,7 +7613,7 @@ setup.post("/models", async (c) => {
6574
7613
  return c.json({ error: "Spirit model is required" }, 400);
6575
7614
  }
6576
7615
  try {
6577
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-SBY2WG7O.js");
7616
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-PSILB5WD.js");
6578
7617
  setEnabledModels3(body.models);
6579
7618
  const config2 = readGlobalConfig();
6580
7619
  config2.spiritModel = body.spiritModel.trim();
@@ -6592,8 +7631,8 @@ setup.post("/complete", async (c) => {
6592
7631
  return c.json({ error: "Setup already complete" }, 400);
6593
7632
  }
6594
7633
  try {
6595
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-VRONKFMF.js");
6596
- const { startSpiritFull } = await import("./mind-service-2MQ6UK5N.js");
7634
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-4JP4TY4C.js");
7635
+ const { startSpiritFull } = await import("./mind-service-VIKZJK2M.js");
6597
7636
  await ensureSpiritProject();
6598
7637
  await syncSpiritTemplate();
6599
7638
  const warnings = [];
@@ -6609,8 +7648,8 @@ setup.post("/complete", async (c) => {
6609
7648
  }
6610
7649
  let spiritConversationId;
6611
7650
  try {
6612
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-GKCDSO4T.js");
6613
- const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-AWI5SZW2.js");
7651
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-ZFZXJZDQ.js");
7652
+ const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-HL2JP5GI.js");
6614
7653
  const spiritUser = await getOrCreateMindUser2("volute");
6615
7654
  const brains = await listUsersByType2("brain");
6616
7655
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6633,8 +7672,8 @@ setup.post("/complete", async (c) => {
6633
7672
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6634
7673
  if (spiritConversationId && spiritStarted) {
6635
7674
  try {
6636
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-DFF5SJRM.js");
6637
- const { listUsersByType: listUsers6 } = await import("./auth-GKCDSO4T.js");
7675
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-V3R6NXJP.js");
7676
+ const { listUsersByType: listUsers6 } = await import("./auth-ZFZXJZDQ.js");
6638
7677
  const admins = await listUsers6("brain");
6639
7678
  const admin2 = admins.find((u) => u.role === "admin");
6640
7679
  const adminName = admin2?.display_name || admin2?.username || "the admin";
@@ -6693,7 +7732,7 @@ var app17 = new Hono20().post("/:name/shared/merge", requireAdmin, async (c) =>
6693
7732
  var shared_default = app17;
6694
7733
 
6695
7734
  // src/web/api/skills.ts
6696
- import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync5 } from "fs";
7735
+ import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync6 } from "fs";
6697
7736
  import { tmpdir } from "os";
6698
7737
  import { join, resolve as resolve15 } from "path";
6699
7738
  import AdmZip from "adm-zip";
@@ -6747,6 +7786,16 @@ var app18 = new Hono21().get("/", async (c) => {
6747
7786
  removed.add(skill);
6748
7787
  writeGlobalConfig({ ...config2, defaultSkills: updated, removedDefaultSkills: [...removed] });
6749
7788
  return c.json({ skills: updated });
7789
+ }).get("/auto-update", (c) => {
7790
+ return c.json({ enabled: isAutoUpdateSkillsEnabled() });
7791
+ }).put("/auto-update", requireAdmin, async (c) => {
7792
+ const body = await c.req.json();
7793
+ if (typeof body.enabled !== "boolean") {
7794
+ return c.json({ error: "body.enabled must be a boolean" }, 400);
7795
+ }
7796
+ const config2 = readGlobalConfig();
7797
+ writeGlobalConfig({ ...config2, autoUpdateSkills: body.enabled });
7798
+ return c.json({ enabled: body.enabled });
6750
7799
  }).post("/upload", requireAdmin, async (c) => {
6751
7800
  const body = await c.req.parseBody();
6752
7801
  const file = body.file;
@@ -6790,7 +7839,7 @@ var app18 = new Hono21().get("/", async (c) => {
6790
7839
  }
6791
7840
  throw e;
6792
7841
  } finally {
6793
- rmSync5(tmpDir, { recursive: true, force: true });
7842
+ rmSync6(tmpDir, { recursive: true, force: true });
6794
7843
  }
6795
7844
  }).get("/:id", async (c) => {
6796
7845
  const id = c.req.param("id");
@@ -7113,13 +8162,13 @@ var app22 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
7113
8162
  var events_default = app22;
7114
8163
 
7115
8164
  // src/web/api/variants.ts
7116
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
8165
+ import { existsSync as existsSync11, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
7117
8166
  import { resolve as resolve17 } from "path";
7118
8167
  import { Hono as Hono26 } from "hono";
7119
8168
 
7120
8169
  // src/lib/spawn-server.ts
7121
8170
  import { spawn as spawn4 } from "child_process";
7122
- import { closeSync, mkdirSync as mkdirSync8, openSync, readFileSync as readFileSync11 } from "fs";
8171
+ import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync11 } from "fs";
7123
8172
  import { resolve as resolve16 } from "path";
7124
8173
  function tsxBin(cwd) {
7125
8174
  return resolve16(cwd, "node_modules", ".bin", "tsx");
@@ -7158,7 +8207,7 @@ function spawnAttached(cwd, port) {
7158
8207
  }
7159
8208
  function spawnDetached(cwd, port, logDir) {
7160
8209
  const logsDir = logDir ?? resolve16(cwd, ".mind", "logs");
7161
- mkdirSync8(logsDir, { recursive: true });
8210
+ mkdirSync9(logsDir, { recursive: true });
7162
8211
  const logPath = resolve16(logsDir, "mind.log");
7163
8212
  const logFd = openSync(logPath, "a");
7164
8213
  const child = spawn4(tsxBin(cwd), ["src/server.ts", "--port", String(port)], {
@@ -7279,7 +8328,7 @@ var app23 = new Hono26().get("/:name/variants", async (c) => {
7279
8328
  if (existsSync11(variantDir)) {
7280
8329
  return c.json({ error: `Variant directory already exists: ${variantDir}` }, 409);
7281
8330
  }
7282
- mkdirSync9(resolve17(projectRoot, ".variants"), { recursive: true });
8331
+ mkdirSync10(resolve17(projectRoot, ".variants"), { recursive: true });
7283
8332
  try {
7284
8333
  await gitExec(["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
7285
8334
  } catch (e) {
@@ -7302,7 +8351,7 @@ var app23 = new Hono26().get("/:name/variants", async (c) => {
7302
8351
  return c.json({ error: `npm install failed: ${msg}` }, 500);
7303
8352
  }
7304
8353
  if (body.soul) {
7305
- writeFileSync8(resolve17(variantDir, "home/SOUL.md"), body.soul);
8354
+ writeFileSync9(resolve17(variantDir, "home/SOUL.md"), body.soul);
7306
8355
  }
7307
8356
  const variantPort = body.port ?? await nextPort();
7308
8357
  await addVariant(variantName, mindName, variantPort, variantDir, variantName);
@@ -7397,8 +8446,8 @@ var app23 = new Hono26().get("/:name/variants", async (c) => {
7397
8446
  await cleanupVariant(variantName, projectRoot, variantEntry.dir);
7398
8447
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7399
8448
  try {
7400
- const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-A6VVKOXJ.js");
7401
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-PJ4S5PHQ.js");
8449
+ const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-EJRTKE36.js");
8450
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-UYV5S6QT.js");
7402
8451
  const tmpl = parentEntry.template ?? "claude";
7403
8452
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
7404
8453
  } catch (err) {
@@ -7622,8 +8671,8 @@ async function fanOutToMinds(opts) {
7622
8671
  );
7623
8672
  const participantNames = participants.map((p) => p.username);
7624
8673
  const isDM = opts.isDM ?? participants.length === 2;
7625
- const { getMindManager: getMindManager2 } = await import("./mind-manager-NBJF5D26.js");
7626
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
8674
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-MWW3BTS4.js");
8675
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BJK2ROPX.js");
7627
8676
  const manager = getMindManager2();
7628
8677
  const sm = getSleepManagerIfReady2();
7629
8678
  const targetMinds = mindParticipants.map((ap) => {
@@ -8188,25 +9237,25 @@ async function startDaemon(opts) {
8188
9237
  }
8189
9238
  const DAEMON_PID_PATH = resolve19(systemDir, "daemon.pid");
8190
9239
  const DAEMON_JSON_PATH = resolve19(systemDir, "daemon.json");
8191
- mkdirSync10(home, { recursive: true });
9240
+ mkdirSync11(home, { recursive: true });
8192
9241
  ensureSystemDir();
8193
9242
  try {
8194
9243
  await ensureSharedRepo();
8195
9244
  } catch (err) {
8196
9245
  logger_default.warn("failed to initialize shared repo", logger_default.errorData(err));
8197
9246
  }
8198
- const { migrateSetupCompleted } = await import("./setup-TISPCO22.js");
9247
+ const { migrateSetupCompleted } = await import("./setup-GGMKENLN.js");
8199
9248
  migrateSetupCompleted();
8200
- await (await import("./db-F34YLV7D.js")).getDb();
9249
+ await (await import("./db-RYX3SS2W.js")).getDb();
8201
9250
  try {
8202
9251
  const { eq: eq8, and: and6 } = await import("drizzle-orm");
8203
- const { users: users2 } = await import("./schema-PA3M5ZKH.js");
8204
- const db = await (await import("./db-F34YLV7D.js")).getDb();
9252
+ const { users: users2 } = await import("./schema-ETMABTW4.js");
9253
+ const db = await (await import("./db-RYX3SS2W.js")).getDb();
8205
9254
  await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
8206
9255
  } catch (err) {
8207
9256
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8208
9257
  }
8209
- const { initSandbox } = await import("./sandbox-GJOK4QLQ.js");
9258
+ const { initSandbox } = await import("./sandbox-SI5HMBP3.js");
8210
9259
  await initSandbox();
8211
9260
  try {
8212
9261
  await syncBuiltinSkills();
@@ -8220,13 +9269,20 @@ async function startDaemon(opts) {
8220
9269
  logger_default.error("failed to load extensions", logger_default.errorData(err));
8221
9270
  }
8222
9271
  await initDefaultSkills();
9272
+ if (isAutoUpdateSkillsEnabled()) {
9273
+ try {
9274
+ await autoUpdateMindSkills();
9275
+ } catch (err) {
9276
+ logger_default.error("failed to auto-update mind skills", logger_default.errorData(err));
9277
+ }
9278
+ }
8223
9279
  try {
8224
9280
  await ensureSystemChannel();
8225
9281
  } catch (err) {
8226
9282
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8227
9283
  }
8228
9284
  try {
8229
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-GKCDSO4T.js");
9285
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-ZFZXJZDQ.js");
8230
9286
  await getOrCreateSystemUser2();
8231
9287
  } catch (err) {
8232
9288
  logger_default.warn(
@@ -8237,7 +9293,7 @@ async function startDaemon(opts) {
8237
9293
  const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
8238
9294
  let tls;
8239
9295
  if (opts.tailscale) {
8240
- const { getTailscaleTls } = await import("./tailscale-XHQBZROW.js");
9296
+ const { getTailscaleTls } = await import("./tailscale-ZEUK7GKZ.js");
8241
9297
  const tlsConfig = await getTailscaleTls();
8242
9298
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
8243
9299
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -8258,11 +9314,11 @@ async function startDaemon(opts) {
8258
9314
  process.env.VOLUTE_DAEMON_TOKEN = token;
8259
9315
  process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
8260
9316
  process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
8261
- writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
9317
+ writeFileSync10(DAEMON_PID_PATH, myPid, { mode: 420 });
8262
9318
  const daemonConfig = { port, hostname, token };
8263
9319
  if (internalPort) daemonConfig.internalPort = internalPort;
8264
9320
  if (tls) daemonConfig.tls = true;
8265
- writeFileSync9(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
9321
+ writeFileSync10(DAEMON_JSON_PATH, `${JSON.stringify(daemonConfig, null, 2)}
8266
9322
  `, { mode: 420 });
8267
9323
  const delivery = initDeliveryManager();
8268
9324
  const manager = initMindManager();
@@ -8276,6 +9332,8 @@ async function startDaemon(opts) {
8276
9332
  tokenBudget.start();
8277
9333
  const sleepManager = initSleepManager();
8278
9334
  sleepManager.start();
9335
+ const summarizer = initSummarizer();
9336
+ summarizer.start();
8279
9337
  const unsubscribeWebhook = initWebhook();
8280
9338
  await completeOrphanedTurns();
8281
9339
  const allMinds = await readAllMinds();
@@ -8307,10 +9365,10 @@ async function startDaemon(opts) {
8307
9365
  await Promise.all(workers);
8308
9366
  }
8309
9367
  try {
8310
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-TISPCO22.js");
9368
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-GGMKENLN.js");
8311
9369
  if (isSetupComplete2()) {
8312
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-VRONKFMF.js");
8313
- const { startSpiritFull } = await import("./mind-service-2MQ6UK5N.js");
9370
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-4JP4TY4C.js");
9371
+ const { startSpiritFull } = await import("./mind-service-VIKZJK2M.js");
8314
9372
  await ensureSpiritProject();
8315
9373
  await syncSpiritTemplate();
8316
9374
  const spiritEntry = await findMind("volute");
@@ -8327,7 +9385,7 @@ async function startDaemon(opts) {
8327
9385
  bridgeManager.startBridges(daemonPort).catch((err) => {
8328
9386
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8329
9387
  });
8330
- import("./cloud-sync-4NWLMFVH.js").then(
9388
+ import("./cloud-sync-TG3TIX5H.js").then(
8331
9389
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8332
9390
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8333
9391
  })
@@ -8335,7 +9393,7 @@ async function startDaemon(opts) {
8335
9393
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8336
9394
  });
8337
9395
  try {
8338
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-NBI2MTJO.js");
9396
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-YCH4UVQ2.js");
8339
9397
  backfillTemplateHashes();
8340
9398
  notifyVersionUpdate().catch((err) => {
8341
9399
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -8393,6 +9451,7 @@ async function startDaemon(opts) {
8393
9451
  safe("scheduler.saveState", () => scheduler.saveState());
8394
9452
  safe("mailPoller.stop", () => mailPoller.stop());
8395
9453
  safe("tokenBudget.stop", () => tokenBudget.stop());
9454
+ safe("summarizer.stop", () => summarizer.stop());
8396
9455
  safe("stopApiKeyRefresh", stopApiKeyRefresh);
8397
9456
  safe("delivery.dispose", () => delivery.dispose());
8398
9457
  await safe("bridgeManager.stopAll", () => bridgeManager.stopAll());