volute 0.32.0 → 0.33.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 (97) hide show
  1. package/README.md +16 -0
  2. package/dist/{activity-events-HETAODOK.js → activity-events-XJO3P4RR.js} +1 -1
  3. package/dist/{ai-service-ZIPCV3MX.js → ai-service-SBY2WG7O.js} +2 -2
  4. package/dist/api.d.ts +666 -848
  5. package/dist/{auth-6DMGES3I.js → auth-GKCDSO4T.js} +2 -2
  6. package/dist/{chat-XT4OBJBU.js → chat-U5ZOME3O.js} +8 -8
  7. package/dist/{chunk-QBQ424EM.js → chunk-3Z2DPESO.js} +457 -203
  8. package/dist/chunk-6LXAAQ43.js +22 -0
  9. package/dist/{spirit-N4W4UQRH.js → chunk-7J3HEVR7.js} +12 -9
  10. package/dist/{chunk-WKF5FEFK.js → chunk-A2A4KLFE.js} +54 -155
  11. package/dist/{chunk-D5G5YOPL.js → chunk-C7I35G4R.js} +3 -3
  12. package/dist/{chunk-SX5TKJBZ.js → chunk-GY5HBI7A.js} +1 -1
  13. package/dist/{chunk-2FLJ63GU.js → chunk-JUKK7FPS.js} +1 -1
  14. package/dist/{chunk-TDRYEPH4.js → chunk-JYVGHWEJ.js} +2 -2
  15. package/dist/chunk-KIEPMIM5.js +59 -0
  16. package/dist/{chunk-QZANELPX.js → chunk-KVK2DLWI.js} +1 -0
  17. package/dist/{chunk-R7E6CRVQ.js → chunk-LOEJ4HPQ.js} +1 -1
  18. package/dist/{chunk-TSXLLQZW.js → chunk-N432I7QH.js} +9 -0
  19. package/dist/{chunk-LSGWR54X.js → chunk-NNB4WIG7.js} +1 -1
  20. package/dist/{chunk-JJ7W6WSB.js → chunk-NPKSDYA2.js} +2 -2
  21. package/dist/chunk-OYAKCAVY.js +29 -0
  22. package/dist/{chunk-IYDIE3HG.js → chunk-QTUVYI7W.js} +1 -1
  23. package/dist/{chunk-S6NFERDC.js → chunk-RVGLDGMI.js} +1 -1
  24. package/dist/{chunk-LGB6JBHI.js → chunk-VH33ZWMW.js} +4 -54
  25. package/dist/cli.js +26 -18
  26. package/dist/{clock-2UOZ6JPU.js → clock-BVH3V6E3.js} +5 -5
  27. package/dist/{cloud-sync-JN3NWKEM.js → cloud-sync-4NWLMFVH.js} +15 -11
  28. package/dist/{conversations-3O5O6AS3.js → conversations-AWI5SZW2.js} +2 -2
  29. package/dist/{create-WBBYI6V7.js → create-2FK7Z46Y.js} +1 -1
  30. package/dist/{create-RNLNCORE.js → create-YWD2TIP4.js} +4 -4
  31. package/dist/{daemon-restart-NGFHFAUF.js → daemon-restart-GOBUKLX7.js} +6 -5
  32. package/dist/daemon.js +1182 -1031
  33. package/dist/delivery-manager-PFAKEJTC.js +32 -0
  34. package/dist/{down-TB3ESMNP.js → down-FWWTEKXM.js} +4 -3
  35. package/dist/{extension-FQ5D3NCC.js → extension-OBTGKQQD.js} +2 -1
  36. package/dist/{extensions-GDYWQXC4.js → extensions-KYNTVTMO.js} +7 -6
  37. package/dist/isolation-LLAYQYDY.js +22 -0
  38. package/dist/message-delivery-DFF5SJRM.js +42 -0
  39. package/dist/{mind-2B6M7Y25.js → mind-IOJFLEM5.js} +13 -7
  40. package/dist/{mind-activity-tracker-NZZT2NTT.js → mind-activity-tracker-F6O4Q2SL.js} +2 -2
  41. package/dist/mind-manager-NBJF5D26.js +32 -0
  42. package/dist/mind-profile-P67FEHOY.js +47 -0
  43. package/dist/mind-service-2MQ6UK5N.js +38 -0
  44. package/dist/{package-PK6JUFL3.js → package-U3VFO273.js} +2 -1
  45. package/dist/read-stdin-HQJ7774D.js +8 -0
  46. package/dist/{sandbox-JANNTX6U.js → sandbox-GJOK4QLQ.js} +2 -2
  47. package/dist/scheduler-ZZ7XGQG6.js +32 -0
  48. package/dist/seed-QDYVLG74.js +11 -0
  49. package/dist/seed-check-S2IX25RL.js +32 -0
  50. package/dist/seed-cmd-DKOUFEAU.js +36 -0
  51. package/dist/{seed-ALUQ55FF.js → seed-create-4XBBOLRH.js} +5 -5
  52. package/dist/{sprout-L2GFOVF7.js → seed-sprout-GQEIIQRT.js} +19 -6
  53. package/dist/{send-3MI36LEF.js → send-QIV2INHB.js} +51 -49
  54. package/dist/{setup-SZIARWI6.js → setup-TISPCO22.js} +3 -1
  55. package/dist/{setup-WENLVPVP.js → setup-XMCBE3LF.js} +7 -5
  56. package/dist/skills/imagegen/SKILL.md +11 -7
  57. package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
  58. package/dist/skills/orientation/SKILL.md +9 -2
  59. package/dist/skills/seed-nurture/SKILL.md +42 -0
  60. package/dist/skills/volute-mind/SKILL.md +4 -0
  61. package/dist/{skills-XNZK6P4K.js → skills-7FV7EJTE.js} +4 -3
  62. package/dist/sleep-manager-JTXSN7NV.js +36 -0
  63. package/dist/spirit-VRONKFMF.js +23 -0
  64. package/dist/sprout-WKLZXUIQ.js +11 -0
  65. package/dist/{status-TCUMUO6M.js → status-3JBTFSMI.js} +3 -2
  66. package/dist/{system-chat-NPYFYZVI.js → system-chat-JAPOJ3KE.js} +15 -11
  67. package/dist/{systems-DHBKVYEY.js → systems-XRI52VCH.js} +2 -2
  68. package/dist/{up-6I6BHRTO.js → up-M5AS6SBV.js} +5 -4
  69. package/dist/{update-QVPRF6GR.js → update-UD543CXX.js} +3 -2
  70. package/dist/{version-notify-TCKWBZZG.js → version-notify-NBI2MTJO.js} +18 -15
  71. package/dist/volute-config-HD7WWUQC.js +10 -0
  72. package/dist/web-assets/assets/index-CWJrVveV.css +1 -0
  73. package/dist/web-assets/assets/index-DJt14FRI.js +75 -0
  74. package/dist/web-assets/index.html +2 -2
  75. package/package.json +2 -1
  76. package/templates/claude/src/lib/stream-consumer.ts +38 -0
  77. package/templates/codex/src/agent.ts +1 -0
  78. package/dist/delivery-manager-SDVXFD4W.js +0 -28
  79. package/dist/message-delivery-2FIM7QKO.js +0 -32
  80. package/dist/mind-manager-BNCMGYXW.js +0 -28
  81. package/dist/mind-service-AV273WT4.js +0 -34
  82. package/dist/sleep-manager-53DZOWW7.js +0 -32
  83. package/dist/web-assets/assets/index-Bui7U9Uu.css +0 -1
  84. package/dist/web-assets/assets/index-e36DIo1b.js +0 -73
  85. package/dist/{accept-74M7I4RZ.js → accept-D5VBM7JW.js} +3 -3
  86. package/dist/{bridge-BVCBTGPF.js → bridge-TXWWPPOJ.js} +3 -3
  87. package/dist/{env-RLYQBOOP.js → env-JCOF2222.js} +3 -3
  88. package/dist/{files-EAMPO2SJ.js → files-65PMW5IK.js} +3 -3
  89. package/dist/{history-FO5PHBQ5.js → history-DKCDI3JO.js} +3 -3
  90. package/dist/{list-DW2VRTOZ.js → list-JQ463EDA.js} +3 -3
  91. package/dist/{login-7CHPW2PN.js → login-D7ETSU4R.js} +3 -3
  92. package/dist/{mind-sleep-B7BHJLH7.js → mind-sleep-WW2IX7JT.js} +3 -3
  93. package/dist/{mind-wake-GY3RFX7Y.js → mind-wake-VSSGW465.js} +3 -3
  94. package/dist/{read-5AMJRO3D.js → read-EBY56C33.js} +3 -3
  95. package/dist/{register-V2JZZKFK.js → register-HD74C4TT.js} +3 -3
  96. package/dist/{reject-33HEZMZ4.js → reject-UJKFBHRO.js} +3 -3
  97. package/dist/{skill-TUVOTW4Z.js → skill-PSQGRRJX.js} +3 -3
package/dist/daemon.js CHANGED
@@ -1,22 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- computeTemplateHash
4
- } from "./chunk-PVY5W6QN.js";
5
2
  import {
6
3
  checkForUpdate,
7
4
  checkForUpdateCached,
8
5
  getCurrentVersion
9
6
  } from "./chunk-M7UL5S3Q.js";
7
+ import {
8
+ computeTemplateHash
9
+ } from "./chunk-PVY5W6QN.js";
10
10
  import {
11
11
  PROMPT_DEFAULTS,
12
12
  PROMPT_KEYS,
13
13
  RestartTracker,
14
14
  RotatingLog,
15
15
  announceToSystem,
16
+ assignSession,
17
+ completeOrphanedTurns,
18
+ completeTurn,
19
+ createTurn,
16
20
  deliverMessage,
17
21
  ensureSystemChannel,
18
22
  generateSystemReply,
23
+ getActiveTurnId,
19
24
  getDeliveryManager,
25
+ getLastToolUseEventId,
20
26
  getMindManager,
21
27
  getMindPromptDefaults,
22
28
  getPrompt,
@@ -33,24 +39,30 @@ import {
33
39
  initTokenBudget,
34
40
  isConversationId,
35
41
  joinSystemChannel,
42
+ linkToolResultToTurn,
36
43
  publish as publish2,
37
44
  publishTypingForChannels,
38
- readVoluteConfig,
39
45
  recordInbound,
46
+ recordOutbound,
40
47
  resolveMindToken,
48
+ setSummaryEventId,
41
49
  startMindFull,
42
50
  stopMindFull,
43
51
  subscribe as subscribe3,
52
+ subscribeAll,
44
53
  substitute,
45
54
  tagUntaggedInbound,
46
- writeVoluteConfig
47
- } from "./chunk-QBQ424EM.js";
48
- import "./chunk-SKLSMHXO.js";
55
+ tagUntaggedOutbound,
56
+ trackToolUse
57
+ } from "./chunk-3Z2DPESO.js";
58
+ import {
59
+ extractTextContent
60
+ } from "./chunk-SKLSMHXO.js";
49
61
  import {
50
62
  getActiveMinds,
51
63
  onMindEvent,
52
64
  stopAll
53
- } from "./chunk-R7E6CRVQ.js";
65
+ } from "./chunk-LOEJ4HPQ.js";
54
66
  import {
55
67
  addMessage,
56
68
  createChannel,
@@ -78,8 +90,8 @@ import {
78
90
  publish,
79
91
  setConversationPrivate,
80
92
  subscribe as subscribe2
81
- } from "./chunk-S6NFERDC.js";
82
- import "./chunk-SX5TKJBZ.js";
93
+ } from "./chunk-RVGLDGMI.js";
94
+ import "./chunk-GY5HBI7A.js";
83
95
  import {
84
96
  acceptPending,
85
97
  formatFileSize,
@@ -88,6 +100,18 @@ import {
88
100
  stageFile,
89
101
  validateFilePath
90
102
  } from "./chunk-ALEF47VT.js";
103
+ import "./chunk-7J3HEVR7.js";
104
+ import {
105
+ applyInitFiles,
106
+ composeTemplate,
107
+ copyTemplateToDir,
108
+ findTemplatesRoot,
109
+ listFiles
110
+ } from "./chunk-G53F3JA4.js";
111
+ import {
112
+ readVoluteConfig,
113
+ writeVoluteConfig
114
+ } from "./chunk-OYAKCAVY.js";
91
115
  import {
92
116
  findBridgeForChannel,
93
117
  findOpenClawSession,
@@ -110,23 +134,18 @@ import {
110
134
  writeEnv
111
135
  } from "./chunk-2NGTS5UU.js";
112
136
  import {
113
- assignSession,
114
- completeOrphanedTurns,
115
- completeTurn,
116
- createTurn,
137
+ isHomeOnlyArchive
138
+ } from "./chunk-I5KY25PQ.js";
139
+ import {
117
140
  deleteSystemsConfig,
118
- getActiveTurnId,
119
141
  getExtensionStandardSkills,
120
- getLastToolUseEventId,
121
142
  getLoadedExtensions,
122
143
  loadAllExtensions,
123
144
  notifyExtensionsDaemonStart,
124
145
  notifyExtensionsDaemonStop,
125
146
  readSystemsConfig,
126
- setSummaryEventId,
127
- trackToolUse,
128
147
  writeSystemsConfig
129
- } from "./chunk-WKF5FEFK.js";
148
+ } from "./chunk-A2A4KLFE.js";
130
149
  import "./chunk-PB65JZK2.js";
131
150
  import {
132
151
  approveUser,
@@ -145,18 +164,11 @@ import {
145
164
  setUserRole,
146
165
  updateUserProfile,
147
166
  verifyUser
148
- } from "./chunk-TDRYEPH4.js";
167
+ } from "./chunk-JYVGHWEJ.js";
149
168
  import {
150
169
  broadcast,
151
170
  subscribe
152
- } from "./chunk-QZANELPX.js";
153
- import {
154
- applyInitFiles,
155
- composeTemplate,
156
- copyTemplateToDir,
157
- findTemplatesRoot,
158
- listFiles
159
- } from "./chunk-G53F3JA4.js";
171
+ } from "./chunk-KVK2DLWI.js";
160
172
  import {
161
173
  SEED_SKILLS,
162
174
  STANDARD_SKILLS,
@@ -174,16 +186,14 @@ import {
174
186
  syncBuiltinSkills,
175
187
  uninstallSkill,
176
188
  updateSkill
177
- } from "./chunk-D5G5YOPL.js";
178
- import {
179
- isHomeOnlyArchive
180
- } from "./chunk-I5KY25PQ.js";
189
+ } from "./chunk-C7I35G4R.js";
181
190
  import {
182
191
  aiCompleteUtility,
183
192
  getAiConfig,
184
193
  getAvailableModels,
185
194
  getConfiguredProviders,
186
195
  getEnabledModels,
196
+ getUtilityModel,
187
197
  qualifyModelId,
188
198
  removeAiConfig,
189
199
  removeProviderConfig,
@@ -191,33 +201,36 @@ import {
191
201
  resolveTemplate,
192
202
  saveProviderConfig,
193
203
  setEnabledModels,
204
+ setUtilityModel,
194
205
  unqualifyModelId
195
- } from "./chunk-IYDIE3HG.js";
206
+ } from "./chunk-QTUVYI7W.js";
196
207
  import {
197
208
  logBuffer,
198
209
  logger_default
199
210
  } from "./chunk-YUIHSKR6.js";
211
+ import {
212
+ exec,
213
+ gitExec,
214
+ resolveVoluteBin
215
+ } from "./chunk-KIEPMIM5.js";
200
216
  import {
201
217
  chownMindDir,
202
218
  createMindUser,
203
219
  deleteMindUser,
204
220
  ensureVoluteGroup,
205
- exec,
206
- gitExec,
207
221
  isIsolationEnabled,
208
222
  mindUserName,
209
- resolveVoluteBin,
210
223
  wrapForIsolation
211
- } from "./chunk-LGB6JBHI.js";
224
+ } from "./chunk-VH33ZWMW.js";
212
225
  import {
213
226
  isSetupComplete,
214
227
  readGlobalConfig,
215
228
  writeGlobalConfig
216
- } from "./chunk-TSXLLQZW.js";
217
- import "./chunk-D424ZQGI.js";
229
+ } from "./chunk-N432I7QH.js";
218
230
  import {
219
231
  readSessionFile
220
232
  } from "./chunk-UKVWJRKN.js";
