volute 0.42.0 → 0.43.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 (129) hide show
  1. package/dist/{accept-7YVHZKRD.js → accept-N5XULCXK.js} +1 -1
  2. package/dist/{activity-events-KFKVRZE3.js → activity-events-UISXY4Y5.js} +3 -3
  3. package/dist/{ai-service-3GBOJGUA.js → ai-service-FBCCEYF5.js} +8 -4
  4. package/dist/{archive-4PPYF7LL.js → archive-I32WHCJB.js} +3 -3
  5. package/dist/{auth-EAASQPTN.js → auth-3RIG6GKN.js} +4 -4
  6. package/dist/{bridge-WPYSOQKR.js → bridge-2PO3AI5D.js} +1 -1
  7. package/dist/{chat-7DOENBY2.js → chat-SDSX5TVV.js} +8 -8
  8. package/dist/{chunk-M2YMNTHK.js → chunk-2HIVYZDB.js} +1 -1
  9. package/dist/{chunk-TKOWXAHG.js → chunk-3WLWKOGM.js} +21 -6
  10. package/dist/{chunk-QPTQKGGF.js → chunk-57LBQ7JX.js} +1 -1
  11. package/dist/{chunk-VSUZE2PD.js → chunk-65OLHBCL.js} +13 -12
  12. package/dist/{chunk-K547QOCM.js → chunk-7G2EEYXX.js} +2 -2
  13. package/dist/{chunk-E7XWFDZL.js → chunk-AD46AYZQ.js} +1 -1
  14. package/dist/{chunk-CGPESELY.js → chunk-ATEX3ZPG.js} +21 -5
  15. package/dist/{chunk-WR7CSHQ4.js → chunk-C6ESRQZ7.js} +1 -1
  16. package/dist/{chunk-OSUF75I7.js → chunk-EE7SZJON.js} +2 -2
  17. package/dist/{chunk-5QG36RV7.js → chunk-EFL5UOY4.js} +3 -3
  18. package/dist/{chunk-JLX3GC7T.js → chunk-FSSUANZJ.js} +5 -5
  19. package/dist/{chunk-2YRTSK5K.js → chunk-FZ5TH5PF.js} +14 -11
  20. package/dist/{chunk-OEJFZHZZ.js → chunk-GZJFC76M.js} +1 -1
  21. package/dist/{chunk-KUJVCDWT.js → chunk-JVK6QQIC.js} +2 -2
  22. package/dist/{chunk-YLTIEEVL.js → chunk-K4EHBHPE.js} +3 -3
  23. package/dist/{chunk-MHFAWJCC.js → chunk-L3IV23ZG.js} +12 -1
  24. package/dist/{chunk-VBKRZCR7.js → chunk-M3PGGSBH.js} +1 -1
  25. package/dist/{chunk-G777ME4L.js → chunk-O3DXAR44.js} +2 -2
  26. package/dist/{chunk-KZAL7HEQ.js → chunk-ONM2AZFL.js} +1 -1
  27. package/dist/{chunk-52NMCDBS.js → chunk-PF2AB7VJ.js} +568 -378
  28. package/dist/chunk-PGPLCJPK.js +272 -0
  29. package/dist/{chunk-IFNSAOA4.js → chunk-RBNMDAKB.js} +4 -0
  30. package/dist/{chunk-62ICA2FF.js → chunk-SPLPKBES.js} +17 -1
  31. package/dist/{chunk-BLOAJLGI.js → chunk-XDTKJNW5.js} +1 -1
  32. package/dist/{chunk-IUZ3EV2D.js → chunk-YAMO2U3N.js} +1 -1
  33. package/dist/{chunk-4ZSUUBG5.js → chunk-YSV3VJQK.js} +55 -2
  34. package/dist/{chunk-RKBYFLOR.js → chunk-ZO7IQJKO.js} +3 -3
  35. package/dist/cli.js +22 -22
  36. package/dist/{clock-HP2TGMG5.js → clock-K43IMROX.js} +3 -3
  37. package/dist/{cloud-sync-3OPHFOYQ.js → cloud-sync-FRYNWKCR.js} +16 -17
  38. package/dist/{config-W7MS3PWY.js → config-TJLYUV3U.js} +3 -3
  39. package/dist/{conversations-FSZCUUYO.js → conversations-67WWW25C.js} +4 -4
  40. package/dist/{create-YNBZWD34.js → create-7W3LV3J7.js} +2 -2
  41. package/dist/{create-CJ2SR4EU.js → create-DF3KISTW.js} +1 -1
  42. package/dist/{daemon-client-EWDPOOHC.js → daemon-client-IYR4KCPU.js} +3 -1
  43. package/dist/{daemon-restart-RAKGR56L.js → daemon-restart-5GVZ5V3X.js} +7 -8
  44. package/dist/daemon.js +541 -440
  45. package/dist/{db-ZJJJYJBU.js → db-CZV62BOQ.js} +2 -2
  46. package/dist/{delete-4ND5PLE7.js → delete-BFMIGAIB.js} +1 -1
  47. package/dist/delivery-manager-5J2BE22P.js +32 -0
  48. package/dist/{delivery-router-FDBZJQOY.js → delivery-router-HXBHYHJM.js} +7 -5
  49. package/dist/down-RRNYIGCU.js +16 -0
  50. package/dist/echo-text-XKBJM6RV.js +32 -0
  51. package/dist/{env-XX2MBZCZ.js → env-VZ4MLFQW.js} +1 -1
  52. package/dist/{exec-ZDD7NEB4.js → exec-SJK7VUTC.js} +3 -4
  53. package/dist/{export-5DOQV2WM.js → export-7LPCIB5J.js} +4 -4
  54. package/dist/{extension-BCFYXI7A.js → extension-SEID47SR.js} +1 -1
  55. package/dist/{extensions-BTNIUS5J.js → extensions-76EGTMFY.js} +16 -17
  56. package/dist/{files-K2MEMV3W.js → files-LV7VAKPC.js} +4 -4
  57. package/dist/{import-V6QEHMPQ.js → import-XPECCRQ7.js} +8 -8
  58. package/dist/{isolation-F7ABZMDA.js → isolation-5AYEY55P.js} +5 -3
  59. package/dist/{join-4S3KT7NE.js → join-RXPXGV4L.js} +1 -1
  60. package/dist/{list-XJB5WII6.js → list-OXIAY3W7.js} +1 -1
  61. package/dist/{login-PHMTLNUF.js → login-KJSN4JDA.js} +3 -3
  62. package/dist/{login-YWD6PUOT.js → login-MY6ETFYB.js} +1 -1
  63. package/dist/{logout-Q2WICH4V.js → logout-NCG3TMDL.js} +1 -1
  64. package/dist/{logout-BP3R3CAA.js → logout-TMJGUQV2.js} +3 -3
  65. package/dist/message-delivery-WJBZFALY.js +34 -0
  66. package/dist/{mind-VXV6ZYZY.js → mind-QEWCR7FA.js} +18 -18
  67. package/dist/{mind-activity-tracker-HWVFE533.js → mind-activity-tracker-JHAT6456.js} +4 -4
  68. package/dist/{mind-history-TDUQFYAG.js → mind-history-VRO5A76U.js} +1 -1
  69. package/dist/{mind-list-2T353Q5G.js → mind-list-4D7J4CHD.js} +1 -1
  70. package/dist/{mind-profile-2HHR7LZ7.js → mind-profile-PEQYASQX.js} +1 -1
  71. package/dist/mind-service-ZBVUMJ5H.js +36 -0
  72. package/dist/{mind-sleep-4NXV4J4U.js → mind-sleep-YMBR3I35.js} +1 -1
  73. package/dist/{mind-status-CG66OXDK.js → mind-status-UO34ASXZ.js} +1 -1
  74. package/dist/{mind-wake-TNB54DBR.js → mind-wake-LCLGTXA2.js} +1 -1
  75. package/dist/{package-UWH5IM76.js → package-ZOI2KAYC.js} +1 -1
  76. package/dist/{read-JLR4NQXU.js → read-QVAXMTPV.js} +1 -1
  77. package/dist/{register-IQ2M4UO6.js → register-Y2H4PY5R.js} +1 -1
  78. package/dist/{registry-MDLNPTTI.js → registry-7LQHTRUX.js} +2 -2
  79. package/dist/{reject-N2D4CZND.js → reject-EV35X64Y.js} +1 -1
  80. package/dist/{restart-43A5MBGV.js → restart-FJQ662XJ.js} +1 -1
  81. package/dist/{sandbox-E7RWZDVG.js → sandbox-SJH3XOPZ.js} +4 -4
  82. package/dist/scheduler-BR6C6B3E.js +30 -0
  83. package/dist/{schema-HHYJDG7B.js → schema-LRUTFOER.js} +1 -1
  84. package/dist/{seed-YQAOIXIY.js → seed-KOG6KJAO.js} +1 -1
  85. package/dist/{seed-check-N5ALJBT6.js → seed-check-VLTVILOV.js} +1 -1
  86. package/dist/{seed-cmd-7KJS4LCA.js → seed-cmd-GJFENEJH.js} +3 -3
  87. package/dist/{seed-create-Z7NDRRGP.js → seed-create-YXO6R4KE.js} +4 -4
  88. package/dist/{seed-sprout-PYDMVVUL.js → seed-sprout-BBU64QXP.js} +8 -9
  89. package/dist/{send-KQECN2EL.js → send-ZBZWIH5V.js} +4 -4
  90. package/dist/{service-Q5OVY5AK.js → service-4EVZKGSA.js} +4 -5
  91. package/dist/{service-install-GTDUHP6R.js → service-install-SHRM6NHK.js} +5 -6
  92. package/dist/{setup-IYOUPCIK.js → setup-25MMJ3J4.js} +3 -3
  93. package/dist/{setup-VARJ7N6Z.js → setup-354PDHUF.js} +8 -10
  94. package/dist/{skill-E43BI34Y.js → skill-FKFHIJRR.js} +1 -1
  95. package/dist/{skills-YBCYVOPQ.js → skills-DPI3WZPQ.js} +5 -6
  96. package/dist/sleep-manager-BVU5D2AE.js +34 -0
  97. package/dist/{spirit-NJFAY5CZ.js → spirit-JQQHUVWR.js} +7 -8
  98. package/dist/{split-5IBUODEE.js → split-WBMZXBHF.js} +1 -1
  99. package/dist/{sprout-B27C2WGE.js → sprout-4WAYBUVV.js} +1 -1
  100. package/dist/{start-5AYFGNS7.js → start-5JE3TV66.js} +1 -1
  101. package/dist/{status-ECZHHSY5.js → status-WTU2WHXY.js} +12 -9
  102. package/dist/{stop-WJ6IG6OK.js → stop-AAMTG3TU.js} +1 -1
  103. package/dist/system-chat-KMQIP7VH.js +34 -0
  104. package/dist/{systems-5MA57Q7G.js → systems-VNB5WKSS.js} +4 -4
  105. package/dist/{tailscale-TIRTGM6E.js → tailscale-Q7ZJL7S7.js} +2 -2
  106. package/dist/{turn-tracker-W4TASMR7.js → turn-tracker-2GMKJQTN.js} +7 -5
  107. package/dist/up-7RSBEKW6.js +18 -0
  108. package/dist/{update-OPR4KU4M.js → update-EJ7Q77ZX.js} +9 -10
  109. package/dist/{update-check-OG27AL4Z.js → update-check-ACYYNKTW.js} +3 -3
  110. package/dist/{upgrade-QB7AHIKX.js → upgrade-37DAGF6H.js} +1 -1
  111. package/dist/{version-notify-TCKAX7ZA.js → version-notify-DCAIXXZB.js} +17 -18
  112. package/dist/web-assets/assets/{index-CrFAsfiE.js → index-DTDmEfPU.js} +2 -2
  113. package/dist/web-assets/index.html +1 -1
  114. package/drizzle/0010_delivery_queue_redrive.sql +10 -0
  115. package/drizzle/meta/_journal.json +7 -0
  116. package/package.json +1 -1
  117. package/templates/codex/src/server.ts +2 -8
  118. package/dist/chunk-GN52E2DP.js +0 -59
  119. package/dist/chunk-TFBQESH7.js +0 -179
  120. package/dist/delivery-manager-5Y35DOX3.js +0 -33
  121. package/dist/down-DS6WCT34.js +0 -17
  122. package/dist/echo-text-YP3JVLNA.js +0 -32
  123. package/dist/message-delivery-FPIL26WV.js +0 -41
  124. package/dist/mind-manager-OYOIBGK6.js +0 -33
  125. package/dist/mind-service-PR4PR4XI.js +0 -37
  126. package/dist/scheduler-KXUY2I7W.js +0 -31
  127. package/dist/sleep-manager-FJLASHHW.js +0 -35
  128. package/dist/system-chat-P6KVGW4T.js +0 -35
  129. package/dist/up-POJX6S5Y.js +0 -19
package/dist/daemon.js CHANGED
@@ -6,21 +6,21 @@ import {
6
6
  rejectPending,
7
7
  stageFile,
8
8
  validateFilePath
9
- } from "./chunk-M2YMNTHK.js";
9
+ } from "./chunk-2HIVYZDB.js";
10
10
  import {
11
11
  findOpenClawSession,
12
12
  importOpenClawConnectors,
13
13
  importPiSession,
14
14
  parseNameFromIdentity
15
- } from "./chunk-KUJVCDWT.js";
15
+ } from "./chunk-JVK6QQIC.js";
16
16
  import {
17
17
  checkForUpdate,
18
18
  checkForUpdateCached,
19
19
  getCurrentVersion
20
- } from "./chunk-E7XWFDZL.js";
20
+ } from "./chunk-AD46AYZQ.js";
21
21
  import {
22
22
  isHomeOnlyArchive
23
- } from "./chunk-OEJFZHZZ.js";
23
+ } from "./chunk-GZJFC76M.js";
24
24
  import {
25
25
  computeTemplateHash
26
26
  } from "./chunk-QMJCNZ6C.js";