233
+ import "./chunk-D424ZQGI.js";
221
234
  import {
222
235
  buildVoluteSlug,
223
236
  slugify
@@ -247,7 +260,6 @@ import {
247
260
  } from "./chunk-LRCG2JLP.js";
248
261
  import {
249
262
  activity,
250
- conversationParticipants,
251
263
  conversations,
252
264
  messages,
253
265
  mindHistory,
@@ -768,6 +780,201 @@ import { Hono } from "hono";
768
780
  import { streamSSE } from "hono/streaming";
769
781
  import { z } from "zod";
770
782
 
783
+ // src/lib/services/imagegen.ts
784
+ import Replicate from "replicate";
785
+ var PROVIDERS = {
786
+ replicate: {
787
+ envVar: "REPLICATE_API_TOKEN",
788
+ generate: replicateGenerate,
789
+ search: replicateSearch
790
+ },
791
+ openrouter: {
792
+ envVar: "OPENROUTER_API_KEY",
793
+ generate: openrouterGenerate,
794
+ search: openrouterSearch
795
+ }
796
+ };
797
+ async function replicateGenerate(model, prompt, apiKey) {
798
+ const replicate = new Replicate({ auth: apiKey });
799
+ const output = await replicate.run(model, {
800
+ input: { prompt }
801
+ });
802
+ const file = Array.isArray(output) ? output[0] : output;
803
+ if (!file) throw new Error(`Model ${model} returned no output`);
804
+ if (typeof file === "string") {
805
+ const res = await fetch(file);
806
+ if (!res.ok) throw new Error(`Failed to fetch image from URL: ${res.status}`);
807
+ return Buffer.from(await res.arrayBuffer());
808
+ }
809
+ const chunks = [];
810
+ for await (const chunk of file) {
811
+ chunks.push(chunk);
812
+ }
813
+ return Buffer.concat(chunks);
814
+ }
815
+ async function replicateSearch(query, apiKey) {
816
+ const replicate = new Replicate({ auth: apiKey });
817
+ const response = await replicate.models.search(query || "text to image");
818
+ return response.results.slice(0, 20).map((m) => ({
819
+ id: `replicate:${m.owner}/${m.name}`,
820
+ name: m.name,
821
+ description: m.description?.slice(0, 200),
822
+ owner: m.owner
823
+ }));
824
+ }
825
+ async function openrouterGenerate(model, prompt, apiKey) {
826
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
827
+ method: "POST",
828
+ headers: {
829
+ "Content-Type": "application/json",
830
+ Authorization: `Bearer ${apiKey}`
831
+ },
832
+ body: JSON.stringify({
833
+ model,
834
+ messages: [{ role: "user", content: prompt }],
835
+ modalities: ["image", "text"]
836
+ })
837
+ });
838
+ if (!res.ok) {
839
+ const body = await res.text().catch(() => `HTTP ${res.status}`);
840
+ throw new Error(`OpenRouter API error: ${body}`);
841
+ }
842
+ const data = await res.json();
843
+ const imageUrl = data.choices?.[0]?.message?.images?.[0]?.image_url?.url;
844
+ if (!imageUrl) throw new Error("OpenRouter returned no image");
845
+ const match = imageUrl.match(/^data:[^;]+;base64,(.+)$/);
846
+ if (match) {
847
+ return Buffer.from(match[1], "base64");
848
+ }
849
+ const imgRes = await fetch(imageUrl);
850
+ if (!imgRes.ok) throw new Error(`Failed to fetch image from URL: ${imgRes.status}`);
851
+ return Buffer.from(await imgRes.arrayBuffer());
852
+ }
853
+ async function openrouterSearch(query, _apiKey) {
854
+ const res = await fetch("https://openrouter.ai/api/v1/models?output_modalities=image");
855
+ if (!res.ok) throw new Error(`OpenRouter model list failed: ${res.status}`);
856
+ const data = await res.json();
857
+ const models = data.data ?? [];
858
+ const q = query.toLowerCase();
859
+ const filtered = models.filter((m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q)).slice(0, 20);
860
+ return filtered.map((m) => {
861
+ const parts = m.id.split("/");
862
+ return {
863
+ id: `openrouter:${m.id}`,
864
+ name: m.name,
865
+ description: m.description?.slice(0, 200),
866
+ owner: parts[0] || m.id
867
+ };
868
+ });
869
+ }
870
+ function parseModelId(prefixedId) {
871
+ const colonIdx = prefixedId.indexOf(":");
872
+ if (colonIdx === -1)
873
+ throw new Error(
874
+ `Model ID must be provider-prefixed (e.g. replicate:owner/model), got: ${prefixedId}`
875
+ );
876
+ const provider = prefixedId.slice(0, colonIdx);
877
+ const model = prefixedId.slice(colonIdx + 1);
878
+ if (!PROVIDERS[provider]) throw new Error(`Unknown imagegen provider: ${provider}`);
879
+ return { provider, model };
880
+ }
881
+ function getImagegenConfig() {
882
+ return readGlobalConfig().imagegen ?? null;
883
+ }
884
+ function updateImagegenConfig(fn) {
885
+ const config2 = readGlobalConfig();
886
+ const ig = config2.imagegen ?? {};
887
+ fn(ig);
888
+ config2.imagegen = ig;
889
+ writeGlobalConfig(config2);
890
+ }
891
+ function saveProviderConfig2(id, apiKey) {
892
+ if (!PROVIDERS[id]) throw new Error(`Unknown imagegen provider: ${id}`);
893
+ updateImagegenConfig((ig) => {
894
+ ig.providers = ig.providers ?? {};
895
+ ig.providers[id] = { apiKey };
896
+ });
897
+ }
898
+ function removeProviderConfig2(id) {
899
+ if (!PROVIDERS[id]) throw new Error(`Unknown imagegen provider: ${id}`);
900
+ updateImagegenConfig((ig) => {
901
+ if (ig.providers) {
902
+ delete ig.providers[id];
903
+ if (Object.keys(ig.providers).length === 0) delete ig.providers;
904
+ }
905
+ });
906
+ }
907
+ function getConfiguredProviders2() {
908
+ const config2 = readGlobalConfig();
909
+ const ig = config2.imagegen ?? null;
910
+ const aiProviders = config2.ai?.providers;
911
+ return Object.entries(PROVIDERS).map(([id, { envVar }]) => {
912
+ const providerConfig = ig?.providers?.[id];
913
+ if (providerConfig?.apiKey) return { id, configured: true, authMethod: "api_key" };
914
+ if (aiProviders?.[id]?.apiKey) return { id, configured: true, authMethod: "api_key" };
915
+ if (process.env[envVar]) return { id, configured: true, authMethod: "env_var" };
916
+ return { id, configured: false, authMethod: null };
917
+ });
918
+ }
919
+ function resolveApiKey2(providerId) {
920
+ const provider = PROVIDERS[providerId];
921
+ if (!provider) return void 0;
922
+ const config2 = readGlobalConfig();
923
+ const configKey = config2.imagegen?.providers?.[providerId]?.apiKey;
924
+ if (configKey) return configKey;
925
+ const aiKey = config2.ai?.providers?.[providerId]?.apiKey;
926
+ if (aiKey) return aiKey;
927
+ return process.env[provider.envVar] || void 0;
928
+ }
929
+ function getEnabledModels2() {
930
+ return getImagegenConfig()?.models ?? [];
931
+ }
932
+ function setEnabledModels2(ids) {
933
+ updateImagegenConfig((ig) => {
934
+ ig.models = ids;
935
+ if (ig.defaultModel && !ids.includes(ig.defaultModel)) {
936
+ delete ig.defaultModel;
937
+ }
938
+ });
939
+ }
940
+ function getDefaultModel() {
941
+ return getImagegenConfig()?.defaultModel;
942
+ }
943
+ function setDefaultModel(modelId) {
944
+ updateImagegenConfig((ig) => {
945
+ if (modelId) {
946
+ ig.defaultModel = modelId;
947
+ } else {
948
+ delete ig.defaultModel;
949
+ }
950
+ });
951
+ }
952
+ async function searchModels(query, provider) {
953
+ if (provider) {
954
+ const providerDef = PROVIDERS[provider];
955
+ if (!providerDef) throw new Error(`Unknown imagegen provider: ${provider}`);
956
+ const apiKey = resolveApiKey2(provider);
957
+ if (!apiKey) throw new Error(`No ${provider} API key configured`);
958
+ return providerDef.search(query || "text to image", apiKey);
959
+ }
960
+ const searches = [];
961
+ for (const [id, def] of Object.entries(PROVIDERS)) {
962
+ const apiKey = resolveApiKey2(id);
963
+ if (!apiKey) continue;
964
+ searches.push(def.search(query || "text to image", apiKey).catch(() => []));
965
+ }
966
+ if (searches.length === 0) {
967
+ throw new Error("No imagegen providers configured");
968
+ }
969
+ return (await Promise.all(searches)).flat();
970
+ }
971
+ async function generateImage(model, prompt) {
972
+ const { provider: providerId, model: modelId } = parseModelId(model);
973
+ const apiKey = resolveApiKey2(providerId);
974
+ if (!apiKey) throw new Error(`No ${providerId} API key configured`);
975
+ return PROVIDERS[providerId].generate(modelId, prompt, apiKey);
976
+ }
977
+
771
978
  // src/web/middleware/auth.ts
772
979
  import { timingSafeEqual } from "crypto";
773
980
  import { eq as eq2, lt as lt2 } from "drizzle-orm";
@@ -902,6 +1109,7 @@ var requireSelf = (paramName = "name") => createMiddleware(async (c, next) => {
902
1109
 
903
1110
  // src/web/api/system.ts
904
1111
  var DEFAULT_API_URL = "https://volute.systems";
1112
+ var igLog = logger_default.child("imagegen");
905
1113
  var app = new Hono().post("/restart", requireAdmin, (c) => {
906
1114
  setTimeout(() => process.exit(1), 200);
907
1115
  return c.json({ ok: true });
@@ -1044,7 +1252,77 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1044
1252
  } catch (err) {
1045
1253
  return c.json({ error: `Connection failed: ${err.message}` }, 502);
1046
1254
  }
1047
- }).get("/ai/key/:provider", requireAdmin, async (c) => {
1255
+ }).get("/imagegen/providers", requireAdmin, (c) => {
1256
+ return c.json(getConfiguredProviders2());
1257
+ }).put(
1258
+ "/imagegen/providers/:id",
1259
+ requireAdmin,
1260
+ zValidator("json", z.object({ apiKey: z.string().min(1) })),
1261
+ (c) => {
1262
+ const id = c.req.param("id");
1263
+ const { apiKey } = c.req.valid("json");
1264
+ try {
1265
+ saveProviderConfig2(id, apiKey);
1266
+ } catch (err) {
1267
+ return c.json({ error: err instanceof Error ? err.message : "Failed to save" }, 400);
1268
+ }
1269
+ return c.json({ ok: true });
1270
+ }
1271
+ ).delete("/imagegen/providers/:id", requireAdmin, (c) => {
1272
+ try {
1273
+ removeProviderConfig2(c.req.param("id"));
1274
+ } catch (err) {
1275
+ return c.json({ error: err instanceof Error ? err.message : "Failed to remove" }, 400);
1276
+ }
1277
+ return c.json({ ok: true });
1278
+ }).get("/imagegen/models", requireAdmin, (c) => {
1279
+ const models = getEnabledModels2();
1280
+ const defaultModel = getDefaultModel();
1281
+ return c.json({ models, defaultModel: defaultModel ?? null });
1282
+ }).put(
1283
+ "/imagegen/models",
1284
+ requireAdmin,
1285
+ zValidator(
1286
+ "json",
1287
+ z.object({ models: z.array(z.string()), defaultModel: z.string().nullable().optional() })
1288
+ ),
1289
+ (c) => {
1290
+ const { models, defaultModel } = c.req.valid("json");
1291
+ setEnabledModels2(models);
1292
+ if (defaultModel !== void 0) {
1293
+ setDefaultModel(defaultModel ?? void 0);
1294
+ }
1295
+ return c.json({ ok: true });
1296
+ }
1297
+ ).get("/imagegen/models/search", async (c) => {
1298
+ const q = c.req.query("q");
1299
+ const provider = c.req.query("provider");
1300
+ try {
1301
+ const results = await searchModels(q || void 0, provider || void 0);
1302
+ return c.json(results);
1303
+ } catch (err) {
1304
+ igLog.error("model search failed", logger_default.errorData(err));
1305
+ return c.json({ error: err instanceof Error ? err.message : "Search failed" }, 500);
1306
+ }
1307
+ }).post(
1308
+ "/imagegen/generate",
1309
+ zValidator("json", z.object({ model: z.string().min(1), prompt: z.string().min(1) })),
1310
+ async (c) => {
1311
+ const { model, prompt } = c.req.valid("json");
1312
+ try {
1313
+ const buf = await generateImage(model, prompt);
1314
+ return new Response(buf, {
1315
+ headers: {
1316
+ "Content-Type": "image/png",
1317
+ "Content-Length": String(buf.length)
1318
+ }
1319
+ });
1320
+ } catch (err) {
1321
+ igLog.error("image generation failed", logger_default.errorData(err));
1322
+ return c.json({ error: err instanceof Error ? err.message : "Generation failed" }, 500);
1323
+ }
1324
+ }
1325
+ ).get("/ai/key/:provider", requireAdmin, async (c) => {
1048
1326
  const key = getCachedApiKey(c.req.param("provider"));
1049
1327
  if (!key) return c.json({ error: "No key available" }, 404);
1050
1328
  return c.json({ key });
@@ -1092,6 +1370,30 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1092
1370
  setEnabledModels(models);
1093
1371
  return c.json({ ok: true });
1094
1372
  }
1373
+ ).get("/ai/defaults", requireAdmin, (c) => {
1374
+ const config2 = readGlobalConfig();
1375
+ return c.json({
1376
+ spiritModel: config2.spiritModel ?? null,
1377
+ utilityModel: getUtilityModel() ?? null
1378
+ });
1379
+ }).put(
1380
+ "/ai/defaults",
1381
+ requireAdmin,
1382
+ zValidator(
1383
+ "json",
1384
+ z.object({
1385
+ spiritModel: z.string().nullable(),
1386
+ utilityModel: z.string().nullable()
1387
+ })
1388
+ ),
1389
+ (c) => {
1390
+ const { spiritModel, utilityModel } = c.req.valid("json");
1391
+ const config2 = readGlobalConfig();
1392
+ config2.spiritModel = spiritModel ?? void 0;
1393
+ writeGlobalConfig(config2);
1394
+ setUtilityModel(utilityModel ?? void 0);
1395
+ return c.json({ ok: true });
1396
+ }
1095
1397
  ).put(
1096
1398
  "/ai/providers/:id",
1097
1399
  requireAdmin,
@@ -1255,7 +1557,7 @@ function stopApiKeyRefresh() {
1255
1557
  var system_default = app;
1256
1558
 
1257
1559
  // src/web/app.ts
1258
- import { Hono as Hono31 } from "hono";
1560
+ import { Hono as Hono30 } from "hono";
1259
1561
  import { bodyLimit } from "hono/body-limit";
1260
1562
  import { csrf } from "hono/csrf";
1261
1563
  import { HTTPException } from "hono/http-exception";
@@ -1718,7 +2020,7 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1718
2020
  }
1719
2021
  const participants = await getParticipants(channel.id);
1720
2022
  if (!participants.some((p) => p.userId === puppet.id)) {
1721
- const { addParticipant } = await import("./conversations-3O5O6AS3.js");
2023
+ const { addParticipant } = await import("./conversations-AWI5SZW2.js");
1722
2024
  await addParticipant(channel.id, puppet.id);
1723
2025
  }
1724
2026
  const contentBlocks = body.content;
@@ -1811,8 +2113,8 @@ async function fanOutToBridgedMinds(opts) {
1811
2113
  const participants = await getParticipants(opts.conversationId);
1812
2114
  const mindParticipants = participants.filter((p) => p.userType === "mind");
1813
2115
  const participantNames = participants.map((p) => p.username);
1814
- const { getMindManager: getMindManager2 } = await import("./mind-manager-BNCMGYXW.js");
1815
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
2116
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-NBJF5D26.js");
2117
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
1816
2118
  const manager = getMindManager2();
1817
2119
  const sm = getSleepManagerIfReady2();
1818
2120
  const targetMinds = mindParticipants.filter((ap) => {
@@ -2345,14 +2647,15 @@ async function send4(env, channelSlug, message, images) {
2345
2647
  if (token) headers.Authorization = `Bearer ${token}`;
2346
2648
  const voluteSession = env.VOLUTE_SESSION || (env.VOLUTE_MIND_DIR ? readSessionFile(env.VOLUTE_MIND_DIR) : void 0);
2347
2649
  if (voluteSession) headers["X-Volute-Session"] = voluteSession;
2348
- const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/chat`, {
2650
+ const res = await fetch(`${url}/api/v1/chat`, {
2349
2651
  method: "POST",
2350
2652
  headers,
2351
2653
  body: JSON.stringify({
2352
2654
  message,
2353
2655
  conversationId,
2354
2656
  sender: env.VOLUTE_SENDER ?? mindName,
2355
- images
2657
+ images,
2658
+ targetMind: mindName
2356
2659
  })
2357
2660
  });
2358
2661
  if (!res.ok) {
@@ -2624,7 +2927,7 @@ async function notifyMind(mindName, message) {
2624
2927
  const entry = await findMind(mindName);
2625
2928
  if (!entry) return;
2626
2929
  try {
2627
- const { sendSystemMessage } = await import("./system-chat-NPYFYZVI.js");
2930
+ const { sendSystemMessage } = await import("./system-chat-JAPOJ3KE.js");
2628
2931
  await sendSystemMessage(mindName, message);
2629
2932
  } catch (err) {
2630
2933
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -2644,13 +2947,13 @@ var app8 = new Hono9().post("/:name/files/send", requireSelf(), async (c) => {
2644
2947
  if (pathErr) return c.json({ error: pathErr }, 400);
2645
2948
  const senderDir = mindDir(senderName);
2646
2949
  const filePath = resolve6(senderDir, "home", body.filePath);
2647
- const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
2950
+ const MAX_FILE_SIZE3 = 50 * 1024 * 1024;
2648
2951
  const stat4 = statSync(filePath, { throwIfNoEntry: false });
2649
2952
  if (!stat4) return c.json({ error: `File not found: ${body.filePath}` }, 404);
2650
- if (stat4.size > MAX_FILE_SIZE2) {
2953
+ if (stat4.size > MAX_FILE_SIZE3) {
2651
2954
  return c.json(
2652
2955
  {
2653
- error: `File too large (${formatFileSize(stat4.size)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
2956
+ error: `File too large (${formatFileSize(stat4.size)}, max ${formatFileSize(MAX_FILE_SIZE3)})`
2654
2957
  },
2655
2958
  413
2656
2959
  );
@@ -2727,11 +3030,11 @@ var app8 = new Hono9().post("/:name/files/send", requireSelf(), async (c) => {
2727
3030
  const pathErr = validateFilePath(body.filename);
2728
3031
  if (pathErr) return c.json({ error: pathErr }, 400);
2729
3032
  const content = Buffer.from(body.data, "base64");
2730
- const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
2731
- if (content.length > MAX_FILE_SIZE2) {
3033
+ const MAX_FILE_SIZE3 = 50 * 1024 * 1024;
3034
+ if (content.length > MAX_FILE_SIZE3) {
2732
3035
  return c.json(
2733
3036
  {
2734
- error: `File too large (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
3037
+ error: `File too large (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE3)})`
2735
3038
  },
2736
3039
  413
2737
3040
  );
@@ -2796,92 +3099,301 @@ var app9 = new Hono10().get("/:name/avatar", async (c) => {
2796
3099
  });
2797
3100
  var files_default = app9;
2798
3101
 
2799
- // src/web/api/keys.ts
3102
+ // src/web/api/history.ts
3103
+ import { and as and3, desc as desc2, eq as eq4, inArray, sql } from "drizzle-orm";
2800
3104
  import { Hono as Hono11 } from "hono";
2801
-
2802
- // src/lib/identity.ts
2803
- import { createHash, generateKeyPairSync, sign, verify } from "crypto";
2804
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2805
- import { resolve as resolve8 } from "path";
2806
- function generateIdentity(mindDir2) {
2807
- const identityDir = resolve8(mindDir2, ".mind/identity");
2808
- mkdirSync4(identityDir, { recursive: true });
2809
- const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
2810
- publicKeyEncoding: { type: "spki", format: "pem" },
2811
- privateKeyEncoding: { type: "pkcs8", format: "pem" }
2812
- });
2813
- const privatePath = resolve8(identityDir, "private.pem");
2814
- const publicPath = resolve8(identityDir, "public.pem");
2815
- writeFileSync4(privatePath, privateKey, { mode: 384 });
2816
- writeFileSync4(publicPath, publicKey, { mode: 420 });
2817
- const config2 = readVoluteConfig(mindDir2) ?? {};
2818
- config2.identity = {
2819
- privateKey: ".mind/identity/private.pem",
2820
- publicKey: ".mind/identity/public.pem"
2821
- };
2822
- writeVoluteConfig(mindDir2, config2);
2823
- return { publicKeyPem: publicKey, privateKeyPem: privateKey };
2824
- }
2825
- function getPublicKey(mindDir2) {
2826
- const config2 = readVoluteConfig(mindDir2);
2827
- const relPath = config2?.identity?.publicKey;
2828
- if (!relPath) return null;
2829
- const fullPath = resolve8(mindDir2, relPath);
2830
- if (!existsSync6(fullPath)) return null;
2831
- return readFileSync7(fullPath, "utf-8");
2832
- }
2833
- function getFingerprint(publicKeyPem) {
2834
- return createHash("sha256").update(publicKeyPem).digest("hex");
2835
- }
2836
- async function publishPublicKey(mindName, publicKeyPem) {
2837
- const systems = readSystemsConfig();
2838
- if (!systems) return false;
2839
- try {
2840
- const res = await fetch(`${systems.apiUrl}/api/keys/${encodeURIComponent(mindName)}`, {
2841
- method: "PUT",
2842
- headers: {
2843
- "Content-Type": "application/json",
2844
- Authorization: `Bearer ${systems.apiKey}`
2845
- },
2846
- body: JSON.stringify({ publicKey: publicKeyPem })
2847
- });
2848
- if (!res.ok) {
2849
- logger_default.warn(`failed to publish key for ${mindName}: ${res.status}`);
2850
- return false;
2851
- }
2852
- return true;
2853
- } catch (err) {
2854
- logger_default.warn(`failed to publish key for ${mindName}`, logger_default.errorData(err));
2855
- return false;
3105
+ var history = new Hono11().get("/turns", async (c) => {
3106
+ const mindFilter = c.req.query("mind");
3107
+ const turnIdFilter = c.req.query("turnId");
3108
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
3109
+ const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
3110
+ const db = await getDb();
3111
+ 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);
3115
+ if (turnRows.length === 0) return c.json([]);
3116
+ 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)));
3118
+ const summaryByTurn = /* @__PURE__ */ new Map();
3119
+ for (const s of summaryRows) {
3120
+ if (s.turn_id)
3121
+ summaryByTurn.set(s.turn_id, { content: s.content ?? "", metadata: s.metadata });
2856
3122
  }
2857
- }
2858
-
2859
- // src/web/api/keys.ts
2860
- var app10 = new Hono11().get("/:fingerprint", async (c) => {
2861
- const fingerprint = c.req.param("fingerprint");
2862
- for (const entry of await readRegistry()) {
2863
- try {
2864
- const pubKey = getPublicKey(mindDir(entry.name));
2865
- if (!pubKey) continue;
2866
- if (getFingerprint(pubKey) === fingerprint) {
2867
- return c.json({ publicKey: pubKey, mind: entry.name });
2868
- }
2869
- } catch {
3123
+ const historyMsgRows = await db.select({
3124
+ id: mindHistory.id,
3125
+ mind: mindHistory.mind,
3126
+ type: mindHistory.type,
3127
+ channel: mindHistory.channel,
3128
+ sender: mindHistory.sender,
3129
+ content: mindHistory.content,
3130
+ turn_id: mindHistory.turn_id,
3131
+ created_at: mindHistory.created_at
3132
+ }).from(mindHistory).where(
3133
+ and3(
3134
+ inArray(mindHistory.turn_id, turnIds),
3135
+ sql`${mindHistory.type} IN ('inbound', 'outbound')`
3136
+ )
3137
+ ).orderBy(mindHistory.created_at);
3138
+ const msgsByTurnChannel = /* @__PURE__ */ new Map();
3139
+ function addToTurnChannel(turnId, channel, event) {
3140
+ let byChannel = msgsByTurnChannel.get(turnId);
3141
+ if (!byChannel) {
3142
+ byChannel = /* @__PURE__ */ new Map();
3143
+ msgsByTurnChannel.set(turnId, byChannel);
3144
+ }
3145
+ let arr = byChannel.get(channel);
3146
+ if (!arr) {
3147
+ arr = [];
3148
+ byChannel.set(channel, arr);
2870
3149
  }
3150
+ arr.push(event);
2871
3151
  }
2872
- return c.json({ error: "Key not found" }, 404);
2873
- });
2874
- var keys_default = app10;
2875
-
2876
- // src/web/api/logs.ts
2877
- import { spawn as spawn2 } from "child_process";
2878
- import { existsSync as existsSync7 } from "fs";
2879
- import { resolve as resolve9 } from "path";
2880
- import { Hono as Hono12 } from "hono";
2881
- import { streamSSE as streamSSE3 } from "hono/streaming";
2882
- var app11 = new Hono12().get("/:name/logs", async (c) => {
2883
- const name = c.req.param("name");
2884
- const entry = await findMind(name);
3152
+ const turnMindMap = /* @__PURE__ */ new Map();
3153
+ for (const t of turnRows) turnMindMap.set(t.id, t.mind);
3154
+ for (const m of historyMsgRows) {
3155
+ if (!m.turn_id || !m.channel) continue;
3156
+ const mindName = turnMindMap.get(m.turn_id) ?? m.mind;
3157
+ addToTurnChannel(m.turn_id, m.channel, {
3158
+ id: m.id,
3159
+ role: m.type === "inbound" ? "user" : "assistant",
3160
+ sender: m.type === "inbound" ? m.sender ?? null : mindName,
3161
+ content: m.content,
3162
+ created_at: m.created_at
3163
+ });
3164
+ }
3165
+ function getChannelLabel(channel) {
3166
+ const isDM = channel.startsWith("@");
3167
+ const colonIdx = channel.indexOf(":");
3168
+ const raw = colonIdx >= 0 ? channel.substring(colonIdx + 1) : channel;
3169
+ const label = isDM ? raw : raw.startsWith("#") ? raw : `#${raw}`;
3170
+ return { label, type: isDM ? "dm" : "channel" };
3171
+ }
3172
+ const activityRows = await db.select().from(activity).where(inArray(activity.turn_id, turnIds)).orderBy(activity.created_at);
3173
+ const activitiesByTurn = /* @__PURE__ */ new Map();
3174
+ for (const a of activityRows) {
3175
+ if (!a.turn_id) continue;
3176
+ let arr = activitiesByTurn.get(a.turn_id);
3177
+ if (!arr) {
3178
+ arr = [];
3179
+ activitiesByTurn.set(a.turn_id, arr);
3180
+ }
3181
+ arr.push(a);
3182
+ }
3183
+ const triggerIds = turnRows.filter((t) => t.trigger_event_id != null).map((t) => t.trigger_event_id);
3184
+ const triggerMap = /* @__PURE__ */ new Map();
3185
+ if (triggerIds.length > 0) {
3186
+ const triggerRows = await db.select({
3187
+ id: mindHistory.id,
3188
+ channel: mindHistory.channel,
3189
+ sender: mindHistory.sender,
3190
+ content: mindHistory.content
3191
+ }).from(mindHistory).where(inArray(mindHistory.id, triggerIds));
3192
+ for (const r of triggerRows) triggerMap.set(r.id, r);
3193
+ }
3194
+ const result = turnRows.map((t) => {
3195
+ const summary = summaryByTurn.get(t.id);
3196
+ const turnChannels = msgsByTurnChannel.get(t.id) ?? /* @__PURE__ */ new Map();
3197
+ const convEntries = [...turnChannels.entries()].map(([channel, evts]) => {
3198
+ const { label, type } = getChannelLabel(channel);
3199
+ return {
3200
+ id: channel,
3201
+ label,
3202
+ type,
3203
+ messages: evts.map((m) => ({
3204
+ id: m.id,
3205
+ role: m.role,
3206
+ sender_name: m.sender,
3207
+ content: [{ type: "text", text: m.content ?? "" }],
3208
+ source_event_id: m.id,
3209
+ created_at: m.created_at
3210
+ }))
3211
+ };
3212
+ });
3213
+ const turnActivities = (activitiesByTurn.get(t.id) ?? []).map((a) => {
3214
+ let metadata = null;
3215
+ if (a.metadata) {
3216
+ try {
3217
+ metadata = JSON.parse(a.metadata);
3218
+ } catch (err) {
3219
+ logger_default.debug(`malformed activity metadata for activity ${a.id}`, logger_default.errorData(err));
3220
+ }
3221
+ }
3222
+ return {
3223
+ id: a.id,
3224
+ type: a.type,
3225
+ summary: a.summary,
3226
+ metadata,
3227
+ source_event_id: a.source_event_id,
3228
+ created_at: a.created_at
3229
+ };
3230
+ });
3231
+ let summaryMeta = null;
3232
+ if (summary?.metadata) {
3233
+ try {
3234
+ summaryMeta = JSON.parse(summary.metadata);
3235
+ } catch (err) {
3236
+ logger_default.debug(`malformed summary metadata for turn ${t.id}`, logger_default.errorData(err));
3237
+ }
3238
+ }
3239
+ const trigger = t.trigger_event_id ? triggerMap.get(t.trigger_event_id) : null;
3240
+ return {
3241
+ id: t.id,
3242
+ mind: t.mind,
3243
+ summary: summary?.content ?? null,
3244
+ summary_meta: summaryMeta,
3245
+ status: t.status,
3246
+ created_at: t.created_at,
3247
+ trigger: trigger ? { channel: trigger.channel, sender: trigger.sender, content: trigger.content } : null,
3248
+ conversations: convEntries,
3249
+ activities: turnActivities
3250
+ };
3251
+ });
3252
+ return c.json(result);
3253
+ }).get("/events", async (c) => {
3254
+ const mindFilter = c.req.query("mind");
3255
+ const stream = new ReadableStream({
3256
+ start(controller) {
3257
+ const encoder = new TextEncoder();
3258
+ const send5 = (data) => {
3259
+ controller.enqueue(encoder.encode(`data: ${data}
3260
+
3261
+ `));
3262
+ };
3263
+ let unsubscribe;
3264
+ const pingInterval = setInterval(() => {
3265
+ try {
3266
+ controller.enqueue(encoder.encode(": ping\n\n"));
3267
+ } catch {
3268
+ clearInterval(pingInterval);
3269
+ unsubscribe?.();
3270
+ }
3271
+ }, 15e3);
3272
+ if (mindFilter) {
3273
+ unsubscribe = subscribe3(mindFilter, (event) => {
3274
+ try {
3275
+ send5(JSON.stringify(event));
3276
+ } catch {
3277
+ clearInterval(pingInterval);
3278
+ unsubscribe?.();
3279
+ }
3280
+ });
3281
+ } else {
3282
+ unsubscribe = subscribeAll((event) => {
3283
+ try {
3284
+ send5(JSON.stringify(event));
3285
+ } catch {
3286
+ clearInterval(pingInterval);
3287
+ unsubscribe?.();
3288
+ }
3289
+ });
3290
+ }
3291
+ c.req.raw.signal.addEventListener("abort", () => {
3292
+ clearInterval(pingInterval);
3293
+ unsubscribe?.();
3294
+ try {
3295
+ controller.close();
3296
+ } catch {
3297
+ }
3298
+ });
3299
+ }
3300
+ });
3301
+ return new Response(stream, {
3302
+ headers: {
3303
+ "Content-Type": "text/event-stream",
3304
+ "Cache-Control": "no-cache",
3305
+ Connection: "keep-alive"
3306
+ }
3307
+ });
3308
+ });
3309
+ var history_default = history;
3310
+
3311
+ // src/web/api/keys.ts
3312
+ import { Hono as Hono12 } from "hono";
3313
+
3314
+ // src/lib/identity.ts
3315
+ import { createHash, generateKeyPairSync, sign, verify } from "crypto";
3316
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3317
+ import { resolve as resolve8 } from "path";
3318
+ function generateIdentity(mindDir2) {
3319
+ const identityDir = resolve8(mindDir2, ".mind/identity");
3320
+ mkdirSync4(identityDir, { recursive: true });
3321
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
3322
+ publicKeyEncoding: { type: "spki", format: "pem" },
3323
+ privateKeyEncoding: { type: "pkcs8", format: "pem" }
3324
+ });
3325
+ const privatePath = resolve8(identityDir, "private.pem");
3326
+ const publicPath = resolve8(identityDir, "public.pem");
3327
+ writeFileSync4(privatePath, privateKey, { mode: 384 });
3328
+ writeFileSync4(publicPath, publicKey, { mode: 420 });
3329
+ const config2 = readVoluteConfig(mindDir2) ?? {};
3330
+ config2.identity = {
3331
+ privateKey: ".mind/identity/private.pem",
3332
+ publicKey: ".mind/identity/public.pem"
3333
+ };
3334
+ writeVoluteConfig(mindDir2, config2);
3335
+ return { publicKeyPem: publicKey, privateKeyPem: privateKey };
3336
+ }
3337
+ function getPublicKey(mindDir2) {
3338
+ const config2 = readVoluteConfig(mindDir2);
3339
+ const relPath = config2?.identity?.publicKey;
3340
+ if (!relPath) return null;
3341
+ const fullPath = resolve8(mindDir2, relPath);
3342
+ if (!existsSync6(fullPath)) return null;
3343
+ return readFileSync7(fullPath, "utf-8");
3344
+ }
3345
+ function getFingerprint(publicKeyPem) {
3346
+ return createHash("sha256").update(publicKeyPem).digest("hex");
3347
+ }
3348
+ async function publishPublicKey(mindName, publicKeyPem) {
3349
+ const systems = readSystemsConfig();
3350
+ if (!systems) return false;
3351
+ try {
3352
+ const res = await fetch(`${systems.apiUrl}/api/keys/${encodeURIComponent(mindName)}`, {
3353
+ method: "PUT",
3354
+ headers: {
3355
+ "Content-Type": "application/json",
3356
+ Authorization: `Bearer ${systems.apiKey}`
3357
+ },
3358
+ body: JSON.stringify({ publicKey: publicKeyPem })
3359
+ });
3360
+ if (!res.ok) {
3361
+ logger_default.warn(`failed to publish key for ${mindName}: ${res.status}`);
3362
+ return false;
3363
+ }
3364
+ return true;
3365
+ } catch (err) {
3366
+ logger_default.warn(`failed to publish key for ${mindName}`, logger_default.errorData(err));
3367
+ return false;
3368
+ }
3369
+ }
3370
+
3371
+ // src/web/api/keys.ts
3372
+ var app10 = new Hono12().get("/:fingerprint", async (c) => {
3373
+ const fingerprint = c.req.param("fingerprint");
3374
+ for (const entry of await readRegistry()) {
3375
+ try {
3376
+ const pubKey = getPublicKey(mindDir(entry.name));
3377
+ if (!pubKey) continue;
3378
+ if (getFingerprint(pubKey) === fingerprint) {
3379
+ return c.json({ publicKey: pubKey, mind: entry.name });
3380
+ }
3381
+ } catch {
3382
+ }
3383
+ }
3384
+ return c.json({ error: "Key not found" }, 404);
3385
+ });
3386
+ var keys_default = app10;
3387
+
3388
+ // src/web/api/logs.ts
3389
+ import { spawn as spawn2 } from "child_process";
3390
+ import { existsSync as existsSync7 } from "fs";
3391
+ import { resolve as resolve9 } from "path";
3392
+ import { Hono as Hono13 } from "hono";
3393
+ import { streamSSE as streamSSE3 } from "hono/streaming";
3394
+ var app11 = new Hono13().get("/:name/logs", async (c) => {
3395
+ const name = c.req.param("name");
3396
+ const entry = await findMind(name);
2885
3397
  if (!entry) return c.json({ error: "Mind not found" }, 404);
2886
3398
  const logFile = resolve9(stateDir(name), "logs", "mind.log");
2887
3399
  if (!existsSync7(logFile)) {
@@ -2931,9 +3443,9 @@ var logs_default = app11;
2931
3443
 
2932
3444
  // src/web/api/mind-skills.ts
2933
3445
  import { zValidator as zValidator4 } from "@hono/zod-validator";
2934
- import { Hono as Hono13 } from "hono";
3446
+ import { Hono as Hono14 } from "hono";
2935
3447
  import { z as z4 } from "zod";
2936
- var app12 = new Hono13().get("/:name/skills", async (c) => {
3448
+ var app12 = new Hono14().get("/:name/skills", async (c) => {
2937
3449
  const name = c.req.param("name");
2938
3450
  const entry = await findMind(name);
2939
3451
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -3022,8 +3534,8 @@ import {
3022
3534
  } from "fs";
3023
3535
  import { resolve as resolve12 } from "path";
3024
3536
  import { zValidator as zValidator5 } from "@hono/zod-validator";
3025
- import { and as and4, desc as desc3, eq as eq5, inArray, sql as sql2 } from "drizzle-orm";
3026
- import { Hono as Hono14 } from "hono";
3537
+ import { and as and5, desc as desc4, eq as eq6, sql as sql3 } from "drizzle-orm";
3538
+ import { Hono as Hono15 } from "hono";
3027
3539
  import { z as z5 } from "zod";
3028
3540
 
3029
3541
  // src/lib/consolidate.ts
@@ -3272,7 +3784,7 @@ function convertAssistantContent(content) {
3272
3784
  }
3273
3785
 
3274
3786
  // src/lib/daemon/turn-summarizer.ts
3275
- import { and as and3, desc as desc2, eq as eq4, gt, lt as lt3, sql } from "drizzle-orm";
3787
+ import { and as and4, desc as desc3, eq as eq5, gt, lt as lt3, sql as sql2 } from "drizzle-orm";
3276
3788
 
3277
3789
  // src/lib/format-tool.ts
3278
3790
  function summarizeTool(name, input) {
@@ -3292,22 +3804,22 @@ var sLog = logger_default.child("turn-summarizer");
3292
3804
  async function gatherTurnEvents(mind, session, doneId) {
3293
3805
  const db = await getDb();
3294
3806
  const conditions = [
3295
- eq4(mindHistory.mind, mind),
3296
- eq4(mindHistory.type, "done"),
3807
+ eq5(mindHistory.mind, mind),
3808
+ eq5(mindHistory.type, "done"),
3297
3809
  lt3(mindHistory.id, doneId)
3298
3810
  ];
3299
3811
  if (session) {
3300
- conditions.push(eq4(mindHistory.session, session));
3812
+ conditions.push(eq5(mindHistory.session, session));
3301
3813
  }
3302
- const prevDone = await db.select({ id: mindHistory.id }).from(mindHistory).where(and3(...conditions)).orderBy(desc2(mindHistory.id)).limit(1);
3814
+ const prevDone = await db.select({ id: mindHistory.id }).from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.id)).limit(1);
3303
3815
  const prevDoneId = prevDone.length > 0 ? prevDone[0].id : 0;
3304
3816
  const turnConditions = [
3305
- eq4(mindHistory.mind, mind),
3817
+ eq5(mindHistory.mind, mind),
3306
3818
  gt(mindHistory.id, prevDoneId),
3307
- sql`${mindHistory.id} <= ${doneId}`
3819
+ sql2`${mindHistory.id} <= ${doneId}`
3308
3820
  ];
3309
3821
  if (session) {
3310
- turnConditions.push(eq4(mindHistory.session, session));
3822
+ turnConditions.push(eq5(mindHistory.session, session));
3311
3823
  }
3312
3824
  const events = await db.select({
3313
3825
  id: mindHistory.id,
@@ -3317,7 +3829,7 @@ async function gatherTurnEvents(mind, session, doneId) {
3317
3829
  content: mindHistory.content,
3318
3830
  metadata: mindHistory.metadata,
3319
3831
  created_at: mindHistory.created_at
3320
- }).from(mindHistory).where(and3(...turnConditions)).orderBy(mindHistory.id);
3832
+ }).from(mindHistory).where(and4(...turnConditions)).orderBy(mindHistory.id);
3321
3833
  return {
3322
3834
  events,
3323
3835
  fromId: events.length > 0 ? events[0].id : doneId,
@@ -3420,7 +3932,7 @@ async function gatherTurnEventsByTurnId(turnId) {
3420
3932
  content: mindHistory.content,
3421
3933
  metadata: mindHistory.metadata,
3422
3934
  created_at: mindHistory.created_at
3423
- }).from(mindHistory).where(eq4(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
3935
+ }).from(mindHistory).where(eq5(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
3424
3936
  return {
3425
3937
  events,
3426
3938
  fromId: events.length > 0 ? events[0].id : 0,
@@ -3439,8 +3951,8 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
3439
3951
  if (turnId) {
3440
3952
  try {
3441
3953
  const db2 = await getDb();
3442
- await db2.update(mindHistory).set({ turn_id: null }).where(and3(eq4(mindHistory.turn_id, turnId), eq4(mindHistory.type, "inbound")));
3443
- await db2.update(messages).set({ turn_id: null }).where(eq4(messages.turn_id, turnId));
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));
3444
3956
  } catch (err) {
3445
3957
  sLog.error(`failed to un-tag events for interrupted turn ${turnId}`, logger_default.errorData(err));
3446
3958
  }
@@ -3600,7 +4112,7 @@ async function getMindStatus(name, port) {
3600
4112
  const manager = getMindManager();
3601
4113
  let status = "stopped";
3602
4114
  try {
3603
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
4115
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
3604
4116
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
3605
4117
  status = "sleeping";
3606
4118
  }
@@ -4027,7 +4539,7 @@ function formatTimeAgo(date) {
4027
4539
  const days = Math.floor(hours / 24);
4028
4540
  return `${days}d ago`;
4029
4541
  }
4030
- var app13 = new Hono14().post("/", requireAdminOrSystem, zValidator5("json", createMindSchema), async (c) => {
4542
+ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", createMindSchema), async (c) => {
4031
4543
  const body = c.req.valid("json");
4032
4544
  const { name } = body;
4033
4545
  const template = body.template ?? resolveTemplate(body.model);
@@ -4119,7 +4631,13 @@ The human who planted you described you as: "${body.description}"
4119
4631
  const seedSoul = body.seedSoul ? substitute(seedSoulRaw, { name, description: descLine }) : seedSoulRaw;
4120
4632
  writeFileSync7(resolve12(dest, "home/SOUL.md"), seedSoul);
4121
4633
  }
4122
- const skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4634
+ let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4635
+ if (body.stage === "seed" && !body.skills) {
4636
+ const { isImagegenEnabled } = await import("./setup-TISPCO22.js");
4637
+ if (isImagegenEnabled()) {
4638
+ skillSet = [...skillSet, "imagegen"];
4639
+ }
4640
+ }
4123
4641
  const skillWarnings = [];
4124
4642
  for (const skillId of skillSet) {
4125
4643
  try {
@@ -4129,6 +4647,33 @@ The human who planted you described you as: "${body.description}"
4129
4647
  skillWarnings.push(`Failed to install skill: ${skillId}`);
4130
4648
  }
4131
4649
  }
4650
+ if (body.stage === "seed") {
4651
+ try {
4652
+ const spiritEntry = await findMind("volute");
4653
+ if (spiritEntry) {
4654
+ const { spiritDir } = await import("./spirit-VRONKFMF.js");
4655
+ const sDir = spiritEntry.dir ?? spiritDir();
4656
+ const spiritConfig = readVoluteConfig(sDir) ?? {};
4657
+ const schedules = spiritConfig.schedules ?? [];
4658
+ const nurtureId = `nurture-${name}`;
4659
+ if (!schedules.some((s) => s.id === nurtureId)) {
4660
+ schedules.push({
4661
+ id: nurtureId,
4662
+ cron: process.env.VOLUTE_NURTURE_CRON ?? "*/5 * * * *",
4663
+ script: `volute seed check ${name}`,
4664
+ enabled: true,
4665
+ whileSleeping: "skip"
4666
+ });
4667
+ spiritConfig.schedules = schedules;
4668
+ writeVoluteConfig(sDir, spiritConfig);
4669
+ const { getScheduler: getScheduler2 } = await import("./scheduler-ZZ7XGQG6.js");
4670
+ getScheduler2().loadSchedules("volute", sDir);
4671
+ }
4672
+ }
4673
+ } catch (err) {
4674
+ logger_default.warn(`failed to add nurture schedule for ${name}`, logger_default.errorData(err));
4675
+ }
4676
+ }
4132
4677
  if (body.stage !== "seed") {
4133
4678
  const customSoul = await getPromptIfCustom("default_soul");
4134
4679
  if (customSoul) {
@@ -4300,7 +4845,7 @@ ${user.trimEnd()}
4300
4845
  const db = await getDb();
4301
4846
  const lastActiveRows = await db.select({
4302
4847
  mind: mindHistory.mind,
4303
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4848
+ lastActiveAt: sql3`MAX(${mindHistory.created_at})`
4304
4849
  }).from(mindHistory).groupBy(mindHistory.mind);
4305
4850
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4306
4851
  } catch {
@@ -4384,7 +4929,7 @@ ${user.trimEnd()}
4384
4929
  const manager = getMindManager();
4385
4930
  try {
4386
4931
  if (context?.type === "reload") {
4387
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
4932
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4388
4933
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4389
4934
  if (sleepState?.sleeping) {
4390
4935
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -4446,7 +4991,7 @@ ${user.trimEnd()}
4446
4991
  if (context?.type === "sprouted" && !entry.parent) {
4447
4992
  try {
4448
4993
  const db = await getDb();
4449
- const activeConvs = await db.select({ id: conversations.id, channel: conversations.channel }).from(conversations).where(eq5(conversations.mind_name, baseName)).all();
4994
+ const activeConvs = await db.select({ id: conversations.id, channel: conversations.channel }).from(conversations).where(eq6(conversations.mind_name, baseName)).all();
4450
4995
  for (const conv of activeConvs) {
4451
4996
  await recordInbound(baseName, conv.channel, "system", "[seed has sprouted]");
4452
4997
  await addMessage(conv.id, "assistant", "system", [
@@ -4480,7 +5025,7 @@ ${user.trimEnd()}
4480
5025
  const name = c.req.param("name");
4481
5026
  const entry = await findMind(name);
4482
5027
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4483
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
5028
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4484
5029
  const sm = getSleepManagerIfReady2();
4485
5030
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4486
5031
  return c.json(sm.getState(name));
@@ -4488,7 +5033,7 @@ ${user.trimEnd()}
4488
5033
  const name = c.req.param("name");
4489
5034
  const entry = await findMind(name);
4490
5035
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4491
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
5036
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4492
5037
  const sm = getSleepManagerIfReady2();
4493
5038
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4494
5039
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -4508,7 +5053,7 @@ ${user.trimEnd()}
4508
5053
  const name = c.req.param("name");
4509
5054
  const entry = await findMind(name);
4510
5055
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4511
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
5056
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4512
5057
  const sm = getSleepManagerIfReady2();
4513
5058
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4514
5059
  const sleepState = sm.getState(name);
@@ -4523,11 +5068,98 @@ ${user.trimEnd()}
4523
5068
  const name = c.req.param("name");
4524
5069
  const entry = await findMind(name);
4525
5070
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4526
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
5071
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
4527
5072
  const sm = getSleepManagerIfReady2();
4528
5073
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4529
5074
  const flushed = await sm.flushQueuedMessages(name);
4530
5075
  return c.json({ ok: true, flushed });
5076
+ }).patch("/:name/profile", requireSelf(), async (c) => {
5077
+ const name = c.req.param("name");
5078
+ const entry = await findMind(name);
5079
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5080
+ const body = await c.req.json();
5081
+ const dir = entry.dir ?? mindDir(name);
5082
+ const config2 = readVoluteConfig(dir) ?? {};
5083
+ const profile = config2.profile ?? {};
5084
+ if (body.displayName !== void 0) profile.displayName = body.displayName;
5085
+ if (body.description !== void 0) profile.description = body.description;
5086
+ if (body.avatar !== void 0) profile.avatar = body.avatar;
5087
+ config2.profile = profile;
5088
+ writeVoluteConfig(dir, config2);
5089
+ const { syncMindProfile } = await import("./auth-GKCDSO4T.js");
5090
+ await syncMindProfile(name, profile);
5091
+ broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
5092
+ return c.json({ ok: true });
5093
+ }).get("/:name/seed-check", requireSelf(), async (c) => {
5094
+ const name = c.req.param("name");
5095
+ const entry = await findMind(name);
5096
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
5097
+ if (entry.stage !== "seed") return c.json({ output: "" });
5098
+ const db = await getDb();
5099
+ const ORIENTATION_MARKER = "You don't have a soul yet";
5100
+ const rawCreator = Number(process.env.VOLUTE_NURTURE_CREATOR_MINUTES);
5101
+ const creatorThreshold = Number.isNaN(rawCreator) ? 5 : rawCreator;
5102
+ const rawSpirit = Number(process.env.VOLUTE_NURTURE_SPIRIT_MINUTES);
5103
+ const spiritThreshold = Number.isNaN(rawSpirit) ? 15 : rawSpirit;
5104
+ const lastCreatorMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
5105
+ and5(
5106
+ eq6(mindHistory.mind, name),
5107
+ eq6(mindHistory.type, "inbound"),
5108
+ sql3`${mindHistory.sender} != 'volute'`,
5109
+ sql3`${mindHistory.sender} != ${name}`,
5110
+ sql3`${mindHistory.sender} IS NOT NULL`
5111
+ )
5112
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
5113
+ const lastSpiritMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
5114
+ and5(
5115
+ eq6(mindHistory.mind, name),
5116
+ eq6(mindHistory.type, "inbound"),
5117
+ eq6(mindHistory.sender, "volute")
5118
+ )
5119
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
5120
+ const now = Date.now();
5121
+ const creatorTime = lastCreatorMsg[0] ? new Date(lastCreatorMsg[0].created_at).getTime() : 0;
5122
+ const spiritTime = lastSpiritMsg[0] ? new Date(lastSpiritMsg[0].created_at).getTime() : 0;
5123
+ const minutesSinceCreator = creatorTime ? (now - creatorTime) / 6e4 : Infinity;
5124
+ const minutesSinceSpirit = spiritTime ? (now - spiritTime) / 6e4 : Infinity;
5125
+ if (minutesSinceCreator < creatorThreshold && minutesSinceSpirit < spiritThreshold) {
5126
+ return c.json({ output: "" });
5127
+ }
5128
+ const dir = entry.dir ?? mindDir(name);
5129
+ const soulPath = resolve12(dir, "home/SOUL.md");
5130
+ const memoryPath = resolve12(dir, "home/MEMORY.md");
5131
+ const soulCustom = existsSync9(soulPath) && !readFileSync10(soulPath, "utf-8").includes(ORIENTATION_MARKER);
5132
+ const memoryWritten = existsSync9(memoryPath) && readFileSync10(memoryPath, "utf-8").trim().length > 0;
5133
+ const config2 = readVoluteConfig(dir);
5134
+ const displayNameSet = !!config2?.profile?.displayName;
5135
+ const avatarSet = !!config2?.profile?.avatar;
5136
+ const { isImagegenEnabled } = await import("./setup-TISPCO22.js");
5137
+ const imagegenEnabled = isImagegenEnabled();
5138
+ const done = [];
5139
+ const remaining = [];
5140
+ if (soulCustom) done.push("SOUL.md written");
5141
+ else remaining.push("Write SOUL.md");
5142
+ if (memoryWritten) done.push("MEMORY.md written");
5143
+ else remaining.push("Write MEMORY.md");
5144
+ if (displayNameSet) done.push("Display name set");
5145
+ else remaining.push("Set display name");
5146
+ if (imagegenEnabled) {
5147
+ if (avatarSet) done.push("Avatar set");
5148
+ else remaining.push("Generate and set avatar");
5149
+ }
5150
+ const creatorStatus = minutesSinceCreator === Infinity ? "No creator messages yet" : `Last creator message: ${Math.round(minutesSinceCreator)} minutes ago`;
5151
+ const lines = [`Seed: ${name}`, creatorStatus];
5152
+ if (done.length > 0) lines.push(`Done: ${done.join(", ")}`);
5153
+ if (remaining.length > 0) lines.push(`Remaining: ${remaining.join(", ")}`);
5154
+ if (remaining.length > 0) {
5155
+ lines.push("", `DM the seed to encourage them: echo "message" | volute chat send @${name}`);
5156
+ } else {
5157
+ lines.push(
5158
+ "",
5159
+ `All checklist items complete \u2014 the seed can run \`volute seed sprout\` when ready.`
5160
+ );
5161
+ }
5162
+ return c.json({ output: lines.join("\n") });
4531
5163
  }).post("/:name/sprout", requireSelf(), async (c) => {
4532
5164
  const name = c.req.param("name");
4533
5165
  const entry = await findMind(name);
@@ -4536,6 +5168,24 @@ ${user.trimEnd()}
4536
5168
  return c.json({ error: `Mind is not a seed (stage: ${entry.stage})` }, 409);
4537
5169
  }
4538
5170
  await setMindStage(name, "sprouted");
5171
+ try {
5172
+ const spiritEntry = await findMind("volute");
5173
+ if (spiritEntry) {
5174
+ const { spiritDir } = await import("./spirit-VRONKFMF.js");
5175
+ const sDir = spiritEntry.dir ?? spiritDir();
5176
+ const spiritConfig = readVoluteConfig(sDir);
5177
+ if (spiritConfig?.schedules) {
5178
+ const nurtureId = `nurture-${name}`;
5179
+ spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
5180
+ if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
5181
+ writeVoluteConfig(sDir, spiritConfig);
5182
+ const { getScheduler: getScheduler2 } = await import("./scheduler-ZZ7XGQG6.js");
5183
+ getScheduler2().loadSchedules("volute", sDir);
5184
+ }
5185
+ }
5186
+ } catch (err) {
5187
+ logger_default.warn(`failed to clean up nurture schedule for ${name}`, logger_default.errorData(err));
5188
+ }
4539
5189
  return c.json({ ok: true });
4540
5190
  }).delete("/:name", requireAdmin, async (c) => {
4541
5191
  const name = c.req.param("name");
@@ -4953,7 +5603,7 @@ ${user.trimEnd()}
4953
5603
  if (!body.systemPrompt || !body.message) {
4954
5604
  return c.json({ error: "systemPrompt and message required" }, 400);
4955
5605
  }
4956
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-ZIPCV3MX.js");
5606
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-SBY2WG7O.js");
4957
5607
  if (!isAiConfigured()) {
4958
5608
  return c.json({ error: "AI service not configured" }, 503);
4959
5609
  }
@@ -5003,6 +5653,8 @@ ${user.trimEnd()}
5003
5653
  }
5004
5654
  }
5005
5655
  }
5656
+ const MARKER_RE = /\[volute:(?:outbound|activity):\d+\]/g;
5657
+ const cleanContent = body.type === "tool_result" && body.content ? body.content.replace(MARKER_RE, "").trimEnd() : body.content;
5006
5658
  const db = await getDb();
5007
5659
  let insertedId;
5008
5660
  try {
@@ -5012,7 +5664,7 @@ ${user.trimEnd()}
5012
5664
  session: body.session ?? null,
5013
5665
  channel: body.channel ?? null,
5014
5666
  message_id: body.messageId ?? null,
5015
- content: body.content ?? null,
5667
+ content: cleanContent ?? null,
5016
5668
  metadata: body.metadata ? JSON.stringify(body.metadata) : null,
5017
5669
  turn_id: turnId ?? null
5018
5670
  }).returning({ id: mindHistory.id });
@@ -5023,13 +5675,21 @@ ${user.trimEnd()}
5023
5675
  if (body.type === "tool_use" && insertedId != null) {
5024
5676
  trackToolUse(baseName, body.session, insertedId);
5025
5677
  }
5678
+ if (body.type === "tool_result" && turnId && body.content) {
5679
+ const toolUseEventId = getLastToolUseEventId(baseName, body.session);
5680
+ try {
5681
+ await linkToolResultToTurn(baseName, turnId, body.content, toolUseEventId);
5682
+ } catch (err) {
5683
+ logger_default.error("failed to link outbound to turn", logger_default.errorData(err));
5684
+ }
5685
+ }
5026
5686
  publish2(baseName, {
5027
5687
  mind: baseName,
5028
5688
  type: body.type,
5029
5689
  session: body.session,
5030
5690
  channel: body.channel,
5031
5691
  messageId: body.messageId,
5032
- content: body.content,
5692
+ content: cleanContent,
5033
5693
  metadata: body.metadata,
5034
5694
  turnId: turnId ?? void 0
5035
5695
  });
@@ -5056,6 +5716,11 @@ ${user.trimEnd()}
5056
5716
  const busy = body.session ? dm.isSessionBusy(baseName, body.session) : false;
5057
5717
  if (!busy) {
5058
5718
  const completedTurnId = await completeTurn(baseName, body.session);
5719
+ if (completedTurnId) {
5720
+ tagUntaggedOutbound(baseName, completedTurnId).catch(
5721
+ (err) => logger_default.warn("failed to tag orphaned outbound records", logger_default.errorData(err))
5722
+ );
5723
+ }
5059
5724
  if (insertedId != null) {
5060
5725
  summarizeTurn(baseName, body.session, body.channel, insertedId, completedTurnId).catch(
5061
5726
  (err) => logger_default.error("turn summarization failed", logger_default.errorData(err))
@@ -5067,6 +5732,11 @@ ${user.trimEnd()}
5067
5732
  logger_default.error("turn completion check failed", logger_default.errorData(err));
5068
5733
  }
5069
5734
  const completedTurnId = await completeTurn(baseName, body.session);
5735
+ if (completedTurnId) {
5736
+ tagUntaggedOutbound(baseName, completedTurnId).catch(
5737
+ (err2) => logger_default.warn("failed to tag orphaned outbound records", logger_default.errorData(err2))
5738
+ );
5739
+ }
5070
5740
  if (insertedId != null) {
5071
5741
  summarizeTurn(baseName, body.session, body.channel, insertedId, completedTurnId).catch(
5072
5742
  (err2) => logger_default.error("turn summarization failed", logger_default.errorData(err2))
@@ -5167,228 +5837,49 @@ ${user.trimEnd()}
5167
5837
  const db = await getDb();
5168
5838
  const rows = await db.select({
5169
5839
  session: mindHistory.session,
5170
- started_at: sql2`MIN(${mindHistory.created_at})`,
5171
- event_count: sql2`COUNT(*)`,
5172
- message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
5173
- tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
5174
- }).from(mindHistory).where(and4(eq5(mindHistory.mind, name), sql2`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql2`MIN(${mindHistory.created_at}) DESC`);
5840
+ started_at: sql3`MIN(${mindHistory.created_at})`,
5841
+ event_count: sql3`COUNT(*)`,
5842
+ message_count: sql3`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
5843
+ tool_count: sql3`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
5844
+ }).from(mindHistory).where(and5(eq6(mindHistory.mind, name), sql3`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql3`MIN(${mindHistory.created_at}) DESC`);
5175
5845
  return c.json(rows);
5176
5846
  }).get("/:name/history/channels", async (c) => {
5177
5847
  const name = c.req.param("name");
5178
5848
  const db = await getDb();
5179
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq5(mindHistory.mind, name));
5849
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq6(mindHistory.mind, name));
5180
5850
  return c.json(rows.map((r) => r.channel));
5181
5851
  }).get("/:name/history/export", async (c) => {
5182
5852
  const name = c.req.param("name");
5183
5853
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5184
5854
  const db = await getDb();
5185
- const rows = await db.select().from(mindHistory).where(eq5(mindHistory.mind, name));
5855
+ const rows = await db.select().from(mindHistory).where(eq6(mindHistory.mind, name));
5186
5856
  return c.json(rows);
5187
- }).get("/:name/history/turns", async (c) => {
5857
+ }).get("/:name/history/turn", async (c) => {
5188
5858
  const name = c.req.param("name");
5189
- const turnIdFilter = c.req.query("turnId");
5190
- const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
5191
- const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
5192
- const db = await getDb();
5193
- const conditions = [eq5(turns.mind, name)];
5194
- if (turnIdFilter) conditions.push(eq5(turns.id, turnIdFilter));
5195
- const turnRows = await db.select().from(turns).where(and4(...conditions)).orderBy(desc3(turns.created_at)).limit(limit).offset(offset);
5196
- if (turnRows.length === 0) return c.json([]);
5197
- const turnIds = turnRows.map((t) => t.id);
5198
- const summaryRows = await db.select().from(mindHistory).where(
5199
- and4(
5200
- eq5(mindHistory.mind, name),
5201
- eq5(mindHistory.type, "summary"),
5202
- inArray(mindHistory.turn_id, turnIds)
5203
- )
5204
- );
5205
- const summaryByTurn = /* @__PURE__ */ new Map();
5206
- for (const s of summaryRows) {
5207
- if (s.turn_id)
5208
- summaryByTurn.set(s.turn_id, { content: s.content ?? "", metadata: s.metadata });
5209
- }
5210
- const msgRows = await db.select({
5211
- id: messages.id,
5212
- conversation_id: messages.conversation_id,
5213
- role: messages.role,
5214
- sender_name: messages.sender_name,
5215
- content: messages.content,
5216
- source_event_id: messages.source_event_id,
5217
- turn_id: messages.turn_id,
5218
- created_at: messages.created_at
5219
- }).from(messages).where(inArray(messages.turn_id, turnIds)).orderBy(messages.created_at);
5220
- const msgsByTurnConv = /* @__PURE__ */ new Map();
5221
- const convIds = /* @__PURE__ */ new Set();
5222
- for (const m of msgRows) {
5223
- if (!m.turn_id) continue;
5224
- let byConv = msgsByTurnConv.get(m.turn_id);
5225
- if (!byConv) {
5226
- byConv = /* @__PURE__ */ new Map();
5227
- msgsByTurnConv.set(m.turn_id, byConv);
5228
- }
5229
- let arr = byConv.get(m.conversation_id);
5230
- if (!arr) {
5231
- arr = [];
5232
- byConv.set(m.conversation_id, arr);
5233
- }
5234
- arr.push(m);
5235
- convIds.add(m.conversation_id);
5236
- }
5237
- const convMeta = /* @__PURE__ */ new Map();
5238
- const convParticipantsMap = /* @__PURE__ */ new Map();
5239
- if (convIds.size > 0) {
5240
- const convIdsArr = [...convIds];
5241
- const convRows = await db.select({
5242
- id: conversations.id,
5243
- type: conversations.type,
5244
- name: conversations.name,
5245
- title: conversations.title
5246
- }).from(conversations).where(inArray(conversations.id, convIdsArr));
5247
- for (const c2 of convRows) convMeta.set(c2.id, c2);
5248
- const cpRows = await db.select({
5249
- conversationId: conversationParticipants.conversation_id,
5250
- username: users.username,
5251
- displayName: users.display_name
5252
- }).from(conversationParticipants).innerJoin(users, eq5(conversationParticipants.user_id, users.id)).where(inArray(conversationParticipants.conversation_id, convIdsArr));
5253
- for (const cp of cpRows) {
5254
- let arr = convParticipantsMap.get(cp.conversationId);
5255
- if (!arr) {
5256
- arr = [];
5257
- convParticipantsMap.set(cp.conversationId, arr);
5258
- }
5259
- arr.push({ username: cp.username, displayName: cp.displayName });
5260
- }
5261
- }
5262
- function getConvLabel(convId) {
5263
- const meta = convMeta.get(convId);
5264
- if (!meta) return "Conversation";
5265
- if (meta.type === "channel" && meta.name) return `#${meta.name}`;
5266
- const parts = convParticipantsMap.get(convId) ?? [];
5267
- if (meta.type === "dm" && parts.length === 2) {
5268
- const other = parts.find((p) => p.username !== name);
5269
- if (other) return `@${other.displayName || other.username}`;
5270
- }
5271
- if (meta.title) return meta.title;
5272
- return "Conversation";
5273
- }
5274
- const activityRows = await db.select().from(activity).where(inArray(activity.turn_id, turnIds)).orderBy(activity.created_at);
5275
- const activitiesByTurn = /* @__PURE__ */ new Map();
5276
- for (const a of activityRows) {
5277
- if (!a.turn_id) continue;
5278
- let arr = activitiesByTurn.get(a.turn_id);
5279
- if (!arr) {
5280
- arr = [];
5281
- activitiesByTurn.set(a.turn_id, arr);
5282
- }
5283
- arr.push(a);
5284
- }
5285
- const triggerIds = turnRows.filter((t) => t.trigger_event_id != null).map((t) => t.trigger_event_id);
5286
- const triggerMap = /* @__PURE__ */ new Map();
5287
- if (triggerIds.length > 0) {
5288
- const triggerRows = await db.select({
5289
- id: mindHistory.id,
5290
- channel: mindHistory.channel,
5291
- sender: mindHistory.sender,
5292
- content: mindHistory.content
5293
- }).from(mindHistory).where(inArray(mindHistory.id, triggerIds));
5294
- for (const r of triggerRows) triggerMap.set(r.id, r);
5295
- }
5296
- const result = turnRows.map((t) => {
5297
- const summary = summaryByTurn.get(t.id);
5298
- const turnConvs = msgsByTurnConv.get(t.id) ?? /* @__PURE__ */ new Map();
5299
- const convEntries = [...turnConvs.entries()].map(([convId, msgs]) => {
5300
- const meta = convMeta.get(convId);
5301
- return {
5302
- id: convId,
5303
- label: getConvLabel(convId),
5304
- type: meta?.type ?? "dm",
5305
- messages: msgs.map((m) => {
5306
- let content;
5307
- try {
5308
- const parsed = JSON.parse(m.content);
5309
- content = Array.isArray(parsed) ? parsed : [{ type: "text", text: m.content }];
5310
- } catch {
5311
- content = [{ type: "text", text: m.content }];
5312
- }
5313
- return {
5314
- id: m.id,
5315
- role: m.role,
5316
- sender_name: m.sender_name,
5317
- content,
5318
- source_event_id: m.source_event_id,
5319
- created_at: m.created_at
5320
- };
5321
- })
5322
- };
5323
- });
5324
- const turnActivities = (activitiesByTurn.get(t.id) ?? []).map((a) => {
5325
- let metadata = null;
5326
- if (a.metadata) {
5327
- try {
5328
- metadata = JSON.parse(a.metadata);
5329
- } catch (err) {
5330
- logger_default.debug(`malformed activity metadata for activity ${a.id}`, logger_default.errorData(err));
5331
- }
5332
- }
5333
- return {
5334
- id: a.id,
5335
- type: a.type,
5336
- summary: a.summary,
5337
- metadata,
5338
- source_event_id: a.source_event_id,
5339
- created_at: a.created_at
5340
- };
5341
- });
5342
- let summaryMeta = null;
5343
- if (summary?.metadata) {
5344
- try {
5345
- summaryMeta = JSON.parse(summary.metadata);
5346
- } catch (err) {
5347
- logger_default.debug(`malformed summary metadata for turn ${t.id}`, logger_default.errorData(err));
5348
- }
5349
- }
5350
- const trigger = t.trigger_event_id ? triggerMap.get(t.trigger_event_id) : null;
5351
- return {
5352
- id: t.id,
5353
- summary: summary?.content ?? null,
5354
- summary_meta: summaryMeta,
5355
- status: t.status,
5356
- created_at: t.created_at,
5357
- trigger: trigger ? { channel: trigger.channel, sender: trigger.sender, content: trigger.content } : null,
5358
- conversations: convEntries,
5359
- activities: turnActivities
5360
- };
5361
- });
5362
- return c.json(result);
5363
- }).get("/:name/history/turn", async (c) => {
5364
- const name = c.req.param("name");
5365
- const turnId = c.req.query("turn_id");
5859
+ const turnId = c.req.query("turn_id");
5860
+ const detail = c.req.query("detail") === "1";
5366
5861
  const db = await getDb();
5862
+ const typeFilter = detail ? void 0 : sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking','activity')`;
5863
+ let rows;
5367
5864
  if (turnId) {
5368
- const rows2 = await db.select().from(mindHistory).where(
5369
- and4(
5370
- eq5(mindHistory.mind, name),
5371
- eq5(mindHistory.turn_id, turnId),
5372
- sql2`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
5865
+ rows = await db.select().from(mindHistory).where(and5(eq6(mindHistory.mind, name), eq6(mindHistory.turn_id, turnId), typeFilter)).orderBy(mindHistory.id);
5866
+ } else {
5867
+ const session = c.req.query("session");
5868
+ const fromId = parseInt(c.req.query("from_id") ?? "", 10);
5869
+ const toId = parseInt(c.req.query("to_id") ?? "", 10);
5870
+ if (!session || Number.isNaN(fromId) || Number.isNaN(toId)) {
5871
+ return c.json({ error: "turn_id, or session with from_id and to_id, required" }, 400);
5872
+ }
5873
+ rows = await db.select().from(mindHistory).where(
5874
+ and5(
5875
+ eq6(mindHistory.mind, name),
5876
+ eq6(mindHistory.session, session),
5877
+ sql3`${mindHistory.id} >= ${fromId}`,
5878
+ sql3`${mindHistory.id} <= ${toId}`,
5879
+ typeFilter
5373
5880
  )
5374
5881
  ).orderBy(mindHistory.id);
5375
- return c.json(rows2);
5376
5882
  }
5377
- const session = c.req.query("session");
5378
- const fromId = parseInt(c.req.query("from_id") ?? "", 10);
5379
- const toId = parseInt(c.req.query("to_id") ?? "", 10);
5380
- if (!session || Number.isNaN(fromId) || Number.isNaN(toId)) {
5381
- return c.json({ error: "turn_id, or session with from_id and to_id, required" }, 400);
5382
- }
5383
- const rows = await db.select().from(mindHistory).where(
5384
- and4(
5385
- eq5(mindHistory.mind, name),
5386
- eq5(mindHistory.session, session),
5387
- sql2`${mindHistory.id} >= ${fromId}`,
5388
- sql2`${mindHistory.id} <= ${toId}`,
5389
- sql2`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
5390
- )
5391
- ).orderBy(mindHistory.id);
5392
5883
  return c.json(rows);
5393
5884
  }).get("/:name/history/cross-session", async (c) => {
5394
5885
  const name = c.req.param("name");
@@ -5397,14 +5888,14 @@ ${user.trimEnd()}
5397
5888
  let sinceTimestamp = null;
5398
5889
  if (currentSession) {
5399
5890
  const lastTurn = await db.select({ turn_id: mindHistory.turn_id }).from(mindHistory).where(
5400
- and4(
5401
- eq5(mindHistory.mind, name),
5402
- eq5(mindHistory.session, currentSession),
5403
- sql2`${mindHistory.turn_id} IS NOT NULL`
5891
+ and5(
5892
+ eq6(mindHistory.mind, name),
5893
+ eq6(mindHistory.session, currentSession),
5894
+ sql3`${mindHistory.turn_id} IS NOT NULL`
5404
5895
  )
5405
- ).orderBy(desc3(mindHistory.created_at)).limit(1);
5896
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
5406
5897
  if (lastTurn.length > 0 && lastTurn[0].turn_id) {
5407
- const firstEvent = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(eq5(mindHistory.turn_id, lastTurn[0].turn_id)).orderBy(mindHistory.created_at).limit(1);
5898
+ const firstEvent = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(eq6(mindHistory.turn_id, lastTurn[0].turn_id)).orderBy(mindHistory.created_at).limit(1);
5408
5899
  if (firstEvent.length > 0) {
5409
5900
  sinceTimestamp = firstEvent[0].created_at;
5410
5901
  }
@@ -5414,18 +5905,18 @@ ${user.trimEnd()}
5414
5905
  sinceTimestamp = new Date(Date.now() - 36e5).toISOString().replace("T", " ").slice(0, 19);
5415
5906
  }
5416
5907
  const conditions = [
5417
- eq5(mindHistory.mind, name),
5418
- eq5(mindHistory.type, "summary"),
5419
- sql2`${mindHistory.created_at} > ${sinceTimestamp}`
5908
+ eq6(mindHistory.mind, name),
5909
+ eq6(mindHistory.type, "summary"),
5910
+ sql3`${mindHistory.created_at} > ${sinceTimestamp}`
5420
5911
  ];
5421
5912
  if (currentSession) {
5422
- conditions.push(sql2`${mindHistory.session} != ${currentSession}`);
5913
+ conditions.push(sql3`${mindHistory.session} != ${currentSession}`);
5423
5914
  }
5424
5915
  const rows = await db.select({
5425
5916
  session: mindHistory.session,
5426
5917
  content: mindHistory.content,
5427
5918
  created_at: mindHistory.created_at
5428
- }).from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(50);
5919
+ }).from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(50);
5429
5920
  if (rows.length === 0) {
5430
5921
  return c.json({ context: null });
5431
5922
  }
@@ -5445,40 +5936,40 @@ ${lines.join("\n")}` });
5445
5936
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
5446
5937
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
5447
5938
  const db = await getDb();
5448
- const conditions = [eq5(mindHistory.mind, name)];
5939
+ const conditions = [eq6(mindHistory.mind, name)];
5449
5940
  if (channel) {
5450
- conditions.push(eq5(mindHistory.channel, channel));
5941
+ conditions.push(eq6(mindHistory.channel, channel));
5451
5942
  }
5452
5943
  if (session) {
5453
- conditions.push(eq5(mindHistory.session, session));
5944
+ conditions.push(eq6(mindHistory.session, session));
5454
5945
  }
5455
5946
  const effectivePreset = full ? "all" : preset;
5456
5947
  switch (effectivePreset) {
5457
5948
  case "all":
5458
5949
  break;
5459
5950
  case "conversation":
5460
- conditions.push(sql2`${mindHistory.type} IN ('summary','inbound','outbound','tool_use')`);
5951
+ conditions.push(sql3`${mindHistory.type} IN ('summary','inbound','outbound','tool_use')`);
5461
5952
  break;
5462
5953
  case "detailed":
5463
5954
  conditions.push(
5464
- sql2`${mindHistory.type} IN ('summary','inbound','outbound','tool_use','tool_result','text','thinking')`
5955
+ sql3`${mindHistory.type} IN ('summary','inbound','outbound','tool_use','tool_result','text','thinking')`
5465
5956
  );
5466
5957
  break;
5467
5958
  default:
5468
- conditions.push(sql2`${mindHistory.type} IN ('summary')`);
5959
+ conditions.push(sql3`${mindHistory.type} IN ('summary')`);
5469
5960
  break;
5470
5961
  }
5471
- const rows = await db.select().from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
5962
+ const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(limit).offset(offset);
5472
5963
  return c.json(rows);
5473
5964
  });
5474
5965
  var minds_default = app13;
5475
5966
 
5476
5967
  // src/web/api/prompts.ts
5477
5968
  import { zValidator as zValidator6 } from "@hono/zod-validator";
5478
- import { eq as eq6, sql as sql3 } from "drizzle-orm";
5479
- import { Hono as Hono15 } from "hono";
5969
+ import { eq as eq7, sql as sql4 } from "drizzle-orm";
5970
+ import { Hono as Hono16 } from "hono";
5480
5971
  import { z as z6 } from "zod";
5481
- var app14 = new Hono15().get("/", async (c) => {
5972
+ var app14 = new Hono16().get("/", async (c) => {
5482
5973
  let rows;
5483
5974
  try {
5484
5975
  const db = await getDb();
@@ -5508,9 +5999,9 @@ var app14 = new Hono15().get("/", async (c) => {
5508
5999
  }
5509
6000
  const { content } = c.req.valid("json");
5510
6001
  const db = await getDb();
5511
- await db.insert(systemPrompts).values({ key, content, updated_at: sql3`(datetime('now'))` }).onConflictDoUpdate({
6002
+ await db.insert(systemPrompts).values({ key, content, updated_at: sql4`(datetime('now'))` }).onConflictDoUpdate({
5512
6003
  target: systemPrompts.key,
5513
- set: { content, updated_at: sql3`(datetime('now'))` }
6004
+ set: { content, updated_at: sql4`(datetime('now'))` }
5514
6005
  });
5515
6006
  return c.json({ ok: true });
5516
6007
  }).delete("/:key", requireAdmin, async (c) => {
@@ -5519,7 +6010,7 @@ var app14 = new Hono15().get("/", async (c) => {
5519
6010
  return c.json({ error: "Unknown prompt key" }, 404);
5520
6011
  }
5521
6012
  const db = await getDb();
5522
- await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
6013
+ await db.delete(systemPrompts).where(eq7(systemPrompts.key, key));
5523
6014
  return c.json({ ok: true });
5524
6015
  });
5525
6016
  var prompts_default = app14;
@@ -5527,7 +6018,7 @@ var prompts_default = app14;
5527
6018
  // src/web/api/public-files.ts
5528
6019
  import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
5529
6020
  import { extname as extname3, resolve as resolve13 } from "path";
5530
- import { Hono as Hono16 } from "hono";
6021
+ import { Hono as Hono17 } from "hono";
5531
6022
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
5532
6023
  async function resolvePublicRoot(name) {
5533
6024
  if (name === "_system") return resolve13(voluteHome(), "shared");
@@ -5568,7 +6059,7 @@ async function listDir(dirPath) {
5568
6059
  type: e.isDirectory() ? "directory" : "file"
5569
6060
  }));
5570
6061
  }
5571
- var app15 = new Hono16().get("/:name/", async (c) => {
6062
+ var app15 = new Hono17().get("/:name/", async (c) => {
5572
6063
  const name = c.req.param("name");
5573
6064
  const publicRoot = await resolvePublicRoot(name);
5574
6065
  if (!publicRoot) return c.json({ error: "Not found" }, 404);
@@ -5615,7 +6106,7 @@ var public_files_default = app15;
5615
6106
 
5616
6107
  // src/web/api/schedules.ts
5617
6108
  import { CronExpressionParser } from "cron-parser";
5618
- import { Hono as Hono17 } from "hono";
6109
+ import { Hono as Hono18 } from "hono";
5619
6110
  var slog2 = logger_default.child("schedules");
5620
6111
  function readSchedules(name) {
5621
6112
  return readVoluteConfig(mindDir(name))?.schedules ?? [];
@@ -5633,7 +6124,7 @@ function writeSchedules(name, schedules) {
5633
6124
  data: { schedules }
5634
6125
  });
5635
6126
  }
5636
- var app16 = new Hono17().get("/:name/clock/status", async (c) => {
6127
+ var app16 = new Hono18().get("/:name/clock/status", async (c) => {
5637
6128
  const name = c.req.param("name");
5638
6129
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5639
6130
  const sleepManager = getSleepManagerIfReady();
@@ -5643,6 +6134,7 @@ var app16 = new Hono17().get("/:name/clock/status", async (c) => {
5643
6134
  const now = /* @__PURE__ */ new Date();
5644
6135
  const in24h = new Date(now.getTime() + 24 * 60 * 6e4);
5645
6136
  const upcoming = [];
6137
+ const previous = [];
5646
6138
  for (const s of schedules) {
5647
6139
  if (!s.enabled) continue;
5648
6140
  if (s.fireAt) {
@@ -5660,10 +6152,17 @@ var app16 = new Hono17().get("/:name/clock/status", async (c) => {
5660
6152
  } catch {
5661
6153
  slog2.warn(`invalid cron "${s.cron}" for schedule "${s.id}" of ${name}`);
5662
6154
  }
6155
+ try {
6156
+ const prevInterval = CronExpressionParser.parse(s.cron);
6157
+ const prev = prevInterval.prev().toDate();
6158
+ previous.push({ id: s.id, at: prev.toISOString() });
6159
+ } catch {
6160
+ }
5663
6161
  }
5664
6162
  }
5665
6163
  upcoming.sort((a, b) => a.at.localeCompare(b.at));
5666
- return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming });
6164
+ previous.sort((a, b) => b.at.localeCompare(a.at));
6165
+ return c.json({ sleep: sleepState, sleepConfig, schedules, upcoming, previous });
5667
6166
  }).get("/:name/schedules", async (c) => {
5668
6167
  const name = c.req.param("name");
5669
6168
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
@@ -5791,7 +6290,7 @@ var app16 = new Hono17().get("/:name/clock/status", async (c) => {
5791
6290
  const body = await c.req.text();
5792
6291
  const message = `[webhook: ${event}] ${body}`;
5793
6292
  try {
5794
- const { sendSystemMessage } = await import("./system-chat-NPYFYZVI.js");
6293
+ const { sendSystemMessage } = await import("./system-chat-JAPOJ3KE.js");
5795
6294
  await sendSystemMessage(name, message);
5796
6295
  return c.json({ ok: true });
5797
6296
  } catch (err) {
@@ -5805,10 +6304,10 @@ var schedules_default = app16;
5805
6304
  import { mkdirSync as mkdirSync7 } from "fs";
5806
6305
  import { homedir as homedir2 } from "os";
5807
6306
  import { resolve as resolve14 } from "path";
5808
- import { Hono as Hono18 } from "hono";
6307
+ import { Hono as Hono19 } from "hono";
5809
6308
  import { setCookie as setCookie2 } from "hono/cookie";
5810
6309
  var DEFAULT_API_URL2 = "https://volute.systems";
5811
- var setup = new Hono18();
6310
+ var setup = new Hono19();
5812
6311
  function writeSetupConfig(systemName, description) {
5813
6312
  const configHome = process.env.VOLUTE_HOME ?? resolve14(homedir2(), ".volute");
5814
6313
  const mindsDir = resolve14(configHome, "minds");
@@ -5844,7 +6343,7 @@ setup.get("/status", async (c) => {
5844
6343
  let hasAccount = false;
5845
6344
  if (hasSystem) {
5846
6345
  try {
5847
- const { listUsersByType: listUsersByType2 } = await import("./auth-6DMGES3I.js");
6346
+ const { listUsersByType: listUsersByType2 } = await import("./auth-GKCDSO4T.js");
5848
6347
  const brains = await listUsersByType2("brain");
5849
6348
  hasAccount = brains.length > 0;
5850
6349
  } catch (err) {
@@ -6029,7 +6528,7 @@ setup.post("/account", async (c) => {
6029
6528
  }
6030
6529
  }
6031
6530
  try {
6032
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-6DMGES3I.js");
6531
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-GKCDSO4T.js");
6033
6532
  const user = await createUser2(body.username.trim(), body.password);
6034
6533
  if (body.displayName?.trim()) {
6035
6534
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6075,13 +6574,13 @@ setup.post("/models", async (c) => {
6075
6574
  return c.json({ error: "Spirit model is required" }, 400);
6076
6575
  }
6077
6576
  try {
6078
- const { setEnabledModels: setEnabledModels2, setUtilityModel } = await import("./ai-service-ZIPCV3MX.js");
6079
- setEnabledModels2(body.models);
6577
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-SBY2WG7O.js");
6578
+ setEnabledModels3(body.models);
6080
6579
  const config2 = readGlobalConfig();
6081
6580
  config2.spiritModel = body.spiritModel.trim();
6082
6581
  writeGlobalConfig(config2);
6083
6582
  if (body.utilityModel?.trim()) {
6084
- setUtilityModel(body.utilityModel.trim());
6583
+ setUtilityModel2(body.utilityModel.trim());
6085
6584
  }
6086
6585
  return c.json({ ok: true });
6087
6586
  } catch (err) {
@@ -6093,8 +6592,8 @@ setup.post("/complete", async (c) => {
6093
6592
  return c.json({ error: "Setup already complete" }, 400);
6094
6593
  }
6095
6594
  try {
6096
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-N4W4UQRH.js");
6097
- const { startSpiritFull } = await import("./mind-service-AV273WT4.js");
6595
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-VRONKFMF.js");
6596
+ const { startSpiritFull } = await import("./mind-service-2MQ6UK5N.js");
6098
6597
  await ensureSpiritProject();
6099
6598
  await syncSpiritTemplate();
6100
6599
  const warnings = [];
@@ -6110,8 +6609,8 @@ setup.post("/complete", async (c) => {
6110
6609
  }
6111
6610
  let spiritConversationId;
6112
6611
  try {
6113
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-6DMGES3I.js");
6114
- const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-3O5O6AS3.js");
6612
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-GKCDSO4T.js");
6613
+ const { createConversation: createConversation6, findDMConversation: findDMConversation2 } = await import("./conversations-AWI5SZW2.js");
6115
6614
  const spiritUser = await getOrCreateMindUser2("volute");
6116
6615
  const brains = await listUsersByType2("brain");
6117
6616
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6134,8 +6633,8 @@ setup.post("/complete", async (c) => {
6134
6633
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6135
6634
  if (spiritConversationId && spiritStarted) {
6136
6635
  try {
6137
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-2FIM7QKO.js");
6138
- const { listUsersByType: listUsers6 } = await import("./auth-6DMGES3I.js");
6636
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-DFF5SJRM.js");
6637
+ const { listUsersByType: listUsers6 } = await import("./auth-GKCDSO4T.js");
6139
6638
  const admins = await listUsers6("brain");
6140
6639
  const admin2 = admins.find((u) => u.role === "admin");
6141
6640
  const adminName = admin2?.display_name || admin2?.username || "the admin";
@@ -6172,8 +6671,8 @@ setup.post("/complete", async (c) => {
6172
6671
  var setup_default = setup;
6173
6672
 
6174
6673
  // src/web/api/shared.ts
6175
- import { Hono as Hono19 } from "hono";
6176
- var app17 = new Hono19().post("/:name/shared/merge", requireAdmin, async (c) => {
6674
+ import { Hono as Hono20 } from "hono";
6675
+ var app17 = new Hono20().post("/:name/shared/merge", requireAdmin, async (c) => {
6177
6676
  const name = c.req.param("name");
6178
6677
  const entry = await findMind(name);
6179
6678
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -6198,8 +6697,8 @@ import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync3, r
6198
6697
  import { tmpdir } from "os";
6199
6698
  import { join, resolve as resolve15 } from "path";
6200
6699
  import AdmZip from "adm-zip";
6201
- import { Hono as Hono20 } from "hono";
6202
- var app18 = new Hono20().get("/", async (c) => {
6700
+ import { Hono as Hono21 } from "hono";
6701
+ var app18 = new Hono21().get("/", async (c) => {
6203
6702
  const skills = await listSharedSkills();
6204
6703
  return c.json(skills);
6205
6704
  }).get("/defaults/list", async (c) => {
@@ -6314,14 +6813,14 @@ var skills_default = app18;
6314
6813
 
6315
6814
  // src/web/api/typing.ts
6316
6815
  import { zValidator as zValidator7 } from "@hono/zod-validator";
6317
- import { Hono as Hono21 } from "hono";
6816
+ import { Hono as Hono22 } from "hono";
6318
6817
  import { z as z7 } from "zod";
6319
6818
  var typingSchema = z7.object({
6320
6819
  channel: z7.string().min(1),
6321
6820
  sender: z7.string().min(1),
6322
6821
  active: z7.boolean()
6323
6822
  });
6324
- var app19 = new Hono21().post("/:name/typing", zValidator7("json", typingSchema), (c) => {
6823
+ var app19 = new Hono22().post("/:name/typing", zValidator7("json", typingSchema), (c) => {
6325
6824
  const { channel, sender, active } = c.req.valid("json");
6326
6825
  const map = getTypingMap();
6327
6826
  if (active) {
@@ -6345,9 +6844,9 @@ var typing_default = app19;
6345
6844
 
6346
6845
  // src/web/api/update.ts
6347
6846
  import { spawn as spawn3 } from "child_process";
6348
- import { Hono as Hono22 } from "hono";
6847
+ import { Hono as Hono23 } from "hono";
6349
6848
  var bin;
6350
- var app20 = new Hono22().get("/update", async (c) => {
6849
+ var app20 = new Hono23().get("/update", async (c) => {
6351
6850
  const result = await checkForUpdate();
6352
6851
  return c.json(result);
6353
6852
  }).post("/update", requireAdmin, async (c) => {
@@ -6364,219 +6863,15 @@ var app20 = new Hono22().get("/update", async (c) => {
6364
6863
  });
6365
6864
  var update_default = app20;
6366
6865
 
6367
- // src/web/api/v1/chat.ts
6368
- import { zValidator as zValidator8 } from "@hono/zod-validator";
6369
- import { Hono as Hono23 } from "hono";
6370
- import { streamSSE as streamSSE4 } from "hono/streaming";
6371
- import { z as z8 } from "zod";
6372
- async function fanOutToMinds(opts) {
6373
- const participants = await getParticipants(opts.conversationId);
6374
- const mindParticipants = participants.filter(
6375
- (p) => p.userType === "mind" || p.userType === "system"
6376
- );
6377
- const participantNames = participants.map((p) => p.username);
6378
- const isDM = opts.isDM ?? participants.length === 2;
6379
- const { getMindManager: getMindManager2 } = await import("./mind-manager-BNCMGYXW.js");
6380
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
6381
- const manager = getMindManager2();
6382
- const sm = getSleepManagerIfReady2();
6383
- const targetMinds = mindParticipants.map((ap) => {
6384
- const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
6385
- if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
6386
- return null;
6387
- }).filter((n) => n !== null && n !== opts.senderName);
6388
- function slugForMind(mindUsername) {
6389
- return buildVoluteSlug({
6390
- participants,
6391
- mindUsername,
6392
- convTitle: opts.convTitle,
6393
- conversationId: opts.conversationId,
6394
- ...opts.slugExtra
6395
- });
6396
- }
6397
- for (const mindName of targetMinds) {
6398
- const target = opts.targetName ? opts.targetName(mindName) : mindName;
6399
- const channel = slugForMind(mindName);
6400
- const typingMap = getTypingMap();
6401
- const currentlyTyping = typingMap.get(channel).filter((name) => participantNames.includes(name));
6402
- deliverMessage(target, {
6403
- content: opts.contentBlocks,
6404
- channel,
6405
- conversationId: opts.conversationId,
6406
- sender: opts.senderName,
6407
- participants: participantNames,
6408
- participantCount: participants.length,
6409
- isDM,
6410
- ...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
6411
- }).catch((err) => {
6412
- logger_default.warn("[v1-chat] delivery failed", logger_default.errorData(err));
6413
- });
6414
- }
6415
- }
6416
- var mindChatSchema = z8.object({
6417
- message: z8.string().optional(),
6418
- conversationId: z8.string().optional(),
6419
- sender: z8.string().optional(),
6420
- images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
6421
- });
6422
- var unifiedChatSchema = z8.object({
6423
- message: z8.string().optional(),
6424
- conversationId: z8.string(),
6425
- images: z8.array(z8.object({ media_type: z8.string(), data: z8.string() })).optional()
6426
- });
6427
- var app21 = new Hono23().use("*", authMiddleware).post("/minds/:name/chat", zValidator8("json", mindChatSchema), async (c) => {
6428
- const name = c.req.param("name");
6429
- const baseName = await getBaseName(name);
6430
- const entry = await findMind(baseName);
6431
- if (!entry) return c.json({ error: "Mind not found" }, 404);
6432
- const body = c.req.valid("json");
6433
- if (!body.message && (!body.images || body.images.length === 0)) {
6434
- return c.json({ error: "message or images required" }, 400);
6435
- }
6436
- const user = c.get("user");
6437
- const mindUser = await getOrCreateMindUser(baseName);
6438
- const senderName = user.id === 0 && body.sender ? body.sender : user.username;
6439
- let conversationId = body.conversationId;
6440
- if (conversationId) {
6441
- if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
6442
- return c.json({ error: "Conversation not found" }, 404);
6443
- }
6444
- } else {
6445
- const participantIds = [];
6446
- if (user.id !== 0) {
6447
- participantIds.push(user.id);
6448
- } else if (body.sender) {
6449
- const senderMind = await findMind(body.sender);
6450
- if (senderMind) {
6451
- const senderMindUser = await getOrCreateMindUser(body.sender);
6452
- participantIds.push(senderMindUser.id);
6453
- }
6454
- }
6455
- participantIds.push(mindUser.id);
6456
- if (participantIds.length === 2) {
6457
- const existing = await findDMConversation(baseName, participantIds);
6458
- if (existing) conversationId = existing;
6459
- }
6460
- if (!conversationId) {
6461
- const participantNames = /* @__PURE__ */ new Set([senderName, baseName]);
6462
- const title = [...participantNames].join(", ");
6463
- const conv2 = await createConversation(baseName, "volute", {
6464
- userId: user.id !== 0 ? user.id : void 0,
6465
- title,
6466
- participantIds
6467
- });
6468
- conversationId = conv2.id;
6469
- }
6470
- }
6471
- const conv = await getConversation(conversationId);
6472
- const convTitle = conv?.title ?? null;
6473
- const contentBlocks = [];
6474
- if (body.message) contentBlocks.push({ type: "text", text: body.message });
6475
- if (body.images) {
6476
- for (const img of body.images) {
6477
- contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
6478
- }
6479
- }
6480
- const senderIsMind = user.id === 0 && body.sender && await findMind(body.sender);
6481
- const v1MindSession = c.get("mindSession");
6482
- let v1SourceEventId;
6483
- let v1TurnId;
6484
- if (senderIsMind) {
6485
- v1SourceEventId = getLastToolUseEventId(body.sender, v1MindSession);
6486
- v1TurnId = getActiveTurnId(body.sender, v1MindSession);
6487
- }
6488
- await addMessage(conversationId, "user", senderName, contentBlocks, {
6489
- sourceEventId: v1SourceEventId,
6490
- turnId: v1TurnId
6491
- });
6492
- const isDM = conv?.type === "dm";
6493
- await fanOutToMinds({
6494
- conversationId,
6495
- contentBlocks,
6496
- senderName,
6497
- convTitle,
6498
- isDM,
6499
- slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
6500
- targetName: (username) => username === baseName ? name : username
6501
- });
6502
- return c.json({ ok: true, conversationId });
6503
- }).get("/minds/:name/conversations/:id/events", async (c) => {
6504
- const conversationId = c.req.param("id");
6505
- const user = c.get("user");
6506
- if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
6507
- return c.json({ error: "Conversation not found" }, 404);
6508
- }
6509
- return streamSSE4(c, async (stream) => {
6510
- const unsubscribe = subscribe2(conversationId, (event) => {
6511
- stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
6512
- if (!stream.aborted) logger_default.error("[v1-chat] SSE write error:", logger_default.errorData(err));
6513
- });
6514
- });
6515
- const keepAlive = setInterval(() => {
6516
- stream.writeSSE({ data: "" }).catch((err) => {
6517
- if (!stream.aborted) logger_default.error("[v1-chat] SSE ping error:", logger_default.errorData(err));
6518
- });
6519
- }, 15e3);
6520
- await new Promise((resolve20) => {
6521
- stream.onAbort(() => {
6522
- unsubscribe();
6523
- clearInterval(keepAlive);
6524
- resolve20();
6525
- });
6526
- });
6527
- });
6528
- }).post("/chat", zValidator8("json", unifiedChatSchema), async (c) => {
6529
- const user = c.get("user");
6530
- const body = c.req.valid("json");
6531
- if (!body.message && (!body.images || body.images.length === 0)) {
6532
- return c.json({ error: "message or images required" }, 400);
6533
- }
6534
- const conv = await getConversation(body.conversationId);
6535
- if (!conv) return c.json({ error: "Conversation not found" }, 404);
6536
- if (user.id !== 0 && !await isParticipantOrOwner(body.conversationId, user.id)) {
6537
- return c.json({ error: "Conversation not found" }, 404);
6538
- }
6539
- const senderName = user.username;
6540
- const contentBlocks = [];
6541
- if (body.message) contentBlocks.push({ type: "text", text: body.message });
6542
- if (body.images) {
6543
- for (const img of body.images) {
6544
- contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
6545
- }
6546
- }
6547
- const unifiedV1Session = c.get("mindSession");
6548
- let unifiedV1SourceEventId;
6549
- let unifiedV1TurnId;
6550
- if (user.user_type === "mind") {
6551
- unifiedV1SourceEventId = getLastToolUseEventId(senderName, unifiedV1Session);
6552
- unifiedV1TurnId = getActiveTurnId(senderName, unifiedV1Session);
6553
- }
6554
- await addMessage(body.conversationId, "user", senderName, contentBlocks, {
6555
- sourceEventId: unifiedV1SourceEventId,
6556
- turnId: unifiedV1TurnId
6557
- });
6558
- const isDM = conv.type === "dm";
6559
- await fanOutToMinds({
6560
- conversationId: body.conversationId,
6561
- contentBlocks,
6562
- senderName,
6563
- convTitle: conv.title,
6564
- isDM,
6565
- slugExtra: { convType: conv.type, convName: conv.name }
6566
- });
6567
- return c.json({ ok: true, conversationId: body.conversationId });
6568
- });
6569
- var chat_default = app21;
6570
-
6571
6866
  // src/web/api/v1/conversations.ts
6572
- import { zValidator as zValidator9 } from "@hono/zod-validator";
6867
+ import { zValidator as zValidator8 } from "@hono/zod-validator";
6573
6868
  import { Hono as Hono24 } from "hono";
6574
- import { z as z9 } from "zod";
6575
- var createSchema = z9.object({
6576
- title: z9.string().optional(),
6577
- participantNames: z9.array(z9.string()).min(1)
6869
+ import { z as z8 } from "zod";
6870
+ var createSchema = z8.object({
6871
+ title: z8.string().optional(),
6872
+ participantNames: z8.array(z8.string()).min(1)
6578
6873
  });
6579
- var app22 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6874
+ var app21 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6580
6875
  const user = c.get("user");
6581
6876
  const convs = await listConversationsWithParticipants(user.id);
6582
6877
  return c.json(convs);
@@ -6603,7 +6898,7 @@ var app22 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6603
6898
  }
6604
6899
  const participants = await getParticipants(id);
6605
6900
  return c.json(participants);
6606
- }).post("/", zValidator9("json", createSchema), async (c) => {
6901
+ }).post("/", zValidator8("json", createSchema), async (c) => {
6607
6902
  const user = c.get("user");
6608
6903
  const body = c.req.valid("json");
6609
6904
  const participantIds = /* @__PURE__ */ new Set();
@@ -6627,6 +6922,9 @@ var app22 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6627
6922
  if (!firstMindName) {
6628
6923
  return c.json({ error: "At least one mind participant is required" }, 400);
6629
6924
  }
6925
+ if (participantIds.size > 2) {
6926
+ return c.json({ error: "Use channels for multi-participant conversations" }, 400);
6927
+ }
6630
6928
  const conv = await createConversation(firstMindName, "volute", {
6631
6929
  userId: user.id !== 0 ? user.id : void 0,
6632
6930
  title: body.title,
@@ -6642,6 +6940,15 @@ var app22 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6642
6940
  }
6643
6941
  await markConversationRead(user.id, id);
6644
6942
  return c.json({ ok: true });
6943
+ }).put("/:id/private", zValidator8("json", z8.object({ private: z8.boolean() })), async (c) => {
6944
+ const id = c.req.param("id");
6945
+ const user = c.get("user");
6946
+ if (!await isParticipantOrOwner(id, user.id)) {
6947
+ return c.json({ error: "Forbidden" }, 403);
6948
+ }
6949
+ const body = c.req.valid("json");
6950
+ await setConversationPrivate(id, body.private);
6951
+ return c.json({ ok: true });
6645
6952
  }).delete("/:id", async (c) => {
6646
6953
  const id = c.req.param("id");
6647
6954
  const user = c.get("user");
@@ -6649,12 +6956,12 @@ var app22 = new Hono24().use("*", authMiddleware).get("/", async (c) => {
6649
6956
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
6650
6957
  return c.json({ ok: true });
6651
6958
  });
6652
- var conversations_default = app22;
6959
+ var conversations_default = app21;
6653
6960
 
6654
6961
  // src/web/api/v1/events.ts
6655
- import { desc as desc4 } from "drizzle-orm";
6962
+ import { desc as desc5 } from "drizzle-orm";
6656
6963
  import { Hono as Hono25 } from "hono";
6657
- import { streamSSE as streamSSE5 } from "hono/streaming";
6964
+ import { streamSSE as streamSSE4 } from "hono/streaming";
6658
6965
 
6659
6966
  // src/lib/events/brain-presence.ts
6660
6967
  var connections = /* @__PURE__ */ new Map();
@@ -6700,11 +7007,11 @@ function getEventsSince(sinceId) {
6700
7007
  }
6701
7008
 
6702
7009
  // src/web/api/v1/events.ts
6703
- var app23 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
7010
+ var app22 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
6704
7011
  const user = c.get("user");
6705
7012
  const since = c.req.query("since");
6706
7013
  const sinceId = since ? Number(since) : 0;
6707
- return streamSSE5(c, async (stream) => {
7014
+ return streamSSE4(c, async (stream) => {
6708
7015
  const cleanups = [];
6709
7016
  if (user.user_type === "brain") {
6710
7017
  addConnection(user.username);
@@ -6723,7 +7030,7 @@ var app23 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
6723
7030
  let recentActivity = [];
6724
7031
  try {
6725
7032
  const db = await getDb();
6726
- recentActivity = await db.select().from(activity).orderBy(desc4(activity.created_at)).limit(50);
7033
+ recentActivity = await db.select().from(activity).orderBy(desc5(activity.created_at)).limit(50);
6727
7034
  recentActivity = recentActivity.map((row) => ({
6728
7035
  ...row,
6729
7036
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -6803,7 +7110,7 @@ var app23 = new Hono25().use("*", authMiddleware).get("/", async (c) => {
6803
7110
  }
6804
7111
  });
6805
7112
  });
6806
- var events_default = app23;
7113
+ var events_default = app22;
6807
7114
 
6808
7115
  // src/web/api/variants.ts
6809
7116
  import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
@@ -6924,7 +7231,7 @@ async function verify2(port) {
6924
7231
  }
6925
7232
 
6926
7233
  // src/web/api/variants.ts
6927
- var app24 = new Hono26().get("/:name/variants", async (c) => {
7234
+ var app23 = new Hono26().get("/:name/variants", async (c) => {
6928
7235
  const name = c.req.param("name");
6929
7236
  const entry = await findMind(name);
6930
7237
  if (!entry) return c.json({ error: "Mind not found" }, 404);
@@ -7146,19 +7453,19 @@ var app24 = new Hono26().get("/:name/variants", async (c) => {
7146
7453
  await cleanupVariant(variantName, projectRoot, variantEntry.dir, { stop: true });
7147
7454
  return c.json({ ok: true });
7148
7455
  });
7149
- var variants_default = app24;
7456
+ var variants_default = app23;
7150
7457
 
7151
7458
  // src/web/api/volute/channels.ts
7152
- import { zValidator as zValidator10 } from "@hono/zod-validator";
7459
+ import { zValidator as zValidator9 } from "@hono/zod-validator";
7153
7460
  import { Hono as Hono27 } from "hono";
7154
- import { z as z10 } from "zod";
7155
- var createSchema2 = z10.object({
7156
- name: z10.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
7461
+ import { z as z9 } from "zod";
7462
+ var createSchema2 = z9.object({
7463
+ name: z9.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Channel names must be lowercase alphanumeric with hyphens")
7157
7464
  });
7158
- var inviteSchema = z10.object({
7159
- username: z10.string().min(1)
7465
+ var inviteSchema = z9.object({
7466
+ username: z9.string().min(1)
7160
7467
  });
7161
- var app25 = new Hono27().get("/", async (c) => {
7468
+ var app24 = new Hono27().get("/", async (c) => {
7162
7469
  const user = c.get("user");
7163
7470
  const channels = await listChannels();
7164
7471
  const results = await Promise.all(
@@ -7169,7 +7476,7 @@ var app25 = new Hono27().get("/", async (c) => {
7169
7476
  })
7170
7477
  );
7171
7478
  return c.json(results);
7172
- }).post("/", zValidator10("json", createSchema2), async (c) => {
7479
+ }).post("/", zValidator9("json", createSchema2), async (c) => {
7173
7480
  const user = c.get("user");
7174
7481
  const body = c.req.valid("json");
7175
7482
  try {
@@ -7208,7 +7515,7 @@ var app25 = new Hono27().get("/", async (c) => {
7208
7515
  if (!ch) return c.json({ error: "Channel not found" }, 404);
7209
7516
  const participants = await getParticipants(ch.id);
7210
7517
  return c.json(participants);
7211
- }).post("/:name/invite", zValidator10("json", inviteSchema), async (c) => {
7518
+ }).post("/:name/invite", zValidator9("json", inviteSchema), async (c) => {
7212
7519
  const name = c.req.param("name");
7213
7520
  const inviter = c.get("user");
7214
7521
  const { username } = c.req.valid("json");
@@ -7228,13 +7535,13 @@ var app25 = new Hono27().get("/", async (c) => {
7228
7535
  ]);
7229
7536
  return c.json({ ok: true });
7230
7537
  });
7231
- var channels_default2 = app25;
7538
+ var channels_default2 = app24;
7232
7539
 
7233
7540
  // src/web/api/volute/chat.ts
7234
- import { zValidator as zValidator11 } from "@hono/zod-validator";
7541
+ import { zValidator as zValidator10 } from "@hono/zod-validator";
7235
7542
  import { Hono as Hono28 } from "hono";
7236
- import { streamSSE as streamSSE6 } from "hono/streaming";
7237
- import { z as z11 } from "zod";
7543
+ import { streamSSE as streamSSE5 } from "hono/streaming";
7544
+ import { z as z10 } from "zod";
7238
7545
 
7239
7546
  // src/lib/bridge-outbound.ts
7240
7547
  function extractContent(contentBlocks) {
@@ -7308,15 +7615,15 @@ async function routeDMOutbound(conversationId, senderName, contentBlocks) {
7308
7615
  }
7309
7616
 
7310
7617
  // src/web/api/volute/chat.ts
7311
- async function fanOutToMinds2(opts) {
7618
+ async function fanOutToMinds(opts) {
7312
7619
  const participants = await getParticipants(opts.conversationId);
7313
7620
  const mindParticipants = participants.filter(
7314
7621
  (p) => p.userType === "mind" || p.userType === "system"
7315
7622
  );
7316
7623
  const participantNames = participants.map((p) => p.username);
7317
7624
  const isDM = opts.isDM ?? participants.length === 2;
7318
- const { getMindManager: getMindManager2 } = await import("./mind-manager-BNCMGYXW.js");
7319
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-53DZOWW7.js");
7625
+ const { getMindManager: getMindManager2 } = await import("./mind-manager-NBJF5D26.js");
7626
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-JTXSN7NV.js");
7320
7627
  const manager = getMindManager2();
7321
7628
  const sm = getSleepManagerIfReady2();
7322
7629
  const targetMinds = mindParticipants.map((ap) => {
@@ -7352,225 +7659,131 @@ async function fanOutToMinds2(opts) {
7352
7659
  });
7353
7660
  }
7354
7661
  }
7355
- var fileSchema = z11.object({
7356
- filename: z11.string(),
7357
- data: z11.string()
7662
+ var fileSchema = z10.object({
7663
+ filename: z10.string(),
7664
+ data: z10.string()
7358
7665
  // base64
7359
7666
  });
7360
- var chatSchema = z11.object({
7361
- message: z11.string().optional(),
7362
- conversationId: z11.string().optional(),
7363
- sender: z11.string().optional(),
7364
- images: z11.array(
7365
- z11.object({
7366
- media_type: z11.string(),
7367
- data: z11.string()
7667
+ var chatSchema = z10.object({
7668
+ message: z10.string().optional(),
7669
+ conversationId: z10.string().optional(),
7670
+ targetMind: z10.string().optional(),
7671
+ sender: z10.string().optional(),
7672
+ images: z10.array(
7673
+ z10.object({
7674
+ media_type: z10.string(),
7675
+ data: z10.string()
7368
7676
  })
7369
7677
  ).optional(),
7370
- files: z11.array(fileSchema).optional()
7678
+ files: z10.array(fileSchema).optional()
7371
7679
  });
7372
- var app26 = new Hono28().post("/:name/chat", zValidator11("json", chatSchema), async (c) => {
7373
- const name = c.req.param("name");
7374
- const baseName = await getBaseName(name);
7375
- const entry = await findMind(baseName);
7376
- if (!entry) return c.json({ error: "Mind not found" }, 404);
7377
- const body = c.req.valid("json");
7378
- if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
7379
- return c.json({ error: "message, images, or files required" }, 400);
7380
- }
7381
- const user = c.get("user");
7382
- const mindUser = await getOrCreateMindUser(baseName);
7383
- const senderName = user.id === 0 && body.sender ? body.sender : user.username;
7384
- let conversationId = body.conversationId;
7385
- if (conversationId) {
7386
- if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7387
- return c.json({ error: "Conversation not found" }, 404);
7388
- }
7389
- } else {
7390
- const participantIds = [];
7391
- if (user.id !== 0) {
7392
- participantIds.push(user.id);
7393
- } else if (body.sender) {
7394
- if (body.sender === "volute") {
7395
- const systemUser = await getOrCreateSystemUser();
7396
- participantIds.push(systemUser.id);
7397
- } else {
7398
- const senderMind = await findMind(body.sender);
7399
- if (senderMind) {
7400
- const senderMindUser = await getOrCreateMindUser(body.sender);
7401
- participantIds.push(senderMindUser.id);
7680
+ var MAX_FILE_SIZE2 = 50 * 1024 * 1024;
7681
+ function stageFilesForMinds(files, targetMinds, senderName) {
7682
+ const notifications = [];
7683
+ for (const file of files) {
7684
+ const pathErr = validateFilePath(file.filename);
7685
+ if (pathErr)
7686
+ return { notifications, error: { message: `Invalid filename: ${pathErr}`, status: 400 } };
7687
+ const content = Buffer.from(file.data, "base64");
7688
+ if (content.length > MAX_FILE_SIZE2) {
7689
+ return {
7690
+ notifications,
7691
+ error: {
7692
+ message: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`,
7693
+ status: 413
7402
7694
  }
7403
- }
7404
- }
7405
- participantIds.push(mindUser.id);
7406
- if (participantIds.length === 2) {
7407
- const existing = await findDMConversation(baseName, participantIds);
7408
- if (existing) {
7409
- conversationId = existing;
7410
- }
7695
+ };
7411
7696
  }
7412
- if (!conversationId) {
7413
- const participantNames = /* @__PURE__ */ new Set([senderName, baseName]);
7414
- const title = [...participantNames].join(", ");
7415
- const conv2 = await createConversation(baseName, "volute", {
7416
- userId: user.id !== 0 ? user.id : void 0,
7417
- title,
7418
- participantIds
7419
- });
7420
- conversationId = conv2.id;
7421
- }
7422
- }
7423
- const conv = await getConversation(conversationId);
7424
- const convTitle = conv?.title ?? null;
7425
- const fileNotifications = [];
7426
- if (body.files && body.files.length > 0) {
7427
- const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
7428
- for (const file of body.files) {
7429
- const pathErr = validateFilePath(file.filename);
7430
- if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
7431
- const content = Buffer.from(file.data, "base64");
7432
- if (content.length > MAX_FILE_SIZE2) {
7433
- return c.json(
7434
- {
7435
- error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
7436
- },
7437
- 413
7438
- );
7439
- }
7440
- const { id } = stageFile(baseName, senderName, file.filename, content, file.filename);
7441
- fileNotifications.push(
7697
+ for (const mind of targetMinds) {
7698
+ const { id } = stageFile(mind, senderName, file.filename, content, file.filename);
7699
+ notifications.push(
7442
7700
  `[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
7443
7701
  );
7444
7702
  }
7445
7703
  }
7446
- const contentBlocks = [];
7447
- if (body.message) {
7448
- contentBlocks.push({ type: "text", text: body.message });
7449
- }
7450
- for (const note of fileNotifications) {
7451
- contentBlocks.push({ type: "text", text: note });
7452
- }
7453
- if (body.images) {
7454
- for (const img of body.images) {
7455
- contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
7456
- }
7457
- }
7458
- const senderIsMind = user.id === 0 && body.sender && await findMind(body.sender);
7459
- const mindSession = c.get("mindSession");
7460
- let sourceEventId;
7461
- let turnId;
7462
- if (senderIsMind) {
7463
- sourceEventId = getLastToolUseEventId(body.sender, mindSession);
7464
- turnId = getActiveTurnId(body.sender, mindSession);
7465
- }
7466
- await addMessage(conversationId, "user", senderName, contentBlocks, {
7467
- sourceEventId,
7468
- turnId
7469
- });
7470
- if (senderIsMind) {
7471
- routeOutboundBridge(conversationId, senderName, contentBlocks).catch((err) => {
7472
- logger_default.warn("outbound bridge routing failed", logger_default.errorData(err));
7473
- });
7474
- }
7475
- const isDM = conv?.type === "dm";
7476
- await fanOutToMinds2({
7477
- conversationId,
7478
- contentBlocks,
7479
- senderName,
7480
- convTitle,
7481
- isDM,
7482
- slugExtra: conv ? { convType: conv.type, convName: conv.name } : void 0,
7483
- targetName: (username) => username === baseName ? name : username
7484
- });
7485
- if (senderIsMind && body.message) {
7486
- const participants = await getParticipants(conversationId);
7487
- const hasSystemUser = participants.some((p) => p.userType === "system");
7488
- if (hasSystemUser) {
7489
- generateSystemReply(conversationId, baseName, body.message).catch(
7490
- (err) => logger_default.error(`system reply generation failed for ${baseName}`, logger_default.errorData(err))
7491
- );
7492
- }
7493
- }
7494
- return c.json({ ok: true, conversationId });
7495
- }).get("/:name/conversations/:id/events", async (c) => {
7496
- const conversationId = c.req.param("id");
7497
- const user = c.get("user");
7498
- if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7499
- return c.json({ error: "Conversation not found" }, 404);
7500
- }
7501
- return streamSSE6(c, async (stream) => {
7502
- const unsubscribe = subscribe2(conversationId, (event) => {
7503
- stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
7504
- if (!stream.aborted) console.error("[chat] SSE write error:", err);
7505
- });
7506
- });
7507
- const keepAlive = setInterval(() => {
7508
- stream.writeSSE({ data: "" }).catch((err) => {
7509
- if (!stream.aborted) console.error("[chat] SSE ping error:", err);
7510
- });
7511
- }, 15e3);
7512
- await new Promise((resolve20) => {
7513
- stream.onAbort(() => {
7514
- unsubscribe();
7515
- clearInterval(keepAlive);
7516
- resolve20();
7517
- });
7518
- });
7519
- });
7520
- });
7521
- var unifiedChatSchema2 = z11.object({
7522
- message: z11.string().optional(),
7523
- conversationId: z11.string(),
7524
- images: z11.array(z11.object({ media_type: z11.string(), data: z11.string() })).optional(),
7525
- files: z11.array(fileSchema).optional()
7526
- });
7704
+ return { notifications };
7705
+ }
7527
7706
  var unifiedChatApp = new Hono28().post(
7528
7707
  "/chat",
7529
- zValidator11("json", unifiedChatSchema2),
7708
+ zValidator10("json", chatSchema),
7530
7709
  async (c) => {
7531
7710
  const user = c.get("user");
7532
7711
  const body = c.req.valid("json");
7533
7712
  if (!body.message && (!body.images || body.images.length === 0) && (!body.files || body.files.length === 0)) {
7534
7713
  return c.json({ error: "message, images, or files required" }, 400);
7535
7714
  }
7536
- const conv = await getConversation(body.conversationId);
7537
- if (!conv) return c.json({ error: "Conversation not found" }, 404);
7538
- if (user.id !== 0 && !await isParticipantOrOwner(body.conversationId, user.id)) {
7539
- return c.json({ error: "Conversation not found" }, 404);
7715
+ if (!body.conversationId && !body.targetMind) {
7716
+ return c.json({ error: "conversationId or targetMind required" }, 400);
7717
+ }
7718
+ const senderName = user.id === 0 && body.sender ? body.sender : user.username;
7719
+ const senderIsMind = user.id === 0 && body.sender && await findMind(body.sender) || user.user_type === "mind";
7720
+ let baseName;
7721
+ let variantName;
7722
+ let conversationId = body.conversationId;
7723
+ if (body.targetMind) {
7724
+ variantName = body.targetMind;
7725
+ baseName = await getBaseName(body.targetMind);
7726
+ const entry = await findMind(baseName);
7727
+ if (!entry) return c.json({ error: "Mind not found" }, 404);
7728
+ if (conversationId) {
7729
+ if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7730
+ return c.json({ error: "Conversation not found" }, 404);
7731
+ }
7732
+ } else {
7733
+ const mindUser = await getOrCreateMindUser(baseName);
7734
+ const participantIds = [];
7735
+ if (user.id !== 0) {
7736
+ participantIds.push(user.id);
7737
+ } else if (body.sender) {
7738
+ if (body.sender === "volute") {
7739
+ const systemUser = await getOrCreateSystemUser();
7740
+ participantIds.push(systemUser.id);
7741
+ } else {
7742
+ const senderMind = await findMind(body.sender);
7743
+ if (senderMind) {
7744
+ const senderMindUser = await getOrCreateMindUser(body.sender);
7745
+ participantIds.push(senderMindUser.id);
7746
+ }
7747
+ }
7748
+ }
7749
+ participantIds.push(mindUser.id);
7750
+ if (participantIds.length === 2) {
7751
+ const existing = await findDMConversation(baseName, participantIds);
7752
+ if (existing) {
7753
+ conversationId = existing;
7754
+ }
7755
+ }
7756
+ if (!conversationId) {
7757
+ const participantNames = /* @__PURE__ */ new Set([senderName, baseName]);
7758
+ const title = [...participantNames].join(", ");
7759
+ const conv2 = await createConversation(baseName, "volute", {
7760
+ userId: user.id !== 0 ? user.id : void 0,
7761
+ title,
7762
+ participantIds
7763
+ });
7764
+ conversationId = conv2.id;
7765
+ }
7766
+ }
7767
+ } else {
7768
+ if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7769
+ return c.json({ error: "Conversation not found" }, 404);
7770
+ }
7540
7771
  }
7541
- const senderName = user.username;
7772
+ const conv = await getConversation(conversationId);
7773
+ if (!conv) return c.json({ error: "Conversation not found" }, 404);
7774
+ const convTitle = conv.title;
7542
7775
  const fileNotifications = [];
7543
7776
  if (body.files && body.files.length > 0) {
7544
- const participants = await getParticipants(body.conversationId);
7545
- const mindParticipants = participants.filter(
7546
- (p) => p.userType === "mind" && p.username !== senderName
7547
- );
7548
- const MAX_FILE_SIZE2 = 50 * 1024 * 1024;
7549
- for (const file of body.files) {
7550
- const pathErr = validateFilePath(file.filename);
7551
- if (pathErr) return c.json({ error: `Invalid filename: ${pathErr}` }, 400);
7552
- const content = Buffer.from(file.data, "base64");
7553
- if (content.length > MAX_FILE_SIZE2) {
7554
- return c.json(
7555
- {
7556
- error: `File too large: ${file.filename} (${formatFileSize(content.length)}, max ${formatFileSize(MAX_FILE_SIZE2)})`
7557
- },
7558
- 413
7559
- );
7560
- }
7561
- for (const mind of mindParticipants) {
7562
- const { id } = stageFile(
7563
- mind.username,
7564
- senderName,
7565
- file.filename,
7566
- content,
7567
- file.filename
7568
- );
7569
- fileNotifications.push(
7570
- `[file] ${senderName} sent ${file.filename} (${formatFileSize(content.length)}) \u2014 run: volute chat accept ${id}`
7571
- );
7572
- }
7777
+ let fileTargets;
7778
+ if (baseName) {
7779
+ fileTargets = [baseName];
7780
+ } else {
7781
+ const participants = await getParticipants(conversationId);
7782
+ fileTargets = participants.filter((p) => p.userType === "mind" && p.username !== senderName).map((p) => p.username);
7573
7783
  }
7784
+ const { notifications, error } = stageFilesForMinds(body.files, fileTargets, senderName);
7785
+ if (error) return c.json({ error: error.message }, error.status);
7786
+ fileNotifications.push(...notifications);
7574
7787
  }
7575
7788
  const contentBlocks = [];
7576
7789
  if (body.message) contentBlocks.push({ type: "text", text: body.message });
@@ -7582,55 +7795,90 @@ var unifiedChatApp = new Hono28().post(
7582
7795
  contentBlocks.push({ type: "image", media_type: img.media_type, data: img.data });
7583
7796
  }
7584
7797
  }
7585
- const unifiedMindSession = c.get("mindSession");
7586
- let unifiedSourceEventId;
7587
- let unifiedTurnId;
7588
- if (user.user_type === "mind") {
7589
- unifiedSourceEventId = getLastToolUseEventId(senderName, unifiedMindSession);
7590
- unifiedTurnId = getActiveTurnId(senderName, unifiedMindSession);
7591
- }
7592
- await addMessage(body.conversationId, "user", senderName, contentBlocks, {
7593
- sourceEventId: unifiedSourceEventId,
7594
- turnId: unifiedTurnId
7595
- });
7596
- if (user.user_type === "mind") {
7597
- routeOutboundBridge(body.conversationId, senderName, contentBlocks).catch((err) => {
7798
+ const message = await addMessage(conversationId, "user", senderName, contentBlocks);
7799
+ let outboundId;
7800
+ if (senderIsMind) {
7801
+ routeOutboundBridge(conversationId, senderName, contentBlocks).catch((err) => {
7598
7802
  logger_default.warn("outbound bridge routing failed", logger_default.errorData(err));
7599
7803
  });
7804
+ const channel = buildVoluteSlug({
7805
+ participants: await getParticipants(conversationId),
7806
+ mindUsername: senderName,
7807
+ convTitle,
7808
+ conversationId,
7809
+ convType: conv.type,
7810
+ convName: conv.name
7811
+ });
7812
+ try {
7813
+ outboundId = await recordOutbound(senderName, channel, extractTextContent(contentBlocks), {
7814
+ messageId: message?.id != null ? String(message.id) : void 0
7815
+ });
7816
+ } catch (err) {
7817
+ logger_default.warn(`recordOutbound failed for ${senderName}`, logger_default.errorData(err));
7818
+ }
7600
7819
  }
7601
7820
  const isDM = conv.type === "dm";
7602
- await fanOutToMinds2({
7603
- conversationId: body.conversationId,
7821
+ await fanOutToMinds({
7822
+ conversationId,
7604
7823
  contentBlocks,
7605
7824
  senderName,
7606
- convTitle: conv.title,
7825
+ convTitle,
7607
7826
  isDM,
7608
- slugExtra: { convType: conv.type, convName: conv.name }
7827
+ slugExtra: { convType: conv.type, convName: conv.name },
7828
+ // Variant-aware targeting: when targetMind is a variant, route to the variant name
7829
+ targetName: baseName ? (username) => username === baseName ? variantName : username : void 0
7609
7830
  });
7610
- if (user.user_type === "mind" && body.message) {
7611
- const participants = await getParticipants(body.conversationId);
7831
+ const systemReplyTarget = baseName ?? senderName;
7832
+ if (senderIsMind && body.message) {
7833
+ const participants = await getParticipants(conversationId);
7612
7834
  const hasSystemUser = participants.some((p) => p.userType === "system");
7613
7835
  if (hasSystemUser) {
7614
- generateSystemReply(body.conversationId, senderName, body.message).catch(
7615
- (err) => logger_default.error(`system reply generation failed for ${senderName}`, logger_default.errorData(err))
7836
+ generateSystemReply(conversationId, systemReplyTarget, body.message).catch(
7837
+ (err) => logger_default.error(`system reply generation failed for ${systemReplyTarget}`, logger_default.errorData(err))
7616
7838
  );
7617
7839
  }
7618
7840
  }
7619
- return c.json({ ok: true, conversationId: body.conversationId });
7841
+ return c.json({ ok: true, conversationId, outboundId });
7620
7842
  }
7621
7843
  );
7622
- var chat_default2 = app26;
7844
+ var app25 = new Hono28().get("/:name/conversations/:id/events", async (c) => {
7845
+ const conversationId = c.req.param("id");
7846
+ const user = c.get("user");
7847
+ if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7848
+ return c.json({ error: "Conversation not found" }, 404);
7849
+ }
7850
+ return streamSSE5(c, async (stream) => {
7851
+ const unsubscribe = subscribe2(conversationId, (event) => {
7852
+ stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
7853
+ if (!stream.aborted) console.error("[chat] SSE write error:", err);
7854
+ });
7855
+ });
7856
+ const keepAlive = setInterval(() => {
7857
+ stream.writeSSE({ data: "" }).catch((err) => {
7858
+ if (!stream.aborted) console.error("[chat] SSE ping error:", err);
7859
+ });
7860
+ }, 15e3);
7861
+ await new Promise((resolve20) => {
7862
+ stream.onAbort(() => {
7863
+ unsubscribe();
7864
+ clearInterval(keepAlive);
7865
+ resolve20();
7866
+ });
7867
+ });
7868
+ });
7869
+ });
7870
+ var chat_default = app25;
7623
7871
 
7624
7872
  // src/web/api/volute/conversations.ts
7625
- import { zValidator as zValidator12 } from "@hono/zod-validator";
7873
+ import { zValidator as zValidator11 } from "@hono/zod-validator";
7626
7874
  import { Hono as Hono29 } from "hono";
7627
- import { z as z12 } from "zod";
7628
- var createConvSchema = z12.object({
7629
- title: z12.string().optional(),
7630
- participantIds: z12.array(z12.number()).optional(),
7631
- participantNames: z12.array(z12.string()).optional()
7875
+ import { z as z11 } from "zod";
7876
+ var createConvSchema = z11.object({
7877
+ title: z11.string().optional(),
7878
+ participantIds: z11.array(z11.number()).optional(),
7879
+ participantNames: z11.array(z11.string()).optional()
7632
7880
  });
7633
- var app27 = new Hono29().get("/:name/conversations", async (c) => {
7881
+ var app26 = new Hono29().get("/:name/conversations", async (c) => {
7634
7882
  const name = c.req.param("name");
7635
7883
  const user = c.get("user");
7636
7884
  let lookupId = user.id;
@@ -7641,7 +7889,7 @@ var app27 = new Hono29().get("/:name/conversations", async (c) => {
7641
7889
  const all = await listConversationsForUser(lookupId);
7642
7890
  const convs = all.filter((c2) => c2.mind_name === name || c2.type === "channel");
7643
7891
  return c.json(convs);
7644
- }).post("/:name/conversations", zValidator12("json", createConvSchema), async (c) => {
7892
+ }).post("/:name/conversations", zValidator11("json", createConvSchema), async (c) => {
7645
7893
  const name = c.req.param("name");
7646
7894
  const user = c.get("user");
7647
7895
  const body = c.req.valid("json");
@@ -7738,109 +7986,12 @@ var app27 = new Hono29().get("/:name/conversations", async (c) => {
7738
7986
  if (!deleted) return c.json({ error: "Conversation not found" }, 404);
7739
7987
  return c.json({ ok: true });
7740
7988
  });
7741
- var conversations_default2 = app27;
7742
-
7743
- // src/web/api/volute/user-conversations.ts
7744
- import { zValidator as zValidator13 } from "@hono/zod-validator";
7745
- import { Hono as Hono30 } from "hono";
7746
- import { streamSSE as streamSSE7 } from "hono/streaming";
7747
- import { z as z13 } from "zod";
7748
- var createSchema3 = z13.object({
7749
- title: z13.string().optional(),
7750
- participantNames: z13.array(z13.string()).min(1)
7751
- });
7752
- var app28 = new Hono30().use("*", authMiddleware).get("/", async (c) => {
7753
- const user = c.get("user");
7754
- const convs = await listConversationsWithParticipants(user.id);
7755
- return c.json(convs);
7756
- }).get("/:id/messages", async (c) => {
7757
- const id = c.req.param("id");
7758
- const user = c.get("user");
7759
- if (user.id !== 0 && !await isParticipantOrOwner(id, user.id)) {
7760
- return c.json({ error: "Conversation not found" }, 404);
7761
- }
7762
- const msgs = await getMessages(id);
7763
- return c.json(msgs);
7764
- }).post("/", zValidator13("json", createSchema3), async (c) => {
7765
- const user = c.get("user");
7766
- const body = c.req.valid("json");
7767
- const participantIds = /* @__PURE__ */ new Set();
7768
- if (user.id !== 0) participantIds.add(user.id);
7769
- let firstMindName;
7770
- for (const name of body.participantNames) {
7771
- const existing = await getUserByUsername(name);
7772
- if (existing) {
7773
- participantIds.add(existing.id);
7774
- if (!firstMindName && existing.user_type === "mind") firstMindName = name;
7775
- continue;
7776
- }
7777
- if (await findMind(name)) {
7778
- const au = await getOrCreateMindUser(name);
7779
- participantIds.add(au.id);
7780
- if (!firstMindName) firstMindName = name;
7781
- continue;
7782
- }
7783
- return c.json({ error: `User not found: ${name}` }, 400);
7784
- }
7785
- if (!firstMindName) {
7786
- return c.json({ error: "At least one mind participant is required" }, 400);
7787
- }
7788
- if (participantIds.size > 2) {
7789
- return c.json({ error: "Use channels for multi-participant conversations" }, 400);
7790
- }
7791
- const conv = await createConversation(firstMindName, "volute", {
7792
- userId: user.id !== 0 ? user.id : void 0,
7793
- title: body.title,
7794
- participantIds: [...participantIds]
7795
- });
7796
- return c.json(conv, 201);
7797
- }).get("/:id/events", async (c) => {
7798
- const conversationId = c.req.param("id");
7799
- const user = c.get("user");
7800
- if (user.id !== 0 && !await isParticipantOrOwner(conversationId, user.id)) {
7801
- return c.json({ error: "Conversation not found" }, 404);
7802
- }
7803
- return streamSSE7(c, async (stream) => {
7804
- const unsubscribe = subscribe2(conversationId, (event) => {
7805
- stream.writeSSE({ data: JSON.stringify(event) }).catch((err) => {
7806
- if (!stream.aborted) console.error("[chat] SSE write error:", err);
7807
- });
7808
- });
7809
- const keepAlive = setInterval(() => {
7810
- stream.writeSSE({ data: "" }).catch((err) => {
7811
- if (!stream.aborted) console.error("[chat] SSE ping error:", err);
7812
- });
7813
- }, 15e3);
7814
- await new Promise((resolve20) => {
7815
- stream.onAbort(() => {
7816
- unsubscribe();
7817
- clearInterval(keepAlive);
7818
- resolve20();
7819
- });
7820
- });
7821
- });
7822
- }).put("/:id/private", zValidator13("json", z13.object({ private: z13.boolean() })), async (c) => {
7823
- const id = c.req.param("id");
7824
- const user = c.get("user");
7825
- if (!await isParticipantOrOwner(id, user.id)) {
7826
- return c.json({ error: "Forbidden" }, 403);
7827
- }
7828
- const body = c.req.valid("json");
7829
- await setConversationPrivate(id, body.private);
7830
- return c.json({ ok: true });
7831
- }).delete("/:id", async (c) => {
7832
- const id = c.req.param("id");
7833
- const user = c.get("user");
7834
- const deleted = await deleteConversationForUser(id, user.id);
7835
- if (!deleted) return c.json({ error: "Conversation not found" }, 404);
7836
- return c.json({ ok: true });
7837
- });
7838
- var user_conversations_default = app28;
7989
+ var conversations_default2 = app26;
7839
7990
 
7840
7991
  // src/web/app.ts
7841
7992
  var httpLog = logger_default.child("http");
7842
- var app29 = new Hono31();
7843
- app29.onError((err, c) => {
7993
+ var app27 = new Hono30();
7994
+ app27.onError((err, c) => {
7844
7995
  if (err instanceof HTTPException) {
7845
7996
  return err.getResponse();
7846
7997
  }
@@ -7851,10 +8002,10 @@ app29.onError((err, c) => {
7851
8002
  });
7852
8003
  return c.json({ error: "Internal server error" }, 500);
7853
8004
  });
7854
- app29.notFound((c) => {
8005
+ app27.notFound((c) => {
7855
8006
  return c.json({ error: "Not found" }, 404);
7856
8007
  });
7857
- app29.use("*", async (c, next) => {
8008
+ app27.use("*", async (c, next) => {
7858
8009
  const start = Date.now();
7859
8010
  await next();
7860
8011
  const duration = Date.now() - start;
@@ -7865,7 +8016,7 @@ app29.use("*", async (c, next) => {
7865
8016
  httpLog.debug("request", data);
7866
8017
  }
7867
8018
  });
7868
- app29.get("/api/health", (c) => {
8019
+ app27.get("/api/health", (c) => {
7869
8020
  let version = "unknown";
7870
8021
  let cached = null;
7871
8022
  try {
@@ -7880,39 +8031,39 @@ app29.get("/api/health", (c) => {
7880
8031
  ...cached?.updateAvailable ? { updateAvailable: true, latest: cached.latest } : {}
7881
8032
  });
7882
8033
  });
7883
- app29.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
7884
- app29.use("/api/*", csrf());
7885
- app29.use("/api/activity/*", authMiddleware);
7886
- app29.use("/api/minds/*", authMiddleware);
7887
- app29.use("/api/conversations/*", authMiddleware);
7888
- app29.use("/api/volute/*", authMiddleware);
7889
- app29.use("/api/system/*", authMiddleware);
7890
- app29.use("/api/env/*", authMiddleware);
7891
- app29.use("/api/prompts/*", authMiddleware);
7892
- app29.use("/api/skills/*", authMiddleware);
7893
- app29.use("/api/extensions/*", authMiddleware);
7894
- app29.use("/api/bridges/*", authMiddleware);
7895
- app29.use("/api/config/*", authMiddleware);
7896
- app29.use("/api/v1/*", authMiddleware);
7897
- app29.route("/api/setup", setup_default);
7898
- app29.route("/public", public_files_default);
7899
- app29.route("/api/config", config_default);
7900
- var routes = app29.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default2).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/conversations", user_conversations_default).route("/api/volute/channels", channels_default2).route("/api/volute", unifiedChatApp).route("/api/bridges", bridges_default).route("/api/extensions", extensions_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", chat_default);
7901
- app29.route("/api/v1/minds", minds_default);
7902
- app29.route("/api/v1/minds", typing_default);
7903
- app29.route("/api/v1/minds", variants_default);
7904
- app29.route("/api/v1/minds", files_default);
7905
- app29.route("/api/v1/minds", env_default);
7906
- app29.route("/api/v1/minds", mind_skills_default);
7907
- app29.route("/api/v1/minds", schedules_default);
7908
- app29.route("/api/v1/minds", logs_default);
7909
- app29.route("/api/v1/system", system_default);
7910
- app29.route("/api/v1/system", update_default);
7911
- app29.route("/api/v1/prompts", prompts_default);
7912
- app29.route("/api/v1/skills", skills_default);
7913
- app29.route("/api/v1/env", sharedEnvApp);
7914
- app29.route("/api/v1/channels", channels_default2);
7915
- var app_default = app29;
8034
+ app27.use("/api/*", bodyLimit({ maxSize: 10 * 1024 * 1024 }));
8035
+ app27.use("/api/*", csrf());
8036
+ app27.use("/api/activity/*", authMiddleware);
8037
+ app27.use("/api/minds/*", authMiddleware);
8038
+ app27.use("/api/conversations/*", authMiddleware);
8039
+ app27.use("/api/system/*", authMiddleware);
8040
+ app27.use("/api/env/*", authMiddleware);
8041
+ app27.use("/api/prompts/*", authMiddleware);
8042
+ app27.use("/api/skills/*", authMiddleware);
8043
+ app27.use("/api/extensions/*", authMiddleware);
8044
+ app27.use("/api/bridges/*", authMiddleware);
8045
+ app27.use("/api/config/*", authMiddleware);
8046
+ app27.use("/api/v1/*", authMiddleware);
8047
+ app27.route("/api/setup", setup_default);
8048
+ app27.route("/public", public_files_default);
8049
+ app27.route("/api/config", config_default);
8050
+ var routes = app27.route("/api/activity", activity_default).route("/api/keys", keys_default).route("/api/auth", auth_default).route("/api/system", system_default).route("/api/system", update_default).route("/api/minds", minds_default).route("/api/minds", chat_default).route("/api/minds", schedules_default).route("/api/minds", logs_default).route("/api/minds", typing_default).route("/api/minds", variants_default).route("/api/minds", file_sharing_default).route("/api/minds", files_default).route("/api/minds", channels_default).route("/api/minds", shared_default).route("/api/minds", env_default).route("/api/minds", mind_skills_default).route("/api/minds", conversations_default2).route("/api/env", sharedEnvApp).route("/api/prompts", prompts_default).route("/api/skills", skills_default).route("/api/bridges", bridges_default).route("/api/extensions", extensions_default).route("/api/v1/conversations", conversations_default).route("/api/v1/events", events_default).route("/api/v1", unifiedChatApp).route("/api/v1/channels", channels_default2).route("/api/v1/history", history_default);
8051
+ app27.route("/api/v1/minds", minds_default);
8052
+ app27.route("/api/v1/minds", chat_default);
8053
+ app27.route("/api/v1/minds", typing_default);
8054
+ app27.route("/api/v1/minds", variants_default);
8055
+ app27.route("/api/v1/minds", files_default);
8056
+ app27.route("/api/v1/minds", env_default);
8057
+ app27.route("/api/v1/minds", mind_skills_default);
8058
+ app27.route("/api/v1/minds", schedules_default);
8059
+ app27.route("/api/v1/minds", logs_default);
8060
+ app27.route("/api/v1/system", system_default);
8061
+ app27.route("/api/v1/system", update_default);
8062
+ app27.route("/api/v1/prompts", prompts_default);
8063
+ app27.route("/api/v1/skills", skills_default);
8064
+ app27.route("/api/v1/env", sharedEnvApp);
8065
+ app27.route("/api/conversations", conversations_default);
8066
+ var app_default = app27;
7916
8067
 
7917
8068
  // src/web/server.ts
7918
8069
  import { existsSync as existsSync12 } from "fs";
@@ -8044,18 +8195,18 @@ async function startDaemon(opts) {
8044
8195
  } catch (err) {
8045
8196
  logger_default.warn("failed to initialize shared repo", logger_default.errorData(err));
8046
8197
  }
8047
- const { migrateSetupCompleted } = await import("./setup-SZIARWI6.js");
8198
+ const { migrateSetupCompleted } = await import("./setup-TISPCO22.js");
8048
8199
  migrateSetupCompleted();
8049
8200
  await (await import("./db-F34YLV7D.js")).getDb();
8050
8201
  try {
8051
- const { eq: eq7, and: and5 } = await import("drizzle-orm");
8202
+ const { eq: eq8, and: and6 } = await import("drizzle-orm");
8052
8203
  const { users: users2 } = await import("./schema-PA3M5ZKH.js");
8053
8204
  const db = await (await import("./db-F34YLV7D.js")).getDb();
8054
- await db.update(users2).set({ role: "system" }).where(and5(eq7(users2.user_type, "system"), eq7(users2.role, "user")));
8205
+ await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
8055
8206
  } catch (err) {
8056
8207
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
8057
8208
  }
8058
- const { initSandbox } = await import("./sandbox-JANNTX6U.js");
8209
+ const { initSandbox } = await import("./sandbox-GJOK4QLQ.js");
8059
8210
  await initSandbox();
8060
8211
  try {
8061
8212
  await syncBuiltinSkills();
@@ -8075,7 +8226,7 @@ async function startDaemon(opts) {
8075
8226
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
8076
8227
  }
8077
8228
  try {
8078
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-6DMGES3I.js");
8229
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-GKCDSO4T.js");
8079
8230
  await getOrCreateSystemUser2();
8080
8231
  } catch (err) {
8081
8232
  logger_default.warn(
@@ -8156,10 +8307,10 @@ async function startDaemon(opts) {
8156
8307
  await Promise.all(workers);
8157
8308
  }
8158
8309
  try {
8159
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-SZIARWI6.js");
8310
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-TISPCO22.js");
8160
8311
  if (isSetupComplete2()) {
8161
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-N4W4UQRH.js");
8162
- const { startSpiritFull } = await import("./mind-service-AV273WT4.js");
8312
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-VRONKFMF.js");
8313
+ const { startSpiritFull } = await import("./mind-service-2MQ6UK5N.js");
8163
8314
  await ensureSpiritProject();
8164
8315
  await syncSpiritTemplate();
8165
8316
  const spiritEntry = await findMind("volute");
@@ -8176,7 +8327,7 @@ async function startDaemon(opts) {
8176
8327
  bridgeManager.startBridges(daemonPort).catch((err) => {
8177
8328
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8178
8329
  });
8179
- import("./cloud-sync-JN3NWKEM.js").then(
8330
+ import("./cloud-sync-4NWLMFVH.js").then(
8180
8331
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8181
8332
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8182
8333
  })
@@ -8184,7 +8335,7 @@ async function startDaemon(opts) {
8184
8335
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8185
8336
  });
8186
8337
  try {
8187
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-TCKWBZZG.js");
8338
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-NBI2MTJO.js");
8188
8339
  backfillTemplateHashes();
8189
8340
  notifyVersionUpdate().catch((err) => {
8190
8341
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));