@@ -30,7 +30,7 @@ import {
30
30
  fixModelEscapes,
31
31
  getPlatformDriver,
32
32
  routeOutboundBridge
33
- } from "./chunk-2YRTSK5K.js";
33
+ } from "./chunk-FZ5TH5PF.js";
34
34
  import {
35
35
  getBridgeConfig,
36
36
  readBridgesConfig,
@@ -39,13 +39,15 @@ import {
39
39
  resolveChannelMapping,
40
40
  setBridgeConfig,
41
41
  setChannelMapping
42
- } from "./chunk-QPTQKGGF.js";
42
+ } from "./chunk-57LBQ7JX.js";
43
43
  import {
44
+ MindStartupError,
44
45
  RestartTracker,
45
46
  RotatingLog,
46
47
  SESSION_MAX_AGE,
47
48
  announceToSystem,
48
49
  authMiddleware,
50
+ checkHealth,
49
51
  cleanExpiredSessions,
50
52
  clearDeliveredNotices,
51
53
  createSession,
@@ -94,19 +96,17 @@ import {
94
96
  startMindFull,
95
97
  stopMindFull,
96
98
  syncProviderToMinds,
97
- tagUntaggedInbound,
98
- tagUntaggedOutbound,
99
99
  uninstallNpmExtension,
100
100
  writeSystemsConfig
101
- } from "./chunk-52NMCDBS.js";
101
+ } from "./chunk-PF2AB7VJ.js";
102
102
  import {
103
103
  loadMergedEnv,
104
104
  mindEnvPath,
105
105
  readEnv,
106
106
  sharedEnvPath,
107
107
  writeEnv
108
- } from "./chunk-BLOAJLGI.js";
109
- import "./chunk-VSUZE2PD.js";
108
+ } from "./chunk-XDTKJNW5.js";
109
+ import "./chunk-65OLHBCL.js";
110
110
  import {
111
111
  applyInitFiles,
112
112
  applyTemplateHomeFiles,
@@ -135,7 +135,7 @@ import {
135
135
  syncBuiltinSkills,
136
136
  uninstallSkill,
137
137
  updateSkill
138
- } from "./chunk-JLX3GC7T.js";
138
+ } from "./chunk-FSSUANZJ.js";
139
139
  import {
140
140
  addMessage,
141
141
  createChannel,
@@ -167,7 +167,7 @@ import {
167
167
  setConversationPrivate,
168
168
  subscribe as subscribe2,
169
169
  updateChannelSettings
170
- } from "./chunk-YLTIEEVL.js";
170
+ } from "./chunk-K4EHBHPE.js";
171
171
  import {
172
172
  readVoluteConfig,
173
173
  writeVoluteConfig
@@ -175,15 +175,15 @@ import {
175
175
  import {
176
176
  isSandboxEnabled,
177
177
  wrapForSandbox
178
- } from "./chunk-WR7CSHQ4.js";
178
+ } from "./chunk-C6ESRQZ7.js";
179
179
  import {
180
180
  getActiveMinds,
181
181
  onMindEvent,
182
182
  stopAll
183
- } from "./chunk-VBKRZCR7.js";
183
+ } from "./chunk-M3PGGSBH.js";
184
184
  import {
185
185
  extractTextContent
186
- } from "./chunk-62ICA2FF.js";
186
+ } from "./chunk-SPLPKBES.js";
187
187
  import {
188
188
  PROMPT_DEFAULTS,
189
189
  PROMPT_KEYS,
@@ -206,20 +206,19 @@ import {
206
206
  summarizeTurn,
207
207
  takeErrored,
208
208
  trackToolUse
209
- } from "./chunk-TKOWXAHG.js";
210
- import {
211
- exec,
212
- gitExec,
213
- resolveVoluteBin
214
- } from "./chunk-GN52E2DP.js";
209
+ } from "./chunk-3WLWKOGM.js";
210
+ import "./chunk-ATEX3ZPG.js";
215
211
  import {
216
212
  chownMindDir,
217
213
  createMindUser,
218
214
  deleteMindUser,
219
215
  ensureVoluteGroup,
216
+ exec,
217
+ gitExec,
220
218
  isIsolationEnabled,
219
+ resolveVoluteBin,
221
220
  wrapForIsolation
222
- } from "./chunk-TFBQESH7.js";
221
+ } from "./chunk-PGPLCJPK.js";
223
222
  import {
224
223
  addCustomModel,
225
224
  buildCustomModel,
@@ -240,12 +239,12 @@ import {
240
239
  setProviderRefreshHook,
241
240
  setUtilityModel,
242
241
  unqualifyModelId
243
- } from "./chunk-4ZSUUBG5.js";
242
+ } from "./chunk-YSV3VJQK.js";
244
243
  import {
245
244
  isSetupComplete,
246
245
  readGlobalConfig,
247
246
  writeGlobalConfig
248
- } from "./chunk-KZAL7HEQ.js";
247
+ } from "./chunk-ONM2AZFL.js";
249
248
  import {
250
249
  approveUser,
251
250
  changePassword,
@@ -264,11 +263,11 @@ import {
264
263
  syncMindProfile,
265
264
  updateUserProfile,
266
265
  verifyUser
267
- } from "./chunk-5QG36RV7.js";
266
+ } from "./chunk-EFL5UOY4.js";
268
267
  import {
269
268
  broadcast,
270
269
  subscribe
271
- } from "./chunk-K547QOCM.js";
270
+ } from "./chunk-7G2EEYXX.js";
272
271
  import {
273
272
  logBuffer,
274
273
  logger_default
@@ -300,7 +299,7 @@ import {
300
299
  validateMindName,
301
300
  voluteHome,
302
301
  voluteSystemDir
303
- } from "./chunk-IUZ3EV2D.js";
302
+ } from "./chunk-YAMO2U3N.js";
304
303
  import {
305
304
  activity,
306
305
  channels,
@@ -311,11 +310,11 @@ import {
311
310
  systemPrompts,
312
311
  turns,
313
312
  users
314
- } from "./chunk-MHFAWJCC.js";
313
+ } from "./chunk-L3IV23ZG.js";
315
314
  import "./chunk-K3NQKI34.js";
316
315
 
317
316
  // packages/daemon/src/daemon.ts
318
- import { randomBytes } from "crypto";
317
+ import { randomBytes as randomBytes2 } from "crypto";
319
318
  import { chmodSync, mkdirSync as mkdirSync10, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
320
319
  import { homedir as homedir3 } from "os";
321
320
  import { resolve as resolve17 } from "path";
@@ -1104,17 +1103,21 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1104
1103
  }).get("/ai/models", requireAdmin, async (c) => {
1105
1104
  const models = await getAvailableModels();
1106
1105
  const enabled = new Set(getEnabledModels());
1107
- const customIds = new Set(getCustomModels().map((m) => m.id));
1106
+ const customIds = new Set(getCustomModels().map((m) => `${m.provider}:${m.id}`));
1108
1107
  return c.json(
1109
- models.map((m) => ({
1110
- id: m.id,
1111
- name: m.name,
1112
- provider: m.provider,
1113
- contextWindow: m.contextWindow,
1114
- maxTokens: m.maxTokens,
1115
- enabled: enabled.has(m.id),
1116
- custom: customIds.has(m.id)
1117
- }))
1108
+ models.map((m) => {
1109
+ const qualifiedId = `${m.provider}:${m.id}`;
1110
+ return {
1111
+ id: m.id,
1112
+ qualifiedId,
1113
+ name: m.name,
1114
+ provider: m.provider,
1115
+ contextWindow: m.contextWindow,
1116
+ maxTokens: m.maxTokens,
1117
+ enabled: enabled.has(qualifiedId),
1118
+ custom: customIds.has(qualifiedId)
1119
+ };
1120
+ })
1118
1121
  );
1119
1122
  }).put(
1120
1123
  "/ai/models",
@@ -1140,14 +1143,8 @@ var app = new Hono().post("/restart", requireAdmin, (c) => {
1140
1143
  400
1141
1144
  );
1142
1145
  }
1143
- if (getCustomModels().some((m) => m.id === id && m.provider !== provider)) {
1144
- return c.json(
1145
- { error: `A custom model "${id}" is already registered under another provider` },
1146
- 400
1147
- );
1148
- }
1149
1146
  addCustomModel(provider, id, name);
1150
- setEnabledModels([.../* @__PURE__ */ new Set([...getEnabledModels(), id])]);
1147
+ setEnabledModels([.../* @__PURE__ */ new Set([...getEnabledModels(), `${provider}:${id}`])]);
1151
1148
  return c.json({ ok: true });
1152
1149
  }
1153
1150
  ).delete("/ai/models/custom", requireAdmin, (c) => {
@@ -1849,6 +1846,76 @@ async function findOrCreatePuppet(platform, platformId, displayName) {
1849
1846
  }
1850
1847
  }
1851
1848
 
1849
+ // packages/daemon/src/lib/delivery/fan-out.ts
1850
+ var MAX_CONSECUTIVE_MIND_TURNS = 6;
1851
+ var consecutiveMindTurns = /* @__PURE__ */ new Map();
1852
+ async function emitLoopNotice(conversationId) {
1853
+ const systemUser = await getOrCreateSystemUser();
1854
+ await addMessage(conversationId, "user", systemUser.username, [
1855
+ {
1856
+ type: "text",
1857
+ text: `[System] This exchange has continued for ${MAX_CONSECUTIVE_MIND_TURNS} consecutive automated turns without human input, so automatic delivery between minds has been paused. Send a message to resume.`
1858
+ }
1859
+ ]);
1860
+ }
1861
+ async function fanOutToMinds(opts) {
1862
+ const participants = opts.participants ?? await getParticipants(opts.conversationId);
1863
+ const mindParticipants = participants.filter(
1864
+ (p) => p.userType === "mind" || p.userType === "system"
1865
+ );
1866
+ const participantNames = participants.map((p) => p.username);
1867
+ const isDM = opts.isDM ?? participants.length === 2;
1868
+ if (mindParticipants.length > 1) {
1869
+ if (opts.senderIsMind) {
1870
+ const count = (consecutiveMindTurns.get(opts.conversationId) ?? 0) + 1;
1871
+ consecutiveMindTurns.set(opts.conversationId, count);
1872
+ if (count > MAX_CONSECUTIVE_MIND_TURNS) {
1873
+ if (count === MAX_CONSECUTIVE_MIND_TURNS + 1) {
1874
+ await emitLoopNotice(opts.conversationId).catch(
1875
+ (err) => logger_default.warn(`failed to emit loop notice for ${opts.conversationId}`, logger_default.errorData(err))
1876
+ );
1877
+ }
1878
+ logger_default.warn(
1879
+ `loop protection: paused mind fan-out for ${opts.conversationId} after ${count} consecutive mind turns`
1880
+ );
1881
+ return;
1882
+ }
1883
+ } else {
1884
+ consecutiveMindTurns.delete(opts.conversationId);
1885
+ }
1886
+ }
1887
+ const manager = getMindManager();
1888
+ const sm = getSleepManagerIfReady();
1889
+ const targetMinds = mindParticipants.map((ap) => {
1890
+ const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
1891
+ if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
1892
+ return null;
1893
+ }).filter((n) => n !== null && n !== opts.senderName);
1894
+ for (const mindName of targetMinds) {
1895
+ const target = opts.targetName ? opts.targetName(mindName) : mindName;
1896
+ const channel = buildVoluteSlug({
1897
+ participants,
1898
+ mindUsername: mindName,
1899
+ conversationId: opts.conversationId,
1900
+ ...opts.slugExtra
1901
+ });
1902
+ const typingMap = getTypingMap();
1903
+ const currentlyTyping = typingMap.get(channel).filter((name) => participantNames.includes(name));
1904
+ deliverMessage(target, {
1905
+ content: opts.contentBlocks,
1906
+ channel,
1907
+ conversationId: opts.conversationId,
1908
+ sender: opts.senderName,
1909
+ participants: participantNames,
1910
+ participantCount: participants.length,
1911
+ isDM,
1912
+ ...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
1913
+ }).catch((err) => {
1914
+ logger_default.warn(`fan-out delivery failed for ${target}`, logger_default.errorData(err));
1915
+ });
1916
+ }
1917
+ }
1918
+
1852
1919
  // packages/daemon/src/web/api/bridges.ts
1853
1920
  var inboundSchema = z3.object({
1854
1921
  content: z3.array(
@@ -1922,7 +1989,7 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
1922
1989
  }
1923
1990
  const participants = await getParticipants(channel.id);
1924
1991
  if (!participants.some((p) => p.userId === puppet.id)) {
1925
- const { addParticipant } = await import("./conversations-FSZCUUYO.js");
1992
+ const { addParticipant } = await import("./conversations-67WWW25C.js");
1926
1993
  await addParticipant(channel.id, puppet.id);
1927
1994
  }
1928
1995
  const contentBlocks = body.content;
@@ -2012,34 +2079,13 @@ var app4 = new Hono4().post("/:platform/inbound", zValidator3("json", inboundSch
2012
2079
  return c.json(config2.channelMappings);
2013
2080
  });
2014
2081
  async function fanOutToBridgedMinds(opts) {
2015
- const participants = await getParticipants(opts.conversationId);
2016
- const mindParticipants = participants.filter((p) => p.userType === "mind");
2017
- const participantNames = participants.map((p) => p.username);
2018
- const { getMindManager: getMindManager2 } = await import("./mind-manager-OYOIBGK6.js");
2019
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
2020
- const manager = getMindManager2();
2021
- const sm = getSleepManagerIfReady2();
2022
- const targetMinds = mindParticipants.filter((ap) => {
2023
- return (manager.isRunning(ap.username) || sm?.isSleeping(ap.username)) && ap.username !== opts.senderName;
2024
- }).map((ap) => ap.username);
2025
- for (const mindName of targetMinds) {
2026
- const channel = buildVoluteSlug({
2027
- participants,
2028
- mindUsername: mindName,
2029
- conversationId: opts.conversationId
2030
- });
2031
- deliverMessage(mindName, {
2032
- content: opts.contentBlocks,
2033
- channel,
2034
- conversationId: opts.conversationId,
2035
- sender: opts.senderName,
2036
- participants: participantNames,
2037
- participantCount: participants.length,
2038
- isDM: opts.isDM
2039
- }).catch((err) => {
2040
- logger_default.warn(`bridge fan-out delivery failed for ${mindName}`, logger_default.errorData(err));
2041
- });
2042
- }
2082
+ await fanOutToMinds({
2083
+ conversationId: opts.conversationId,
2084
+ contentBlocks: opts.contentBlocks,
2085
+ senderName: opts.senderName,
2086
+ senderIsMind: false,
2087
+ isDM: opts.isDM
2088
+ });
2043
2089
  }
2044
2090
  var bridges_default = app4;
2045
2091
 
@@ -2082,7 +2128,7 @@ config.get("/models", async (c) => {
2082
2128
  id: m.id,
2083
2129
  name: m.name,
2084
2130
  provider: m.provider,
2085
- enabled: enabled.has(m.id)
2131
+ enabled: enabled.has(`${m.provider}:${m.id}`)
2086
2132
  }));
2087
2133
  return c.json(models);
2088
2134
  });
@@ -2228,7 +2274,7 @@ async function notifyMind(mindName, message) {
2228
2274
  const entry = await findMind(mindName);
2229
2275
  if (!entry) return;
2230
2276
  try {
2231
- const { sendSystemMessage } = await import("./system-chat-P6KVGW4T.js");
2277
+ const { sendSystemMessage } = await import("./system-chat-KMQIP7VH.js");
2232
2278
  await sendSystemMessage(mindName, message);
2233
2279
  } catch (err) {
2234
2280
  logger_default.warn(`[file-sharing] notify mind ${mindName} failed`, logger_default.errorData(err));
@@ -3163,10 +3209,13 @@ import {
3163
3209
  } from "fs";
3164
3210
  import { basename, resolve as resolve11 } from "path";
3165
3211
  import { zValidator as zValidator5 } from "@hono/zod-validator";
3166
- import { and as and4, desc as desc3, eq as eq4, sql as sql2 } from "drizzle-orm";
3212
+ import { and as and5, desc as desc4, eq as eq5, sql as sql3 } from "drizzle-orm";
3167
3213
  import { Hono as Hono15 } from "hono";
3168
3214
  import { z as z5 } from "zod";
3169
3215
 
3216
+ // packages/daemon/src/lib/daemon/turn-lifecycle.ts
3217
+ import { and as and4, desc as desc3, eq as eq4, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3218
+
3170
3219
  // packages/daemon/src/lib/daemon/error-classify.ts
3171
3220
  function classify(raw) {
3172
3221
  const s = (raw ?? "").toLowerCase();
@@ -3204,6 +3253,178 @@ function classify(raw) {
3204
3253
  };
3205
3254
  }
3206
3255
 
3256
+ // packages/daemon/src/lib/daemon/turn-lifecycle.ts
3257
+ var llog = logger_default.child("turn-lifecycle");
3258
+ var SUBSTANTIVE_TYPES = /* @__PURE__ */ new Set(["thinking", "text", "tool_use", "tool_result", "outbound"]);
3259
+ var MARKER_RE = /\[volute:(?:outbound|activity):\d+\]/g;
3260
+ var noticeDrainWatermarks = /* @__PURE__ */ new Map();
3261
+ function setNoticeDrainWatermark(mind, session, id) {
3262
+ noticeDrainWatermarks.set(`${mind}:${session}`, id);
3263
+ }
3264
+ function markDeliveredOnCleanTurn(mind, session) {
3265
+ if (!session) return;
3266
+ const wmKey = `${mind}:${session}`;
3267
+ const watermark = noticeDrainWatermarks.get(wmKey);
3268
+ noticeDrainWatermarks.delete(wmKey);
3269
+ const errored = takeErrored(mind, session);
3270
+ if (!errored && watermark != null) {
3271
+ clearDeliveredNotices(mind, session, watermark).catch(
3272
+ (err) => llog.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err))
3273
+ );
3274
+ }
3275
+ }
3276
+ async function linkPendingInbound(mind, turnId, channel) {
3277
+ if (!channel) return;
3278
+ const db = await getDb();
3279
+ const recent = await db.select({ id: mindHistory.id }).from(mindHistory).where(
3280
+ and4(
3281
+ eq4(mindHistory.mind, mind),
3282
+ eq4(mindHistory.type, "inbound"),
3283
+ sql2`${mindHistory.turn_id} IS NULL`,
3284
+ eq4(mindHistory.channel, channel)
3285
+ )
3286
+ ).orderBy(desc3(mindHistory.id)).limit(5);
3287
+ if (recent.length === 0) return;
3288
+ const ids = recent.map((r) => r.id);
3289
+ await db.update(mindHistory).set({ turn_id: turnId }).where(inArray2(mindHistory.id, ids));
3290
+ await db.update(turns).set({ trigger_event_id: recent[0].id }).where(eq4(turns.id, turnId));
3291
+ }
3292
+ async function handleMindEvent(mind, event) {
3293
+ if (event.type === "session_start" && event.session) {
3294
+ const activeTurnId = getActiveTurnId(mind);
3295
+ if (activeTurnId) await assignSession(mind, activeTurnId, event.session);
3296
+ }
3297
+ let turnId = getActiveTurnId(mind, event.session);
3298
+ if (!turnId && SUBSTANTIVE_TYPES.has(event.type)) {
3299
+ turnId = await createTurn(mind);
3300
+ if (!turnId) {
3301
+ llog.warn(`skipping turn tracking for ${mind}: createTurn failed`);
3302
+ } else {
3303
+ publish2(mind, { mind, type: "turn_created", turnId });
3304
+ if (event.session) await assignSession(mind, turnId, event.session);
3305
+ try {
3306
+ await linkPendingInbound(mind, turnId, event.channel);
3307
+ } catch (err) {
3308
+ llog.warn(
3309
+ `failed to link trigger inbound for turn ${turnId} (mind: ${mind})`,
3310
+ logger_default.errorData(err)
3311
+ );
3312
+ }
3313
+ }
3314
+ }
3315
+ const cleanContent = event.type === "tool_result" && event.content ? event.content.replace(MARKER_RE, "").trimEnd() : event.content;
3316
+ const db = await getDb();
3317
+ let insertedId;
3318
+ try {
3319
+ const result = await db.insert(mindHistory).values({
3320
+ mind,
3321
+ type: event.type,
3322
+ session: event.session ?? null,
3323
+ channel: event.channel ?? null,
3324
+ message_id: event.messageId ?? null,
3325
+ content: cleanContent ?? null,
3326
+ metadata: event.metadata ? JSON.stringify(event.metadata) : null,
3327
+ turn_id: turnId ?? null
3328
+ }).returning({ id: mindHistory.id });
3329
+ insertedId = result[0]?.id;
3330
+ } catch (err) {
3331
+ llog.error(`failed to persist event for ${mind}`, logger_default.errorData(err));
3332
+ }
3333
+ if (event.type === "tool_use" && insertedId != null) {
3334
+ trackToolUse(mind, event.session, insertedId);
3335
+ }
3336
+ if (event.type === "tool_result" && turnId && event.content) {
3337
+ const toolUseEventId = getLastToolUseEventId(mind, event.session);
3338
+ try {
3339
+ await linkToolResultToTurn(mind, turnId, event.content, toolUseEventId);
3340
+ } catch (err) {
3341
+ llog.error("failed to link tool_result to turn", logger_default.errorData(err));
3342
+ }
3343
+ }
3344
+ publish2(mind, {
3345
+ mind,
3346
+ type: event.type,
3347
+ session: event.session,
3348
+ channel: event.channel,
3349
+ messageId: event.messageId,
3350
+ content: cleanContent,
3351
+ metadata: event.metadata,
3352
+ turnId: turnId ?? void 0
3353
+ });
3354
+ if (event.type === "text" && event.channel && cleanContent) {
3355
+ echoTextToChannel(mind, event.channel, cleanContent, turnId ?? void 0, insertedId).catch(
3356
+ (err) => llog.error(`echo-text failed for ${mind} on ${event.channel}`, logger_default.errorData(err))
3357
+ );
3358
+ }
3359
+ onMindEvent(mind, event.type, event.channel);
3360
+ if ((event.type === "text" || event.type === "outbound") && event.channel) {
3361
+ const map = getTypingMap();
3362
+ publishTypingForChannels(map.deleteSender(mind), map);
3363
+ }
3364
+ if (event.type === "error" && event.session) {
3365
+ markErrored(mind, event.session);
3366
+ const { reason, detail } = classify(event.content ?? "");
3367
+ await recordNotice({
3368
+ mind,
3369
+ session: event.session,
3370
+ kind: "turn_error",
3371
+ reason,
3372
+ detail,
3373
+ raw: event.content ?? null
3374
+ });
3375
+ }
3376
+ if (event.type === "done") {
3377
+ const map = getTypingMap();
3378
+ publishTypingForChannels(map.deleteSender(mind), map);
3379
+ broadcast({ type: "mind_done", mind, summary: "Finished processing" });
3380
+ try {
3381
+ getDeliveryManager().sessionDone(mind, event.session);
3382
+ } catch (err) {
3383
+ if (!(err instanceof Error && err.message.includes("not initialized"))) {
3384
+ llog.error(`delivery manager sessionDone failed for ${mind}`, logger_default.errorData(err));
3385
+ }
3386
+ }
3387
+ await completeTurnAndSummarize(mind, event, insertedId);
3388
+ }
3389
+ if (event.type === "usage" && event.metadata) {
3390
+ const inputTokens = event.metadata.input_tokens ?? 0;
3391
+ const outputTokens = event.metadata.output_tokens ?? 0;
3392
+ const tb = getTokenBudget();
3393
+ tb.recordUsage(mind, inputTokens, outputTokens);
3394
+ if (event.session && tb.noteExceeded(mind)) {
3395
+ void recordNotice({
3396
+ mind,
3397
+ session: event.session,
3398
+ kind: "budget",
3399
+ reason: "token_budget",
3400
+ detail: "You've reached your token budget for this period. Further activity may be paused until the budget resets \u2014 wrap up or prioritize accordingly."
3401
+ });
3402
+ }
3403
+ }
3404
+ return { turnId: turnId ?? void 0, insertedId };
3405
+ }
3406
+ async function completeTurnAndSummarize(mind, event, insertedId) {
3407
+ const finish = async () => {
3408
+ const completedTurnId = await completeTurn(mind, event.session);
3409
+ markDeliveredOnCleanTurn(mind, event.session);
3410
+ if (insertedId != null) {
3411
+ summarizeTurn(mind, event.session, event.channel, insertedId, completedTurnId).catch(
3412
+ (err) => llog.error("turn summarization failed", logger_default.errorData(err))
3413
+ );
3414
+ }
3415
+ };
3416
+ try {
3417
+ const dm = getDeliveryManager();
3418
+ const busy = event.session ? dm.isSessionBusy(mind, event.session) : false;
3419
+ if (!busy) await finish();
3420
+ } catch (err) {
3421
+ if (!(err instanceof Error && err.message.includes("not initialized"))) {
3422
+ llog.error("turn completion check failed", logger_default.errorData(err));
3423
+ }
3424
+ await finish();
3425
+ }
3426
+ }
3427
+
3207
3428
  // packages/daemon/src/lib/mind/consolidate.ts
3208
3429
  import { readdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3209
3430
  import { resolve as resolve9 } from "path";
@@ -3275,6 +3496,39 @@ ${content2}`);
3275
3496
  }
3276
3497
  }
3277
3498
 
3499
+ // packages/daemon/src/lib/mind/last-known-good.ts
3500
+ import { randomBytes } from "crypto";
3501
+ var glog = logger_default.child("last-known-good");
3502
+ async function hasSrcChanges(dir, mindName) {
3503
+ const status = (await gitExec(["status", "--porcelain", "--", "src"], { cwd: dir, mindName })).trim();
3504
+ return status.length > 0;
3505
+ }
3506
+ async function rollbackSrcChanges(dir, mindName) {
3507
+ if (!await hasSrcChanges(dir, mindName)) return { parked: false };
3508
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3509
+ const branch = `broken/${ts}-${randomBytes(4).toString("hex")}`;
3510
+ const opts = { cwd: dir, mindName };
3511
+ await gitExec(["add", "--", "src"], opts);
3512
+ await gitExec(
3513
+ ["commit", "--only", "-m", `Parked broken src changes (${branch})`, "--", "src"],
3514
+ opts
3515
+ );
3516
+ const brokenHash = (await gitExec(["rev-parse", "HEAD"], opts)).trim();
3517
+ await gitExec(["reset", "--mixed", "HEAD~1"], opts);
3518
+ await gitExec(["branch", branch, brokenHash], opts);
3519
+ await gitExec(["checkout", "--", "src"], opts);
3520
+ await gitExec(["clean", "-fdq", "--", "src"], opts);
3521
+ glog.info(`parked broken src changes on ${branch} (${dir})`);
3522
+ return { parked: true, branch };
3523
+ }
3524
+ async function commitSrcChanges(dir, mindName) {
3525
+ if (!await hasSrcChanges(dir, mindName)) return;
3526
+ const opts = { cwd: dir, mindName };
3527
+ await gitExec(["add", "--", "src"], opts);
3528
+ await gitExec(["commit", "--only", "-m", "Update src (self-edit)", "--", "src"], opts);
3529
+ glog.info(`committed known-good src changes (${dir})`);
3530
+ }
3531
+
3278
3532
  // packages/daemon/src/lib/mind/variant-cleanup.ts
3279
3533
  import { existsSync as existsSync6, rmSync as rmSync3 } from "fs";
3280
3534
  async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
@@ -3285,7 +3539,7 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3285
3539
  logger_default.warn(`failed to stop variant ${variantName}`, logger_default.errorData(err));
3286
3540
  }
3287
3541
  }
3288
- const { findMind: findMind2 } = await import("./registry-MDLNPTTI.js");
3542
+ const { findMind: findMind2 } = await import("./registry-7LQHTRUX.js");
3289
3543
  const variantEntry = await findMind2(variantName);
3290
3544
  const branchName = variantEntry?.branch ?? variantName;
3291
3545
  if (existsSync6(variantPath)) {
@@ -3312,7 +3566,7 @@ async function cleanupVariant(variantName, projectRoot, variantPath, opts) {
3312
3566
  logger_default.warn(`failed to remove variant ${variantName} from DB`, logger_default.errorData(err));
3313
3567
  }
3314
3568
  try {
3315
- chownMindDir(projectRoot, baseName);
3569
+ await chownMindDir(projectRoot, baseName);
3316
3570
  } catch (err) {
3317
3571
  logger_default.error(
3318
3572
  `failed to fix ownership during variant cleanup for ${variantName}`,
@@ -3507,42 +3761,14 @@ function convertAssistantContent(content) {
3507
3761
  return result;
3508
3762
  }
3509
3763
 
3510
- // packages/daemon/src/lib/util/health.ts
3511
- async function checkHealth(port) {
3512
- try {
3513
- const res = await fetch(`http://127.0.0.1:${port}/health`, {
3514
- signal: AbortSignal.timeout(2e3)
3515
- });
3516
- if (!res.ok) return { ok: false };
3517
- const data = await res.json();
3518
- return { ok: true, name: data.name };
3519
- } catch {
3520
- return { ok: false };
3521
- }
3522
- }
3523
-
3524
3764
  // packages/daemon/src/web/api/minds.ts
3525
- var SUBSTANTIVE_TYPES = /* @__PURE__ */ new Set(["thinking", "text", "tool_use", "tool_result", "outbound"]);
3526
- var noticeDrainWatermarks = /* @__PURE__ */ new Map();
3527
- function markDeliveredOnCleanTurn(mind, session) {
3528
- if (!session) return;
3529
- const wmKey = `${mind}:${session}`;
3530
- const watermark = noticeDrainWatermarks.get(wmKey);
3531
- noticeDrainWatermarks.delete(wmKey);
3532
- const errored = takeErrored(mind, session);
3533
- if (!errored && watermark != null) {
3534
- clearDeliveredNotices(mind, session, watermark).catch(
3535
- (err) => logger_default.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err))
3536
- );
3537
- }
3538
- }
3539
3765
  var _lastActiveCache = { map: /* @__PURE__ */ new Map(), ts: 0 };
3540
3766
  var _LAST_ACTIVE_TTL = 6e4;
3541
3767
  async function getMindStatus(name, port, registryRunning) {
3542
3768
  const manager = getMindManager();
3543
3769
  let status = "stopped";
3544
3770
  try {
3545
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
3771
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
3546
3772
  if (getSleepManagerIfReady2()?.isSleeping(name)) {
3547
3773
  status = "sleeping";
3548
3774
  }
@@ -3703,7 +3929,7 @@ async function mergeUpgradeAndRestart(mindName, dir, worktreeDir, upgradeVariant
3703
3929
  cwd: dir
3704
3930
  });
3705
3931
  }
3706
- chownMindDir(dir, mindName);
3932
+ await chownMindDir(dir, mindName);
3707
3933
  switchWarning = `Switched ${oldTemplate}\u2192${template}: config reset to ${template} defaults, mechanics doc replaced, conversation starts fresh (sessions aren't portable across runtimes).`;
3708
3934
  } catch (err) {
3709
3935
  logger_default.warn(`failed to swap template home files for ${mindName}`, logger_default.errorData(err));
@@ -3785,7 +4011,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3785
4011
  const homeDir = resolve11(dest, "home");
3786
4012
  ensureVoluteGroup();
3787
4013
  createMindUser(name, homeDir);
3788
- chownMindDir(dest, name);
4014
+ await chownMindDir(dest, name);
3789
4015
  await npmInstallAsMind(dest, name);
3790
4016
  await importHistoryFromArchive(name, tempDir);
3791
4017
  importSessionsFromArchive(dest, tempDir);
@@ -3801,7 +4027,7 @@ async function importFromFullArchive(c, tempDir, extractedMindDir, nameOverride,
3801
4027
  rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
3802
4028
  }
3803
4029
  }
3804
- chownMindDir(dest, name);
4030
+ await chownMindDir(dest, name);
3805
4031
  rmSync4(tempDir, { recursive: true, force: true });
3806
4032
  return c.json({ ok: true, name, port, message: `Imported mind: ${name} (port ${port})` });
3807
4033
  } catch (err) {
@@ -3863,7 +4089,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3863
4089
  const homeDir = resolve11(dest, "home");
3864
4090
  ensureVoluteGroup();
3865
4091
  createMindUser(name, homeDir);
3866
- chownMindDir(dest, name);
4092
+ await chownMindDir(dest, name);
3867
4093
  await npmInstallAsMind(dest, name);
3868
4094
  let gitWarning;
3869
4095
  try {
@@ -3888,7 +4114,7 @@ async function importFromHomeOnlyArchive(c, tempDir, extractedMindDir, nameOverr
3888
4114
  }
3889
4115
  await importHistoryFromArchive(name, tempDir);
3890
4116
  importSessionsFromArchive(dest, tempDir);
3891
- chownMindDir(dest, name);
4117
+ await chownMindDir(dest, name);
3892
4118
  publishPublicKey(name, publicKeyPem).catch(
3893
4119
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
3894
4120
  );
@@ -4037,7 +4263,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4037
4263
  applyInitFiles(dest);
4038
4264
  const { publicKeyPem } = generateIdentity(dest);
4039
4265
  {
4040
- const { readGlobalConfig: readGlobal } = await import("./setup-IYOUPCIK.js");
4266
+ const { readGlobalConfig: readGlobal } = await import("./setup-25MMJ3J4.js");
4041
4267
  const mindDefaults = readGlobal().mindDefaults;
4042
4268
  const config2 = readVoluteConfig(dest);
4043
4269
  if (!config2) throw new Error("Failed to read volute.json after identity generation");
@@ -4104,7 +4330,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4104
4330
  const homeDir = resolve11(dest, "home");
4105
4331
  ensureVoluteGroup();
4106
4332
  createMindUser(name, homeDir);
4107
- chownMindDir(dest, name);
4333
+ await chownMindDir(dest, name);
4108
4334
  await npmInstallAsMind(dest, name);
4109
4335
  let gitWarning;
4110
4336
  try {
@@ -4117,7 +4343,7 @@ var app13 = new Hono15().post("/", requireAdminOrSystem, zValidator5("json", cre
4117
4343
  rmSync4(resolve11(dest, ".git"), { recursive: true, force: true });
4118
4344
  gitWarning = "Git setup failed \u2014 variants and upgrades won't be available until git is initialized.";
4119
4345
  }
4120
- chownMindDir(dest, name);
4346
+ await chownMindDir(dest, name);
4121
4347
  if (body.stage === "seed") {
4122
4348
  const descLine = body.description ? `
4123
4349
  The human who planted you described you as: "${body.description}"
@@ -4128,7 +4354,7 @@ The human who planted you described you as: "${body.description}"
4128
4354
  }
4129
4355
  let skillSet = body.skills ?? (body.stage === "seed" ? SEED_SKILLS : getStandardSkillsWithExtensions());
4130
4356
  if (body.stage === "seed" && !body.skills) {
4131
- const { isImagegenEnabled } = await import("./setup-IYOUPCIK.js");
4357
+ const { isImagegenEnabled } = await import("./setup-25MMJ3J4.js");
4132
4358
  if (isImagegenEnabled()) {
4133
4359
  skillSet = [...skillSet, "imagegen"];
4134
4360
  }
@@ -4146,7 +4372,7 @@ The human who planted you described you as: "${body.description}"
4146
4372
  try {
4147
4373
  const spiritEntry = await findMind("volute");
4148
4374
  if (spiritEntry) {
4149
- const { spiritDir } = await import("./spirit-NJFAY5CZ.js");
4375
+ const { spiritDir } = await import("./spirit-JQQHUVWR.js");
4150
4376
  const sDir = spiritEntry.dir ?? spiritDir();
4151
4377
  const spiritConfig = readVoluteConfig(sDir) ?? {};
4152
4378
  const schedules = spiritConfig.schedules ?? [];
@@ -4161,7 +4387,7 @@ The human who planted you described you as: "${body.description}"
4161
4387
  });
4162
4388
  spiritConfig.schedules = schedules;
4163
4389
  writeVoluteConfig(sDir, spiritConfig);
4164
- const { getScheduler: getScheduler2 } = await import("./scheduler-KXUY2I7W.js");
4390
+ const { getScheduler: getScheduler2 } = await import("./scheduler-BR6C6B3E.js");
4165
4391
  getScheduler2().loadSchedules("volute", sDir);
4166
4392
  }
4167
4393
  }
@@ -4291,7 +4517,7 @@ ${user.trimEnd()}
4291
4517
  const homeDir = resolve11(dest, "home");
4292
4518
  ensureVoluteGroup();
4293
4519
  createMindUser(name, homeDir);
4294
- chownMindDir(dest, name);
4520
+ await chownMindDir(dest, name);
4295
4521
  await npmInstallAsMind(dest, name);
4296
4522
  if (!hasMemory && dailyLogCount > 0) {
4297
4523
  await consolidateMemory(dest);
@@ -4313,7 +4539,7 @@ ${user.trimEnd()}
4313
4539
  }
4314
4540
  }
4315
4541
  importOpenClawConnectors(name, dest);
4316
- chownMindDir(dest, name);
4542
+ await chownMindDir(dest, name);
4317
4543
  publishPublicKey(name, importPublicKey).catch(
4318
4544
  (err) => logger_default.warn(`failed to publish key for ${name}`, { error: err.message })
4319
4545
  );
@@ -4339,7 +4565,7 @@ ${user.trimEnd()}
4339
4565
  const db = await getDb();
4340
4566
  const lastActiveRows = await db.select({
4341
4567
  mind: mindHistory.mind,
4342
- lastActiveAt: sql2`MAX(${mindHistory.created_at})`
4568
+ lastActiveAt: sql3`MAX(${mindHistory.created_at})`
4343
4569
  }).from(mindHistory).groupBy(mindHistory.mind);
4344
4570
  lastActiveMap = new Map(lastActiveRows.map((r) => [r.mind, r.lastActiveAt]));
4345
4571
  _lastActiveCache.map = lastActiveMap;
@@ -4426,7 +4652,7 @@ ${user.trimEnd()}
4426
4652
  const manager = getMindManager();
4427
4653
  try {
4428
4654
  if (context?.type === "reload") {
4429
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
4655
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4430
4656
  const sleepState = getSleepManagerIfReady2()?.getState(name);
4431
4657
  if (sleepState?.sleeping) {
4432
4658
  logger_default.info(`skipping reload for ${name} during sleep \u2014 will apply on next wake`);
@@ -4498,7 +4724,39 @@ ${user.trimEnd()}
4498
4724
  logger_default.error(`failed to inject sprouted message for ${baseName}`, logger_default.errorData(err));
4499
4725
  }
4500
4726
  }
4501
- await startMindFull(name);
4727
+ const repoDir = entry.parent ? entry.dir : mindDir(name);
4728
+ try {
4729
+ await startMindFull(name);
4730
+ } catch (startErr) {
4731
+ const stderr = startErr instanceof MindStartupError ? startErr.stderr : void 0;
4732
+ let rollback = { parked: false };
4733
+ try {
4734
+ rollback = await rollbackSrcChanges(repoDir, name);
4735
+ } catch (rbErr) {
4736
+ logger_default.error(`failed to roll back src changes for ${name}`, logger_default.errorData(rbErr));
4737
+ }
4738
+ if (!rollback.parked) throw startErr;
4739
+ await startMindFull(name);
4740
+ const startMsg = startErr instanceof Error ? startErr.message : String(startErr);
4741
+ const errLine = (stderr ?? startMsg).trim().split("\n").filter(Boolean).pop() ?? "";
4742
+ await recordNotice({
4743
+ mind: name,
4744
+ session: "main",
4745
+ kind: "startup",
4746
+ reason: "startup_failed",
4747
+ detail: `Your last change to src/ broke startup, so it was rolled back and your previous working code was restored. The broken change is preserved on branch \`${rollback.branch}\` \u2014 check it out to inspect and fix it. Error: ${errLine}`,
4748
+ raw: stderr ?? null
4749
+ });
4750
+ return c.json({
4751
+ ok: true,
4752
+ recovered: true,
4753
+ brokenBranch: rollback.branch,
4754
+ port: targetPort
4755
+ });
4756
+ }
4757
+ await commitSrcChanges(repoDir, name).catch(
4758
+ (e) => logger_default.error(`failed to commit known-good src for ${name}`, logger_default.errorData(e))
4759
+ );
4502
4760
  return c.json({ ok: true, port: targetPort });
4503
4761
  } catch (err) {
4504
4762
  return c.json({ error: err instanceof Error ? err.message : "Failed to restart mind" }, 500);
@@ -4521,7 +4779,7 @@ ${user.trimEnd()}
4521
4779
  const name = c.req.param("name");
4522
4780
  const entry = await findMind(name);
4523
4781
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4524
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
4782
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4525
4783
  const sm = getSleepManagerIfReady2();
4526
4784
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4527
4785
  return c.json(sm.getState(name));
@@ -4529,7 +4787,7 @@ ${user.trimEnd()}
4529
4787
  const name = c.req.param("name");
4530
4788
  const entry = await findMind(name);
4531
4789
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4532
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
4790
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4533
4791
  const sm = getSleepManagerIfReady2();
4534
4792
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4535
4793
  if (sm.isSleeping(name)) return c.json({ error: "Mind is already sleeping" }, 409);
@@ -4549,7 +4807,7 @@ ${user.trimEnd()}
4549
4807
  const name = c.req.param("name");
4550
4808
  const entry = await findMind(name);
4551
4809
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4552
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
4810
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4553
4811
  const sm = getSleepManagerIfReady2();
4554
4812
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4555
4813
  const sleepState = sm.getState(name);
@@ -4564,7 +4822,7 @@ ${user.trimEnd()}
4564
4822
  const name = c.req.param("name");
4565
4823
  const entry = await findMind(name);
4566
4824
  if (!entry) return c.json({ error: "Mind not found" }, 404);
4567
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-FJLASHHW.js");
4825
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4568
4826
  const sm = getSleepManagerIfReady2();
4569
4827
  if (!sm) return c.json({ error: "Sleep manager not initialized" }, 503);
4570
4828
  const flushed = await sm.flushQueuedMessages(name);
@@ -4582,7 +4840,7 @@ ${user.trimEnd()}
4582
4840
  if (body.avatar !== void 0) profile.avatar = basename(body.avatar);
4583
4841
  config2.profile = profile;
4584
4842
  writeVoluteConfig(dir, config2);
4585
- const { syncMindProfile: syncMindProfile2 } = await import("./auth-EAASQPTN.js");
4843
+ const { syncMindProfile: syncMindProfile2 } = await import("./auth-3RIG6GKN.js");
4586
4844
  await syncMindProfile2(name, profile);
4587
4845
  broadcast({ type: "profile_updated", mind: name, summary: `${name} profile updated` });
4588
4846
  return c.json({ ok: true });
@@ -4598,21 +4856,21 @@ ${user.trimEnd()}
4598
4856
  const rawSpirit = Number(process.env.VOLUTE_NURTURE_SPIRIT_MINUTES);
4599
4857
  const spiritThreshold = Number.isNaN(rawSpirit) ? 15 : rawSpirit;
4600
4858
  const lastCreatorMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
4601
- and4(
4602
- eq4(mindHistory.mind, name),
4603
- eq4(mindHistory.type, "inbound"),
4604
- sql2`${mindHistory.sender} != 'volute'`,
4605
- sql2`${mindHistory.sender} != ${name}`,
4606
- sql2`${mindHistory.sender} IS NOT NULL`
4859
+ and5(
4860
+ eq5(mindHistory.mind, name),
4861
+ eq5(mindHistory.type, "inbound"),
4862
+ sql3`${mindHistory.sender} != 'volute'`,
4863
+ sql3`${mindHistory.sender} != ${name}`,
4864
+ sql3`${mindHistory.sender} IS NOT NULL`
4607
4865
  )
4608
- ).orderBy(desc3(mindHistory.created_at)).limit(1);
4866
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
4609
4867
  const lastSpiritMsg = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(
4610
- and4(
4611
- eq4(mindHistory.mind, name),
4612
- eq4(mindHistory.type, "inbound"),
4613
- eq4(mindHistory.sender, "volute")
4868
+ and5(
4869
+ eq5(mindHistory.mind, name),
4870
+ eq5(mindHistory.type, "inbound"),
4871
+ eq5(mindHistory.sender, "volute")
4614
4872
  )
4615
- ).orderBy(desc3(mindHistory.created_at)).limit(1);
4873
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
4616
4874
  const now = Date.now();
4617
4875
  const creatorTime = lastCreatorMsg[0] ? new Date(lastCreatorMsg[0].created_at).getTime() : 0;
4618
4876
  const spiritTime = lastSpiritMsg[0] ? new Date(lastSpiritMsg[0].created_at).getTime() : 0;
@@ -4629,7 +4887,7 @@ ${user.trimEnd()}
4629
4887
  const config2 = readVoluteConfig(dir);
4630
4888
  const displayNameSet = !!config2?.profile?.displayName;
4631
4889
  const avatarSet = !!config2?.profile?.avatar;
4632
- const { isImagegenEnabled } = await import("./setup-IYOUPCIK.js");
4890
+ const { isImagegenEnabled } = await import("./setup-25MMJ3J4.js");
4633
4891
  const imagegenEnabled = isImagegenEnabled();
4634
4892
  const done = [];
4635
4893
  const remaining = [];
@@ -4667,7 +4925,7 @@ ${user.trimEnd()}
4667
4925
  try {
4668
4926
  const spiritEntry = await findMind("volute");
4669
4927
  if (spiritEntry) {
4670
- const { spiritDir } = await import("./spirit-NJFAY5CZ.js");
4928
+ const { spiritDir } = await import("./spirit-JQQHUVWR.js");
4671
4929
  const sDir = spiritEntry.dir ?? spiritDir();
4672
4930
  const spiritConfig = readVoluteConfig(sDir);
4673
4931
  if (spiritConfig?.schedules) {
@@ -4675,7 +4933,7 @@ ${user.trimEnd()}
4675
4933
  spiritConfig.schedules = spiritConfig.schedules.filter((s) => s.id !== nurtureId);
4676
4934
  if (spiritConfig.schedules.length === 0) spiritConfig.schedules = void 0;
4677
4935
  writeVoluteConfig(sDir, spiritConfig);
4678
- const { getScheduler: getScheduler2 } = await import("./scheduler-KXUY2I7W.js");
4936
+ const { getScheduler: getScheduler2 } = await import("./scheduler-BR6C6B3E.js");
4679
4937
  getScheduler2().loadSchedules("volute", sDir);
4680
4938
  }
4681
4939
  }
@@ -4792,7 +5050,7 @@ ${user.trimEnd()}
4792
5050
  cwd: worktreeDir
4793
5051
  });
4794
5052
  }
4795
- chownMindDir(dir, mindName);
5053
+ await chownMindDir(dir, mindName);
4796
5054
  try {
4797
5055
  const result = await mergeUpgradeAndRestart(
4798
5056
  mindName,
@@ -4858,7 +5116,7 @@ ${user.trimEnd()}
4858
5116
  await configureGitIdentity(mindName, { cwd: dir, mindName, env });
4859
5117
  await gitExec(["add", "-A"], { cwd: dir, mindName, env });
4860
5118
  await gitExec(["commit", "-m", "initial commit"], { cwd: dir, mindName, env });
4861
- chownMindDir(dir, mindName);
5119
+ await chownMindDir(dir, mindName);
4862
5120
  } catch (err) {
4863
5121
  rmSync4(resolve11(dir, ".git"), { recursive: true, force: true });
4864
5122
  return c.json(
@@ -4917,7 +5175,7 @@ ${user.trimEnd()}
4917
5175
  });
4918
5176
  }
4919
5177
  }
4920
- chownMindDir(dir, mindName);
5178
+ await chownMindDir(dir, mindName);
4921
5179
  if (hasConflicts) {
4922
5180
  return c.json({
4923
5181
  ok: false,
@@ -5110,7 +5368,7 @@ ${user.trimEnd()}
5110
5368
  }
5111
5369
  writeVoluteConfig(dir, existing);
5112
5370
  if (body.unescapeNewlines !== void 0) {
5113
- const { clearEchoTextCache } = await import("./echo-text-YP3JVLNA.js");
5371
+ const { clearEchoTextCache } = await import("./echo-text-XKBJM6RV.js");
5114
5372
  clearEchoTextCache(name);
5115
5373
  }
5116
5374
  const needsConfigJson = body.model !== void 0 || body.thinkingLevel !== void 0 || body.maxThinkingTokens !== void 0 || body.compaction !== void 0;
@@ -5187,7 +5445,7 @@ ${user.trimEnd()}
5187
5445
  }
5188
5446
  if (entry.mindType === "spirit" && body.model !== void 0) {
5189
5447
  try {
5190
- const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-IYOUPCIK.js");
5448
+ const { readGlobalConfig: readGlobalConfig2, writeGlobalConfig: writeGlobalConfig2 } = await import("./setup-25MMJ3J4.js");
5191
5449
  const globalConfig = readGlobalConfig2();
5192
5450
  globalConfig.spiritModel = body.model;
5193
5451
  writeGlobalConfig2(globalConfig);
@@ -5215,7 +5473,7 @@ ${user.trimEnd()}
5215
5473
  if (!body.systemPrompt || !body.message) {
5216
5474
  return c.json({ error: "systemPrompt and message required" }, 400);
5217
5475
  }
5218
- const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-3GBOJGUA.js");
5476
+ const { aiComplete: aiCompleteFn, isAiConfigured } = await import("./ai-service-FBCCEYF5.js");
5219
5477
  if (!isAiConfigured()) {
5220
5478
  return c.json({ error: "AI service not configured" }, 503);
5221
5479
  }
@@ -5236,166 +5494,7 @@ ${user.trimEnd()}
5236
5494
  if (!body.type) {
5237
5495
  return c.json({ error: "type required" }, 400);
5238
5496
  }
5239
- if (body.type === "session_start" && body.session) {
5240
- const activeTurnId = getActiveTurnId(baseName);
5241
- if (activeTurnId) {
5242
- await assignSession(baseName, activeTurnId, body.session);
5243
- }
5244
- }
5245
- let turnId = getActiveTurnId(baseName, body.session);
5246
- if (!turnId && SUBSTANTIVE_TYPES.has(body.type)) {
5247
- turnId = await createTurn(baseName);
5248
- if (!turnId) {
5249
- logger_default.warn(`skipping turn tracking for ${baseName}: createTurn failed`);
5250
- } else {
5251
- publish2(baseName, { mind: baseName, type: "turn_created", turnId });
5252
- if (body.session) {
5253
- await assignSession(baseName, turnId, body.session);
5254
- }
5255
- try {
5256
- await tagUntaggedInbound(baseName, turnId, {
5257
- setTrigger: true,
5258
- channel: body.channel
5259
- });
5260
- } catch (err) {
5261
- logger_default.warn(
5262
- `failed to link trigger/tag inbounds for turn ${turnId} (mind: ${baseName})`,
5263
- logger_default.errorData(err)
5264
- );
5265
- }
5266
- }
5267
- }
5268
- const MARKER_RE = /\[volute:(?:outbound|activity):\d+\]/g;
5269
- const cleanContent = body.type === "tool_result" && body.content ? body.content.replace(MARKER_RE, "").trimEnd() : body.content;
5270
- const db = await getDb();
5271
- let insertedId;
5272
- try {
5273
- const result = await db.insert(mindHistory).values({
5274
- mind: baseName,
5275
- type: body.type,
5276
- session: body.session ?? null,
5277
- channel: body.channel ?? null,
5278
- message_id: body.messageId ?? null,
5279
- content: cleanContent ?? null,
5280
- metadata: body.metadata ? JSON.stringify(body.metadata) : null,
5281
- turn_id: turnId ?? null
5282
- }).returning({ id: mindHistory.id });
5283
- insertedId = result[0]?.id;
5284
- } catch (err) {
5285
- logger_default.error(`failed to persist event for ${baseName}`, logger_default.errorData(err));
5286
- }
5287
- if (body.type === "tool_use" && insertedId != null) {
5288
- trackToolUse(baseName, body.session, insertedId);
5289
- }
5290
- if (body.type === "tool_result" && turnId && body.content) {
5291
- const toolUseEventId = getLastToolUseEventId(baseName, body.session);
5292
- try {
5293
- await linkToolResultToTurn(baseName, turnId, body.content, toolUseEventId);
5294
- } catch (err) {
5295
- logger_default.error("failed to link outbound to turn", logger_default.errorData(err));
5296
- }
5297
- }
5298
- publish2(baseName, {
5299
- mind: baseName,
5300
- type: body.type,
5301
- session: body.session,
5302
- channel: body.channel,
5303
- messageId: body.messageId,
5304
- content: cleanContent,
5305
- metadata: body.metadata,
5306
- turnId: turnId ?? void 0
5307
- });
5308
- if (body.type === "text" && body.channel && cleanContent) {
5309
- echoTextToChannel(
5310
- baseName,
5311
- body.channel,
5312
- cleanContent,
5313
- turnId ?? void 0,
5314
- insertedId
5315
- ).catch(
5316
- (err) => logger_default.error(`echo-text failed for ${baseName} on ${body.channel}`, logger_default.errorData(err))
5317
- );
5318
- }
5319
- onMindEvent(baseName, body.type, body.channel);
5320
- if ((body.type === "text" || body.type === "outbound") && body.channel) {
5321
- const map = getTypingMap();
5322
- const affected = map.deleteSender(baseName);
5323
- publishTypingForChannels(affected, map);
5324
- }
5325
- if (body.type === "error" && body.session) {
5326
- markErrored(baseName, body.session);
5327
- const { reason, detail } = classify(body.content ?? "");
5328
- await recordNotice({
5329
- mind: baseName,
5330
- session: body.session,
5331
- kind: "turn_error",
5332
- reason,
5333
- detail,
5334
- raw: body.content ?? null
5335
- });
5336
- }
5337
- if (body.type === "done") {
5338
- const map = getTypingMap();
5339
- const affected = map.deleteSender(baseName);
5340
- publishTypingForChannels(affected, map);
5341
- broadcast({ type: "mind_done", mind: baseName, summary: "Finished processing" });
5342
- try {
5343
- getDeliveryManager().sessionDone(baseName, body.session);
5344
- } catch (err) {
5345
- if (!(err instanceof Error && err.message.includes("not initialized"))) {
5346
- logger_default.error(`delivery manager sessionDone failed for ${baseName}`, logger_default.errorData(err));
5347
- }
5348
- }
5349
- try {
5350
- const dm = getDeliveryManager();
5351
- const busy = body.session ? dm.isSessionBusy(baseName, body.session) : false;
5352
- if (!busy) {
5353
- const completedTurnId = await completeTurn(baseName, body.session);
5354
- if (completedTurnId) {
5355
- tagUntaggedOutbound(baseName, completedTurnId).catch(
5356
- (err) => logger_default.warn("failed to tag orphaned outbound records", logger_default.errorData(err))
5357
- );
5358
- }
5359
- markDeliveredOnCleanTurn(baseName, body.session);
5360
- if (insertedId != null) {
5361
- summarizeTurn(baseName, body.session, body.channel, insertedId, completedTurnId).catch(
5362
- (err) => logger_default.error("turn summarization failed", logger_default.errorData(err))
5363
- );
5364
- }
5365
- }
5366
- } catch (err) {
5367
- if (!(err instanceof Error && err.message.includes("not initialized"))) {
5368
- logger_default.error("turn completion check failed", logger_default.errorData(err));
5369
- }
5370
- const completedTurnId = await completeTurn(baseName, body.session);
5371
- if (completedTurnId) {
5372
- tagUntaggedOutbound(baseName, completedTurnId).catch(
5373
- (err2) => logger_default.warn("failed to tag orphaned outbound records", logger_default.errorData(err2))
5374
- );
5375
- }
5376
- markDeliveredOnCleanTurn(baseName, body.session);
5377
- if (insertedId != null) {
5378
- summarizeTurn(baseName, body.session, body.channel, insertedId, completedTurnId).catch(
5379
- (err2) => logger_default.error("turn summarization failed", logger_default.errorData(err2))
5380
- );
5381
- }
5382
- }
5383
- }
5384
- if (body.type === "usage" && body.metadata) {
5385
- const inputTokens = body.metadata.input_tokens ?? 0;
5386
- const outputTokens = body.metadata.output_tokens ?? 0;
5387
- const tb = getTokenBudget();
5388
- tb.recordUsage(baseName, inputTokens, outputTokens);
5389
- if (body.session && tb.noteExceeded(baseName)) {
5390
- void recordNotice({
5391
- mind: baseName,
5392
- session: body.session,
5393
- kind: "budget",
5394
- reason: "token_budget",
5395
- detail: "You've reached your token budget for this period. Further activity may be paused until the budget resets \u2014 wrap up or prioritize accordingly."
5396
- });
5397
- }
5398
- }
5497
+ await handleMindEvent(baseName, body);
5399
5498
  return c.json({ ok: true });
5400
5499
  }).get("/:name/events", requireSelf(), async (c) => {
5401
5500
  const name = c.req.param("name");
@@ -5484,32 +5583,32 @@ ${user.trimEnd()}
5484
5583
  const db = await getDb();
5485
5584
  const rows = await db.select({
5486
5585
  session: mindHistory.session,
5487
- started_at: sql2`MIN(${mindHistory.created_at})`,
5488
- event_count: sql2`COUNT(*)`,
5489
- message_count: sql2`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
5490
- tool_count: sql2`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
5491
- }).from(mindHistory).where(and4(eq4(mindHistory.mind, name), sql2`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql2`MIN(${mindHistory.created_at}) DESC`);
5586
+ started_at: sql3`MIN(${mindHistory.created_at})`,
5587
+ event_count: sql3`COUNT(*)`,
5588
+ message_count: sql3`SUM(CASE WHEN ${mindHistory.type} IN ('inbound','outbound') THEN 1 ELSE 0 END)`,
5589
+ tool_count: sql3`SUM(CASE WHEN ${mindHistory.type}='tool_use' THEN 1 ELSE 0 END)`
5590
+ }).from(mindHistory).where(and5(eq5(mindHistory.mind, name), sql3`${mindHistory.session} IS NOT NULL`)).groupBy(mindHistory.session).orderBy(sql3`MIN(${mindHistory.created_at}) DESC`);
5492
5591
  return c.json(rows);
5493
5592
  }).get("/:name/history/channels", requireSelf(), async (c) => {
5494
5593
  const name = c.req.param("name");
5495
5594
  const db = await getDb();
5496
- const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq4(mindHistory.mind, name));
5595
+ const rows = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq5(mindHistory.mind, name));
5497
5596
  return c.json(rows.map((r) => r.channel));
5498
5597
  }).get("/:name/history/export", requireSelf(), async (c) => {
5499
5598
  const name = c.req.param("name");
5500
5599
  if (!await findMind(name)) return c.json({ error: "Mind not found" }, 404);
5501
5600
  const db = await getDb();
5502
- const rows = await db.select().from(mindHistory).where(eq4(mindHistory.mind, name));
5601
+ const rows = await db.select().from(mindHistory).where(eq5(mindHistory.mind, name));
5503
5602
  return c.json(rows);
5504
5603
  }).get("/:name/history/turn", requireSelf(), async (c) => {
5505
5604
  const name = c.req.param("name");
5506
5605
  const turnId = c.req.query("turn_id");
5507
5606
  const detail = c.req.query("detail") === "1";
5508
5607
  const db = await getDb();
5509
- const typeFilter = detail ? void 0 : sql2`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking','activity')`;
5608
+ const typeFilter = detail ? void 0 : sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking','activity')`;
5510
5609
  let rows;
5511
5610
  if (turnId) {
5512
- rows = await db.select().from(mindHistory).where(and4(eq4(mindHistory.mind, name), eq4(mindHistory.turn_id, turnId), typeFilter)).orderBy(mindHistory.id);
5611
+ rows = await db.select().from(mindHistory).where(and5(eq5(mindHistory.mind, name), eq5(mindHistory.turn_id, turnId), typeFilter)).orderBy(mindHistory.id);
5513
5612
  } else {
5514
5613
  const session = c.req.query("session");
5515
5614
  const fromId = parseInt(c.req.query("from_id") ?? "", 10);
@@ -5518,11 +5617,11 @@ ${user.trimEnd()}
5518
5617
  return c.json({ error: "turn_id, or session with from_id and to_id, required" }, 400);
5519
5618
  }
5520
5619
  rows = await db.select().from(mindHistory).where(
5521
- and4(
5522
- eq4(mindHistory.mind, name),
5523
- eq4(mindHistory.session, session),
5524
- sql2`${mindHistory.id} >= ${fromId}`,
5525
- sql2`${mindHistory.id} <= ${toId}`,
5620
+ and5(
5621
+ eq5(mindHistory.mind, name),
5622
+ eq5(mindHistory.session, session),
5623
+ sql3`${mindHistory.id} >= ${fromId}`,
5624
+ sql3`${mindHistory.id} <= ${toId}`,
5526
5625
  typeFilter
5527
5626
  )
5528
5627
  ).orderBy(mindHistory.id);
@@ -5535,14 +5634,14 @@ ${user.trimEnd()}
5535
5634
  let sinceTimestamp = null;
5536
5635
  if (currentSession) {
5537
5636
  const lastTurn = await db.select({ turn_id: mindHistory.turn_id }).from(mindHistory).where(
5538
- and4(
5539
- eq4(mindHistory.mind, name),
5540
- eq4(mindHistory.session, currentSession),
5541
- sql2`${mindHistory.turn_id} IS NOT NULL`
5637
+ and5(
5638
+ eq5(mindHistory.mind, name),
5639
+ eq5(mindHistory.session, currentSession),
5640
+ sql3`${mindHistory.turn_id} IS NOT NULL`
5542
5641
  )
5543
- ).orderBy(desc3(mindHistory.created_at)).limit(1);
5642
+ ).orderBy(desc4(mindHistory.created_at)).limit(1);
5544
5643
  if (lastTurn.length > 0 && lastTurn[0].turn_id) {
5545
- const firstEvent = await db.select({ created_at: mindHistory.created_at }).from(mindHistory).where(eq4(mindHistory.turn_id, lastTurn[0].turn_id)).orderBy(mindHistory.created_at).limit(1);
5644
+ 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);
5546
5645
  if (firstEvent.length > 0) {
5547
5646
  sinceTimestamp = firstEvent[0].created_at;
5548
5647
  }
@@ -5552,18 +5651,18 @@ ${user.trimEnd()}
5552
5651
  sinceTimestamp = new Date(Date.now() - 36e5).toISOString().replace("T", " ").slice(0, 19);
5553
5652
  }
5554
5653
  const conditions = [
5555
- eq4(summaries.mind, name),
5556
- eq4(summaries.period, "turn"),
5557
- sql2`${summaries.created_at} > ${sinceTimestamp}`
5654
+ eq5(summaries.mind, name),
5655
+ eq5(summaries.period, "turn"),
5656
+ sql3`${summaries.created_at} > ${sinceTimestamp}`
5558
5657
  ];
5559
5658
  if (currentSession) {
5560
- conditions.push(sql2`${turns.session} != ${currentSession}`);
5659
+ conditions.push(sql3`${turns.session} != ${currentSession}`);
5561
5660
  }
5562
5661
  const rows = await db.select({
5563
5662
  session: turns.session,
5564
5663
  content: summaries.content,
5565
5664
  created_at: summaries.created_at
5566
- }).from(summaries).innerJoin(turns, eq4(turns.id, summaries.period_key)).where(and4(...conditions)).orderBy(desc3(summaries.created_at)).limit(50);
5665
+ }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...conditions)).orderBy(desc4(summaries.created_at)).limit(50);
5567
5666
  if (rows.length === 0) {
5568
5667
  return c.json({ context: null });
5569
5668
  }
@@ -5582,7 +5681,7 @@ ${lines.join("\n")}` });
5582
5681
  const notices = await drainNotices(baseName, session);
5583
5682
  if (notices.length === 0) return c.json({ context: null, notices: [] });
5584
5683
  const maxId = notices.reduce((m, n) => Math.max(m, n.id), 0);
5585
- noticeDrainWatermarks.set(`${baseName}:${session}`, maxId);
5684
+ setNoticeDrainWatermark(baseName, session, maxId);
5586
5685
  return c.json({ context: formatNotices(notices), notices });
5587
5686
  }).get("/:name/history", requireSelf(), async (c) => {
5588
5687
  const name = c.req.param("name");
@@ -5593,18 +5692,18 @@ ${lines.join("\n")}` });
5593
5692
  const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
5594
5693
  const offset = Math.max(parseInt(c.req.query("offset") ?? "0", 10) || 0, 0);
5595
5694
  const db = await getDb();
5596
- const conditions = [eq4(mindHistory.mind, name)];
5695
+ const conditions = [eq5(mindHistory.mind, name)];
5597
5696
  if (channel) {
5598
- conditions.push(eq4(mindHistory.channel, channel));
5697
+ conditions.push(eq5(mindHistory.channel, channel));
5599
5698
  }
5600
5699
  if (session) {
5601
- conditions.push(eq4(mindHistory.session, session));
5700
+ conditions.push(eq5(mindHistory.session, session));
5602
5701
  }
5603
5702
  const effectivePreset = full ? "all" : preset;
5604
5703
  if (!effectivePreset || effectivePreset === "summary") {
5605
- const sumConditions = [eq4(summaries.mind, name), eq4(summaries.period, "turn")];
5704
+ const sumConditions = [eq5(summaries.mind, name), eq5(summaries.period, "turn")];
5606
5705
  if (session) {
5607
- sumConditions.push(eq4(turns.session, session));
5706
+ sumConditions.push(eq5(turns.session, session));
5608
5707
  const sumRows2 = await db.select({
5609
5708
  id: summaries.id,
5610
5709
  mind: summaries.mind,
@@ -5614,7 +5713,7 @@ ${lines.join("\n")}` });
5614
5713
  metadata: summaries.metadata,
5615
5714
  created_at: summaries.created_at,
5616
5715
  session: turns.session
5617
- }).from(summaries).innerJoin(turns, eq4(turns.id, summaries.period_key)).where(and4(...sumConditions)).orderBy(desc3(summaries.created_at)).limit(limit).offset(offset);
5716
+ }).from(summaries).innerJoin(turns, eq5(turns.id, summaries.period_key)).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
5618
5717
  return c.json(
5619
5718
  sumRows2.map((r) => ({
5620
5719
  id: r.id,
@@ -5631,7 +5730,7 @@ ${lines.join("\n")}` });
5631
5730
  }))
5632
5731
  );
5633
5732
  }
5634
- const sumRows = await db.select().from(summaries).where(and4(...sumConditions)).orderBy(desc3(summaries.created_at)).limit(limit).offset(offset);
5733
+ const sumRows = await db.select().from(summaries).where(and5(...sumConditions)).orderBy(desc4(summaries.created_at)).limit(limit).offset(offset);
5635
5734
  return c.json(
5636
5735
  sumRows.map((r) => ({
5637
5736
  id: r.id,
@@ -5652,22 +5751,22 @@ ${lines.join("\n")}` });
5652
5751
  case "all":
5653
5752
  break;
5654
5753
  case "conversation":
5655
- conditions.push(sql2`${mindHistory.type} IN ('inbound','outbound','tool_use')`);
5754
+ conditions.push(sql3`${mindHistory.type} IN ('inbound','outbound','tool_use')`);
5656
5755
  break;
5657
5756
  case "detailed":
5658
5757
  conditions.push(
5659
- sql2`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
5758
+ sql3`${mindHistory.type} IN ('inbound','outbound','tool_use','tool_result','text','thinking')`
5660
5759
  );
5661
5760
  break;
5662
5761
  }
5663
- const rows = await db.select().from(mindHistory).where(and4(...conditions)).orderBy(desc3(mindHistory.created_at)).limit(limit).offset(offset);
5762
+ const rows = await db.select().from(mindHistory).where(and5(...conditions)).orderBy(desc4(mindHistory.created_at)).limit(limit).offset(offset);
5664
5763
  return c.json(rows);
5665
5764
  });
5666
5765
  var minds_default = app13;
5667
5766
 
5668
5767
  // packages/daemon/src/web/api/prompts.ts
5669
5768
  import { zValidator as zValidator6 } from "@hono/zod-validator";
5670
- import { eq as eq5, sql as sql3 } from "drizzle-orm";
5769
+ import { eq as eq6, sql as sql4 } from "drizzle-orm";
5671
5770
  import { Hono as Hono16 } from "hono";
5672
5771
  import { z as z6 } from "zod";
5673
5772
  var app14 = new Hono16().get("/", async (c) => {
@@ -5700,9 +5799,9 @@ var app14 = new Hono16().get("/", async (c) => {
5700
5799
  }
5701
5800
  const { content } = c.req.valid("json");
5702
5801
  const db = await getDb();
5703
- await db.insert(systemPrompts).values({ key, content, updated_at: sql3`(datetime('now'))` }).onConflictDoUpdate({
5802
+ await db.insert(systemPrompts).values({ key, content, updated_at: sql4`(datetime('now'))` }).onConflictDoUpdate({
5704
5803
  target: systemPrompts.key,
5705
- set: { content, updated_at: sql3`(datetime('now'))` }
5804
+ set: { content, updated_at: sql4`(datetime('now'))` }
5706
5805
  });
5707
5806
  return c.json({ ok: true });
5708
5807
  }).delete("/:key", requireAdmin, async (c) => {
@@ -5711,7 +5810,7 @@ var app14 = new Hono16().get("/", async (c) => {
5711
5810
  return c.json({ error: "Unknown prompt key" }, 404);
5712
5811
  }
5713
5812
  const db = await getDb();
5714
- await db.delete(systemPrompts).where(eq5(systemPrompts.key, key));
5813
+ await db.delete(systemPrompts).where(eq6(systemPrompts.key, key));
5715
5814
  return c.json({ ok: true });
5716
5815
  });
5717
5816
  var prompts_default = app14;
@@ -5959,7 +6058,7 @@ var app15 = new Hono17().get("/:name/clock/status", requireSelf(), async (c) =>
5959
6058
  const body = await c.req.text();
5960
6059
  const message = `[webhook: ${event}] ${body}`;
5961
6060
  try {
5962
- const { sendSystemMessage } = await import("./system-chat-P6KVGW4T.js");
6061
+ const { sendSystemMessage } = await import("./system-chat-KMQIP7VH.js");
5963
6062
  await sendSystemMessage(name, message);
5964
6063
  return c.json({ ok: true });
5965
6064
  } catch (err) {
@@ -6013,7 +6112,7 @@ setup.get("/status", async (c) => {
6013
6112
  let hasAccount = false;
6014
6113
  if (hasSystem) {
6015
6114
  try {
6016
- const { listUsersByType: listUsersByType2 } = await import("./auth-EAASQPTN.js");
6115
+ const { listUsersByType: listUsersByType2 } = await import("./auth-3RIG6GKN.js");
6017
6116
  const brains = await listUsersByType2("brain");
6018
6117
  hasAccount = brains.length > 0;
6019
6118
  } catch (err) {
@@ -6224,7 +6323,7 @@ setup.post("/account", async (c) => {
6224
6323
  }
6225
6324
  }
6226
6325
  try {
6227
- const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-EAASQPTN.js");
6326
+ const { createUser: createUser2, updateUserProfile: updateUserProfile2 } = await import("./auth-3RIG6GKN.js");
6228
6327
  const user = await createUser2(body.username.trim(), body.password);
6229
6328
  if (body.displayName?.trim()) {
6230
6329
  await updateUserProfile2(user.id, { display_name: body.displayName.trim() });
@@ -6270,7 +6369,7 @@ setup.post("/models", async (c) => {
6270
6369
  return c.json({ error: "Spirit model is required" }, 400);
6271
6370
  }
6272
6371
  try {
6273
- const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-3GBOJGUA.js");
6372
+ const { setEnabledModels: setEnabledModels3, setUtilityModel: setUtilityModel2 } = await import("./ai-service-FBCCEYF5.js");
6274
6373
  setEnabledModels3(body.models);
6275
6374
  const config2 = readGlobalConfig();
6276
6375
  config2.spiritModel = body.spiritModel.trim();
@@ -6288,15 +6387,15 @@ setup.post("/complete", async (c) => {
6288
6387
  return c.json({ error: "Setup already complete" }, 400);
6289
6388
  }
6290
6389
  try {
6291
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-NJFAY5CZ.js");
6292
- const { startSpiritFull } = await import("./mind-service-PR4PR4XI.js");
6390
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-JQQHUVWR.js");
6391
+ const { startSpiritFull } = await import("./mind-service-ZBVUMJ5H.js");
6293
6392
  await ensureSpiritProject();
6294
6393
  await syncSpiritTemplate();
6295
6394
  const warnings = [];
6296
6395
  const config2 = readGlobalConfig();
6297
6396
  if (config2.setup?.service && config2.setup?.type === "local") {
6298
6397
  try {
6299
- const { installUserService } = await import("./service-install-GTDUHP6R.js");
6398
+ const { installUserService } = await import("./service-install-SHRM6NHK.js");
6300
6399
  const installed = await installUserService(config2.port, config2.hostname);
6301
6400
  if (!installed) {
6302
6401
  warnings.push("Service installation is not supported on this platform.");
@@ -6324,8 +6423,8 @@ setup.post("/complete", async (c) => {
6324
6423
  }
6325
6424
  let spiritConversationId;
6326
6425
  try {
6327
- const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-EAASQPTN.js");
6328
- const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-FSZCUUYO.js");
6426
+ const { getOrCreateMindUser: getOrCreateMindUser2, listUsersByType: listUsersByType2 } = await import("./auth-3RIG6GKN.js");
6427
+ const { createConversation: createConversation2, findDMConversation: findDMConversation2 } = await import("./conversations-67WWW25C.js");
6329
6428
  const spiritUser = await getOrCreateMindUser2("volute");
6330
6429
  const brains = await listUsersByType2("brain");
6331
6430
  const admin2 = brains.find((u) => u.role === "admin");
@@ -6347,8 +6446,8 @@ setup.post("/complete", async (c) => {
6347
6446
  logger_default.info("setup complete state", { spiritConversationId, spiritStarted });
6348
6447
  if (spiritConversationId && spiritStarted) {
6349
6448
  try {
6350
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-FPIL26WV.js");
6351
- const { listUsersByType: listUsers2 } = await import("./auth-EAASQPTN.js");
6449
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-WJBZFALY.js");
6450
+ const { listUsersByType: listUsers2 } = await import("./auth-3RIG6GKN.js");
6352
6451
  const admins = await listUsers2("brain");
6353
6452
  const admin2 = admins.find((u) => u.role === "admin");
6354
6453
  const adminName = admin2?.display_name || admin2?.username || "the admin";
@@ -6667,7 +6766,7 @@ var app19 = new Hono22().use("*", authMiddleware).get("/", async (c) => {
6667
6766
  var conversations_default = app19;
6668
6767
 
6669
6768
  // packages/daemon/src/web/api/v1/events.ts
6670
- import { desc as desc4 } from "drizzle-orm";
6769
+ import { desc as desc5, eq as eq7 } from "drizzle-orm";
6671
6770
  import { Hono as Hono23 } from "hono";
6672
6771
  import { streamSSE as streamSSE4 } from "hono/streaming";
6673
6772
 
@@ -6710,16 +6809,23 @@ function bufferEvent(data) {
6710
6809
  function nextEventId() {
6711
6810
  return nextId++;
6712
6811
  }
6713
- function getEventsSince(sinceId, allowedConversationIds) {
6812
+ function getEventsSince(sinceId, allowedConversationIds, allowedActivityMind) {
6714
6813
  const now = Date.now();
6715
6814
  const startIdx = buffer.findIndex((e) => e.id > sinceId);
6716
6815
  if (startIdx === -1) return [];
6717
6816
  return buffer.slice(startIdx).filter((e) => {
6718
6817
  if (now - e.timestamp >= MAX_AGE_MS) return false;
6719
6818
  const data = e.data;
6720
- if (data.event === "snapshot") return false;
6721
- if (data.event === "conversation") return allowedConversationIds.has(data.conversationId);
6722
- return true;
6819
+ switch (data.event) {
6820
+ case "snapshot":
6821
+ return false;
6822
+ case "conversation":
6823
+ return allowedConversationIds.has(data.conversationId);
6824
+ case "activity":
6825
+ return allowedActivityMind === void 0 || data.mind === allowedActivityMind;
6826
+ default:
6827
+ return false;
6828
+ }
6723
6829
  });
6724
6830
  }
6725
6831
 
@@ -6728,6 +6834,8 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6728
6834
  const user = c.get("user");
6729
6835
  const since = c.req.query("since");
6730
6836
  const sinceId = since ? Number(since) : 0;
6837
+ const privileged = user.role === "admin" || user.role === "system";
6838
+ const activityMind = privileged ? void 0 : await getBaseName(user.username);
6731
6839
  return streamSSE4(c, async (stream) => {
6732
6840
  const cleanups = [];
6733
6841
  if (user.user_type === "brain") {
@@ -6750,7 +6858,7 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6750
6858
  }
6751
6859
  const allowedConversationIds = new Set(conversations2.map((c2) => c2.id));
6752
6860
  if (sinceId > 0) {
6753
- const missed = getEventsSince(sinceId, allowedConversationIds);
6861
+ const missed = getEventsSince(sinceId, allowedConversationIds, activityMind);
6754
6862
  for (const event of missed) {
6755
6863
  await stream.writeSSE({
6756
6864
  id: String(event.id),
@@ -6761,7 +6869,7 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6761
6869
  let recentActivity = [];
6762
6870
  try {
6763
6871
  const db = await getDb();
6764
- recentActivity = await db.select().from(activity).orderBy(desc4(activity.created_at)).limit(50);
6872
+ recentActivity = await db.select().from(activity).where(activityMind ? eq7(activity.mind, activityMind) : void 0).orderBy(desc5(activity.created_at)).limit(50);
6765
6873
  recentActivity = recentActivity.map((row) => ({
6766
6874
  ...row,
6767
6875
  metadata: row.metadata ? JSON.parse(row.metadata) : null
@@ -6788,6 +6896,7 @@ var app20 = new Hono23().use("*", authMiddleware).get("/", async (c) => {
6788
6896
  metadata: event.metadata ?? null
6789
6897
  };
6790
6898
  const eventId = bufferEvent(data);
6899
+ if (activityMind !== void 0 && event.mind !== activityMind) return;
6791
6900
  stream.writeSSE({
6792
6901
  id: String(eventId),
6793
6902
  data: JSON.stringify(data)
@@ -7013,7 +7122,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7013
7122
  const msg = e instanceof Error ? e.message : String(e);
7014
7123
  return c.json({ error: `Failed to create worktree: ${msg}` }, 500);
7015
7124
  }
7016
- chownMindDir(projectRoot, mindName);
7125
+ await chownMindDir(projectRoot, mindName);
7017
7126
  try {
7018
7127
  if (isIsolationEnabled()) {
7019
7128
  const [cmd, args] = await wrapForIsolation("npm", ["install"], mindName);
@@ -7129,7 +7238,7 @@ var app21 = new Hono24().get("/:name/variants", async (c) => {
7129
7238
  if (variantName.endsWith("-upgrade") || variantName === "upgrade") {
7130
7239
  try {
7131
7240
  const { computeTemplateHash: computeTemplateHash2 } = await import("./template-hash-446HHH5V.js");
7132
- const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-MDLNPTTI.js");
7241
+ const { setMindTemplateHash: setMindTemplateHash2 } = await import("./registry-7LQHTRUX.js");
7133
7242
  const tmpl = parentEntry.template ?? "claude";
7134
7243
  await setMindTemplateHash2(mindName, computeTemplateHash2(tmpl));
7135
7244
  } catch (err) {
@@ -7303,47 +7412,6 @@ import { zValidator as zValidator10 } from "@hono/zod-validator";
7303
7412
  import { Hono as Hono26 } from "hono";
7304
7413
  import { streamSSE as streamSSE5 } from "hono/streaming";
7305
7414
  import { z as z10 } from "zod";
7306
- async function fanOutToMinds(opts) {
7307
- const participants = opts.participants;
7308
- const mindParticipants = participants.filter(
7309
- (p) => p.userType === "mind" || p.userType === "system"
7310
- );
7311
- const participantNames = participants.map((p) => p.username);
7312
- const isDM = opts.isDM ?? participants.length === 2;
7313
- const manager = getMindManager();
7314
- const sm = getSleepManagerIfReady();
7315
- const targetMinds = mindParticipants.map((ap) => {
7316
- const key = opts.targetName ? opts.targetName(ap.username) : ap.username;
7317
- if (manager.isRunning(key) || sm?.isSleeping(ap.username)) return ap.username;
7318
- return null;
7319
- }).filter((n) => n !== null && n !== opts.senderName);
7320
- function slugForMind(mindUsername) {
7321
- return buildVoluteSlug({
7322
- participants,
7323
- mindUsername,
7324
- conversationId: opts.conversationId,
7325
- ...opts.slugExtra
7326
- });
7327
- }
7328
- for (const mindName of targetMinds) {
7329
- const target = opts.targetName ? opts.targetName(mindName) : mindName;
7330
- const channel = slugForMind(mindName);
7331
- const typingMap = getTypingMap();
7332
- const currentlyTyping = typingMap.get(channel).filter((name) => participantNames.includes(name));
7333
- deliverMessage(target, {
7334
- content: opts.contentBlocks,
7335
- channel,
7336
- conversationId: opts.conversationId,
7337
- sender: opts.senderName,
7338
- participants: participantNames,
7339
- participantCount: participants.length,
7340
- isDM,
7341
- ...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
7342
- }).catch((err) => {
7343
- logger_default.warn(`fan-out delivery failed for ${target}`, logger_default.errorData(err));
7344
- });
7345
- }
7346
- }
7347
7415
  var fileSchema = z10.object({
7348
7416
  filename: z10.string(),
7349
7417
  data: z10.string()
@@ -7505,7 +7573,15 @@ var unifiedChatApp = new Hono26().post(
7505
7573
  logger_default.warn("failed to look up channel char_limit, skipping enforcement", logger_default.errorData(err));
7506
7574
  }
7507
7575
  }
7508
- const message = await addMessage(conversationId, "user", senderName, contentBlocks);
7576
+ let outboundTurnId;
7577
+ let senderBase;
7578
+ if (senderIsMind) {
7579
+ senderBase = await getBaseName(senderName);
7580
+ outboundTurnId = getActiveTurnId(senderBase, c.get("mindSession"));
7581
+ }
7582
+ const message = await addMessage(conversationId, "user", senderName, contentBlocks, {
7583
+ turnId: outboundTurnId
7584
+ });
7509
7585
  let outboundId;
7510
7586
  if (senderIsMind) {
7511
7587
  routeOutboundBridge(conversationId, senderName, contentBlocks).catch((err) => {
@@ -7518,10 +7594,22 @@ var unifiedChatApp = new Hono26().post(
7518
7594
  convType: conv.type,
7519
7595
  convName
7520
7596
  });
7597
+ const text = extractTextContent(contentBlocks);
7521
7598
  try {
7522
- outboundId = await recordOutbound(senderName, channel, extractTextContent(contentBlocks), {
7523
- messageId: message?.id != null ? String(message.id) : void 0
7599
+ outboundId = await recordOutbound(senderName, channel, text, {
7600
+ messageId: message?.id != null ? String(message.id) : void 0,
7601
+ turnId: outboundTurnId
7524
7602
  });
7603
+ if (outboundTurnId) {
7604
+ const mindKey = senderBase ?? senderName;
7605
+ publish2(mindKey, {
7606
+ mind: mindKey,
7607
+ type: "outbound",
7608
+ channel,
7609
+ content: text,
7610
+ turnId: outboundTurnId
7611
+ });
7612
+ }
7525
7613
  } catch (err) {
7526
7614
  logger_default.warn(`recordOutbound failed for ${senderName}`, logger_default.errorData(err));
7527
7615
  }
@@ -7531,6 +7619,7 @@ var unifiedChatApp = new Hono26().post(
7531
7619
  conversationId,
7532
7620
  contentBlocks,
7533
7621
  senderName,
7622
+ senderIsMind: !!senderIsMind,
7534
7623
  participants,
7535
7624
  isDM,
7536
7625
  slugExtra: { convType: conv.type, convName },
@@ -7888,10 +7977,15 @@ if (!process.env.VOLUTE_HOME) {
7888
7977
  if (process.env.VOLUTE_TIMEZONE && !process.env.TZ) {
7889
7978
  process.env.TZ = process.env.VOLUTE_TIMEZONE;
7890
7979
  }
7891
- function writeDaemonConfig(path, config2) {
7892
- writeFileSync9(path, `${JSON.stringify(config2, null, 2)}
7980
+ function writeDaemonConfig(systemDir, config2, token) {
7981
+ const configPath = resolve17(systemDir, "daemon.json");
7982
+ writeFileSync9(configPath, `${JSON.stringify(config2, null, 2)}
7983
+ `, { mode: 420 });
7984
+ chmodSync(configPath, 420);
7985
+ const tokenPath = resolve17(systemDir, "daemon-token");
7986
+ writeFileSync9(tokenPath, `${token}
7893
7987
  `, { mode: 384 });
7894
- chmodSync(path, 384);
7988
+ chmodSync(tokenPath, 384);
7895
7989
  }
7896
7990
  async function startDaemon(opts) {
7897
7991
  const { port, hostname } = opts;
@@ -7911,21 +8005,24 @@ async function startDaemon(opts) {
7911
8005
  }
7912
8006
  const DAEMON_PID_PATH = resolve17(systemDir, "daemon.pid");
7913
8007
  const DAEMON_JSON_PATH = resolve17(systemDir, "daemon.json");
8008
+ const DAEMON_TOKEN_PATH = resolve17(systemDir, "daemon-token");
7914
8009
  mkdirSync10(home, { recursive: true });
7915
8010
  ensureSystemDir();
7916
- const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-IYOUPCIK.js");
8011
+ const { migrateConfigSecrets, migrateSetupCompleted } = await import("./setup-25MMJ3J4.js");
7917
8012
  migrateConfigSecrets();
7918
8013
  migrateSetupCompleted();
7919
- await (await import("./db-ZJJJYJBU.js")).getDb();
8014
+ const { migrateAiModelQualification } = await import("./ai-service-FBCCEYF5.js");
8015
+ migrateAiModelQualification();
8016
+ await (await import("./db-CZV62BOQ.js")).getDb();
7920
8017
  try {
7921
- const { eq: eq6, and: and5 } = await import("drizzle-orm");
7922
- const { users: users2 } = await import("./schema-HHYJDG7B.js");
7923
- const db = await (await import("./db-ZJJJYJBU.js")).getDb();
7924
- await db.update(users2).set({ role: "system" }).where(and5(eq6(users2.user_type, "system"), eq6(users2.role, "user")));
8018
+ const { eq: eq8, and: and6 } = await import("drizzle-orm");
8019
+ const { users: users2 } = await import("./schema-LRUTFOER.js");
8020
+ const db = await (await import("./db-CZV62BOQ.js")).getDb();
8021
+ await db.update(users2).set({ role: "system" }).where(and6(eq8(users2.user_type, "system"), eq8(users2.role, "user")));
7925
8022
  } catch (err) {
7926
8023
  logger_default.warn("failed to migrate system user role", logger_default.errorData(err));
7927
8024
  }
7928
- const { initSandbox } = await import("./sandbox-E7RWZDVG.js");
8025
+ const { initSandbox } = await import("./sandbox-SJH3XOPZ.js");
7929
8026
  await initSandbox();
7930
8027
  try {
7931
8028
  await syncBuiltinSkills();
@@ -7952,7 +8049,7 @@ async function startDaemon(opts) {
7952
8049
  logger_default.warn("failed to ensure #system channel", logger_default.errorData(err));
7953
8050
  }
7954
8051
  try {
7955
- const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-EAASQPTN.js");
8052
+ const { getOrCreateSystemUser: getOrCreateSystemUser2 } = await import("./auth-3RIG6GKN.js");
7956
8053
  await getOrCreateSystemUser2();
7957
8054
  } catch (err) {
7958
8055
  logger_default.warn(
@@ -7960,13 +8057,13 @@ async function startDaemon(opts) {
7960
8057
  logger_default.errorData(err)
7961
8058
  );
7962
8059
  }
7963
- const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes(32).toString("hex");
7964
- const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-IYOUPCIK.js");
8060
+ const token = process.env.VOLUTE_DAEMON_TOKEN || randomBytes2(32).toString("hex");
8061
+ const { readGlobalConfig: readGlobalConfig2 } = await import("./setup-25MMJ3J4.js");
7965
8062
  const globalCfg = readGlobalConfig2();
7966
8063
  let tls;
7967
8064
  if (opts.tailscale || globalCfg.tailscale) {
7968
8065
  try {
7969
- const { getTailscaleTls } = await import("./tailscale-TIRTGM6E.js");
8066
+ const { getTailscaleTls } = await import("./tailscale-Q7ZJL7S7.js");
7970
8067
  const tlsConfig = await getTailscaleTls();
7971
8068
  tls = { key: tlsConfig.key, cert: tlsConfig.cert };
7972
8069
  logger_default.info("Tailscale HTTPS enabled", { hostname: tlsConfig.hostname });
@@ -7994,10 +8091,10 @@ async function startDaemon(opts) {
7994
8091
  process.env.VOLUTE_DAEMON_PORT = String(daemonPort);
7995
8092
  process.env.VOLUTE_DAEMON_HOSTNAME = hostname;
7996
8093
  writeFileSync9(DAEMON_PID_PATH, myPid, { mode: 420 });
7997
- const daemonConfig = { port, hostname, token };
8094
+ const daemonConfig = { port, hostname };
7998
8095
  if (internalPort) daemonConfig.internalPort = internalPort;
7999
8096
  if (tls) daemonConfig.tls = true;
8000
- writeDaemonConfig(DAEMON_JSON_PATH, daemonConfig);
8097
+ writeDaemonConfig(systemDir, daemonConfig, token);
8001
8098
  const delivery = initDeliveryManager();
8002
8099
  const manager = initMindManager();
8003
8100
  manager.loadCrashAttempts();
@@ -8044,10 +8141,10 @@ async function startDaemon(opts) {
8044
8141
  await Promise.all(workers);
8045
8142
  }
8046
8143
  try {
8047
- const { isSetupComplete: isSetupComplete2 } = await import("./setup-IYOUPCIK.js");
8144
+ const { isSetupComplete: isSetupComplete2 } = await import("./setup-25MMJ3J4.js");
8048
8145
  if (isSetupComplete2()) {
8049
- const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-NJFAY5CZ.js");
8050
- const { startSpiritFull } = await import("./mind-service-PR4PR4XI.js");
8146
+ const { ensureSpiritProject, syncSpiritTemplate } = await import("./spirit-JQQHUVWR.js");
8147
+ const { startSpiritFull } = await import("./mind-service-ZBVUMJ5H.js");
8051
8148
  await ensureSpiritProject();
8052
8149
  await syncSpiritTemplate();
8053
8150
  const spiritEntry = await findMind("volute");
@@ -8064,7 +8161,7 @@ async function startDaemon(opts) {
8064
8161
  bridgeManager.startBridges(daemonPort).catch((err) => {
8065
8162
  logger_default.warn("failed to start bridges", logger_default.errorData(err));
8066
8163
  });
8067
- import("./cloud-sync-3OPHFOYQ.js").then(
8164
+ import("./cloud-sync-FRYNWKCR.js").then(
8068
8165
  ({ consumeQueuedMessages }) => consumeQueuedMessages().catch((err) => {
8069
8166
  logger_default.warn("failed to consume queued cloud messages", logger_default.errorData(err));
8070
8167
  })
@@ -8072,7 +8169,7 @@ async function startDaemon(opts) {
8072
8169
  logger_default.warn("failed to load cloud-sync module", logger_default.errorData(err));
8073
8170
  });
8074
8171
  try {
8075
- const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-TCKAX7ZA.js");
8172
+ const { backfillTemplateHashes, notifyVersionUpdate } = await import("./version-notify-DCAIXXZB.js");
8076
8173
  backfillTemplateHashes();
8077
8174
  notifyVersionUpdate().catch((err) => {
8078
8175
  logger_default.warn("failed to send version update notifications", logger_default.errorData(err));
@@ -8083,6 +8180,7 @@ async function startDaemon(opts) {
8083
8180
  delivery.restoreFromDb().catch((err) => {
8084
8181
  logger_default.warn("failed to restore delivery queue", logger_default.errorData(err));
8085
8182
  });
8183
+ delivery.startRedrive();
8086
8184
  cleanExpiredSessions().catch((err) => {
8087
8185
  logger_default.warn("failed to clean expired sessions", logger_default.errorData(err));
8088
8186
  });
@@ -8104,9 +8202,12 @@ async function startDaemon(opts) {
8104
8202
  } catch {
8105
8203
  }
8106
8204
  try {
8107
- const data = JSON.parse(readFileSync10(DAEMON_JSON_PATH, "utf-8"));
8108
- if (data.token === token) {
8109
- unlinkSync2(DAEMON_JSON_PATH);
8205
+ if (readFileSync10(DAEMON_TOKEN_PATH, "utf-8").trim() === token) {
8206
+ unlinkSync2(DAEMON_TOKEN_PATH);
8207
+ try {
8208
+ unlinkSync2(DAEMON_JSON_PATH);
8209
+ } catch {
8210
+ }
8110
8211
  }
8111
8212
  } catch {
8112
8213
  }