volute 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +79 -328
  2. package/dist/{accept-KYNC5X3U.js → accept-5NK5XJB5.js} +2 -1
  3. package/dist/{activity-events-MIOK3IOU.js → activity-events-H7YI3BTF.js} +4 -4
  4. package/dist/{ai-service-643TNFPH.js → ai-service-V4CTNGI3.js} +6 -5
  5. package/dist/{archive-6Z4GFQPH.js → archive-ORM3ENHI.js} +3 -3
  6. package/dist/{auth-A4VGHADI.js → auth-DT7GY7EZ.js} +11 -7
  7. package/dist/{avatar-image-Y45R7LW5.js → avatar-image-QEPAP3A5.js} +4 -4
  8. package/dist/{backup-UDH4GMSW.js → backup-IE6PMXJO.js} +17 -18
  9. package/dist/{bridge-3FNSWLNQ.js → bridge-YK7NTRU5.js} +2 -1
  10. package/dist/{channels-SGQZHNQJ.js → channels-FSAHOQWI.js} +2 -1
  11. package/dist/{chat-X43VYHH2.js → chat-W5K6HCBE.js} +9 -9
  12. package/dist/{chunk-LTLFOSA4.js → chunk-3BAFMGCJ.js} +3 -3
  13. package/dist/{chunk-XVBSFLM7.js → chunk-4CN7OCJE.js} +2 -2
  14. package/dist/{chunk-HPGN6XRT.js → chunk-574M6I7F.js} +39 -6
  15. package/dist/{chunk-FJ2MZ5PH.js → chunk-5FVANDON.js} +2 -2
  16. package/dist/{chunk-2MJ7EOMT.js → chunk-5IIVOWSP.js} +28 -19
  17. package/dist/{chunk-4FCL4DVZ.js → chunk-5PKFJHCR.js} +2 -2
  18. package/dist/{chunk-MOERAQXH.js → chunk-5TPC4KCV.js} +38 -31
  19. package/dist/{chunk-YNBXEQAP.js → chunk-632FJ23O.js} +24 -20
  20. package/dist/{chunk-T4VS7UDW.js → chunk-636GFQNE.js} +12 -12
  21. package/dist/{chunk-EIQDZC43.js → chunk-6BM2I736.js} +25 -6
  22. package/dist/{chunk-WARH6GIV.js → chunk-74T3CEXK.js} +17 -15
  23. package/dist/{chunk-OQJMTHEH.js → chunk-77IZUVBB.js} +2 -2
  24. package/dist/{chunk-USD45JKR.js → chunk-7DXLIYPY.js} +77 -64
  25. package/dist/chunk-7FDWPQCU.js +289 -0
  26. package/dist/chunk-7OZWKHA3.js +548 -0
  27. package/dist/{chunk-IJPMIEVE.js → chunk-7UWXAJMK.js} +1 -1
  28. package/dist/{chunk-SZJCJVXV.js → chunk-7XDOTN4R.js} +2 -2
  29. package/dist/{chunk-YKA7DCVW.js → chunk-AFSLNUTT.js} +1 -1
  30. package/dist/{chunk-DGGZVZTM.js → chunk-AJWFDIEY.js} +17 -2
  31. package/dist/chunk-ALIVXDM4.js +1014 -0
  32. package/dist/chunk-CAIYV33P.js +523 -0
  33. package/dist/{chunk-MTRMQFCQ.js → chunk-E5S3ILVA.js} +2 -2
  34. package/dist/{chunk-SHKM5RGD.js → chunk-EEOMA4DX.js} +1 -1
  35. package/dist/chunk-EQKSRVDE.js +2857 -0
  36. package/dist/chunk-FNJCBRD3.js +31 -0
  37. package/dist/chunk-HW6Q7AMB.js +113 -0
  38. package/dist/chunk-JI67D7O2.js +1222 -0
  39. package/dist/{chunk-3YUBFTDN.js → chunk-KAEZOVP6.js} +1 -1
  40. package/dist/chunk-LNNI3MS4.js +54 -0
  41. package/dist/{chunk-BCG45AQB.js → chunk-M74I3HHF.js} +15 -5
  42. package/dist/chunk-MLZBBLMT.js +109 -0
  43. package/dist/{chunk-5T5YMX6S.js → chunk-PW65BXY5.js} +5 -0
  44. package/dist/{chunk-2OIYNHWO.js → chunk-QFVGPKJI.js} +1 -1
  45. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  46. package/dist/{chunk-HNGFKPNS.js → chunk-SFRYRW37.js} +85 -36
  47. package/dist/{chunk-7KRIHYLR.js → chunk-SSNZ6ZNN.js} +2 -2
  48. package/dist/{chunk-HUNDXZBZ.js → chunk-TGJHGVBZ.js} +48 -19
  49. package/dist/{chunk-PJVPDY2Z.js → chunk-U3EJJ7JN.js} +2 -2
  50. package/dist/{chunk-ODDGGLM6.js → chunk-UC6BNMGT.js} +43 -13
  51. package/dist/{chunk-Q7HOAVFR.js → chunk-WBBKYUGF.js} +4 -6
  52. package/dist/{chunk-WQ46E33M.js → chunk-X67VT4QO.js} +2 -2
  53. package/dist/{chunk-MJRXFUMS.js → chunk-YQOYZ6G7.js} +3 -5
  54. package/dist/{chunk-VZRA5TYO.js → chunk-ZUG76D3P.js} +36 -6
  55. package/dist/cli.js +34 -27
  56. package/dist/{clock-CNRF6F7T.js → clock-LH7UFNLJ.js} +5 -4
  57. package/dist/{cloud-sync-WHKCB4CD.js → cloud-sync-IDJFVDEP.js} +23 -20
  58. package/dist/{config-RPSLA2HX.js → config-UGJS57VT.js} +3 -3
  59. package/dist/{conversations-C7IKYSHD.js → conversations-S2232Y5I.js} +9 -5
  60. package/dist/{create-GJKHABJB.js → create-2M7DAHQX.js} +2 -2
  61. package/dist/{create-VIQVP3QB.js → create-M727VUDF.js} +2 -1
  62. package/dist/{daemon-client-ZFY5MOAL.js → daemon-client-VVYFZI2D.js} +2 -1
  63. package/dist/{daemon-restart-7KUHVZIN.js → daemon-restart-2AAGYFHE.js} +6 -7
  64. package/dist/daemon.js +2166 -833
  65. package/dist/{db-FO6LXYCW.js → db-QAY45Z56.js} +2 -2
  66. package/dist/{delete-ESEP7NGQ.js → delete-O7PEFQDF.js} +1 -1
  67. package/dist/delivery-manager-B4OKWD2P.js +33 -0
  68. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-JE7SHYNU.js} +10 -6
  69. package/dist/{down-G6IADJXV.js → down-D4IRNEMJ.js} +5 -5
  70. package/dist/echo-text-WYAAODBV.js +39 -0
  71. package/dist/{env-C7Z55QCJ.js → env-ABM5IMA7.js} +8 -7
  72. package/dist/{env-6TGPO5WG.js → env-AZK5X4W7.js} +3 -3
  73. package/dist/{exec-QIFYZG4C.js → exec-2H65LMTA.js} +3 -3
  74. package/dist/{export-C2K3NIRD.js → export-3E3EL6W3.js} +4 -4
  75. package/dist/{extension-J4ZQ2GHV.js → extension-MRYJWSMV.js} +2 -1
  76. package/dist/{extensions-GEKF47A4.js → extensions-7KP3X6G7.js} +23 -20
  77. package/dist/{files-LXQSQOFG.js → files-6BMKVG5G.js} +5 -4
  78. package/dist/{import-SX3ZXHB4.js → import-UPNTEBBT.js} +9 -9
  79. package/dist/{isolation-MCT4ZYLT.js → isolation-OKDFM6WA.js} +5 -3
  80. package/dist/{join-34TR2GXQ.js → join-PYA36HEC.js} +1 -1
  81. package/dist/{list-PMUXZNAM.js → list-4BVGH7AN.js} +8 -3
  82. package/dist/{login-CL4IT2ED.js → login-MOXVLULE.js} +7 -6
  83. package/dist/{login-ARRZ4ORJ.js → login-RJJTCVR3.js} +4 -3
  84. package/dist/{logout-YZRMZ3O2.js → logout-BYV433X6.js} +2 -1
  85. package/dist/{logout-HZYJCYCA.js → logout-DWD3OQVT.js} +4 -3
  86. package/dist/message-delivery-3456PXB7.js +43 -0
  87. package/dist/migrate-thread-config-4A5LW36Z.js +90 -0
  88. package/dist/{mind-46AOK373.js → mind-NYRALSV2.js} +19 -19
  89. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-L7GCYXQR.js} +5 -5
  90. package/dist/{mind-contacts-GV7XYL5D.js → mind-contacts-QYU4TPEA.js} +8 -7
  91. package/dist/{mind-history-TTGA5BHZ.js → mind-history-NXXOOTX6.js} +34 -11
  92. package/dist/{mind-list-OTHV4ZO4.js → mind-list-SYRA7CH5.js} +3 -2
  93. package/dist/{mind-profile-LUG353ZT.js → mind-profile-MRCVZPYT.js} +1 -1
  94. package/dist/mind-service-5TA7UJAT.js +47 -0
  95. package/dist/{mind-sleep-DV7KP255.js → mind-sleep-TH6BRQ5X.js} +5 -4
  96. package/dist/{mind-status-BJNPGXKC.js → mind-status-Z2LQFA6F.js} +3 -2
  97. package/dist/{mind-wake-LVTZ55EZ.js → mind-wake-LDGPRA42.js} +2 -1
  98. package/dist/{package-RTN2PS7O.js → package-XYSV7NUT.js} +27 -26
  99. package/dist/prompts-QKFPX6DQ.js +24 -0
  100. package/dist/{read-YSWUXYL7.js → read-ZXGUT4QC.js} +5 -3
  101. package/dist/{register-42VBJKTP.js → register-OX4HRSGC.js} +4 -3
  102. package/dist/{registry-UMMDRPRM.js → registry-4UF6OCOE.js} +10 -4
  103. package/dist/{reject-SXGCYN4A.js → reject-TWB4QJHG.js} +2 -1
  104. package/dist/{restart-YYEGZJDD.js → restart-AGEGNSYQ.js} +6 -5
  105. package/dist/{sandbox-5VQ454XP.js → sandbox-EGEU5GGE.js} +8 -5
  106. package/dist/scheduler-UV6RUJ7P.js +25 -0
  107. package/dist/{schema-VEVO7HG6.js → schema-C2GIPD4V.js} +3 -3
  108. package/dist/{seed-F2HXZQU4.js → seed-3IMBOAGK.js} +1 -1
  109. package/dist/{seed-check-TYD7RRZY.js → seed-check-NRJEQZST.js} +14 -5
  110. package/dist/{seed-cmd-MLCTGSX5.js → seed-cmd-U73XVQMI.js} +3 -3
  111. package/dist/{seed-create-KQ7OILWB.js → seed-create-FBLNVSUP.js} +4 -4
  112. package/dist/{seed-readiness-RBPGOLSZ.js → seed-readiness-QRA46ZJQ.js} +4 -5
  113. package/dist/{seed-sprout-HVYCFJLA.js → seed-sprout-3B4SZX7J.js} +12 -12
  114. package/dist/{send-TD2X5U3K.js → send-NBRZJXKX.js} +34 -12
  115. package/dist/{service-CVZMQYK6.js → service-XMXAXGJD.js} +4 -4
  116. package/dist/{service-install-VCRMFWX5.js → service-install-MOKTEG7B.js} +5 -5
  117. package/dist/{setup-5ECWTYKG.js → setup-CAJFOR2N.js} +12 -3
  118. package/dist/{setup-AE4CVUC4.js → setup-JGYAXXW7.js} +6 -7
  119. package/dist/{skill-UUM4JZRF.js → skill-NXIHDQCG.js} +7 -6
  120. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  121. package/dist/skills/imagegen/SKILL.md +21 -4
  122. package/dist/skills/imagegen/scripts/imagegen.ts +124 -21
  123. package/dist/skills/tending/SKILL.md +7 -7
  124. package/dist/skills/volute-mind/SKILL.md +6 -6
  125. package/dist/skills/volute-mind/references/integrations.md +2 -2
  126. package/dist/skills/volute-mind/references/routing.md +19 -18
  127. package/dist/skills/volute-mind/references/variants.md +48 -14
  128. package/dist/{skills-B3SVDCEB.js → skills-5MSMT7PT.js} +7 -6
  129. package/dist/sleep-manager-LIEPO6WG.js +41 -0
  130. package/dist/spirit-IL5EMWYZ.js +34 -0
  131. package/dist/spirit-availability-YFEGJNJ2.js +42 -0
  132. package/dist/{split-SKZ4Y2D6.js → split-BU5VWX6P.js} +5 -2
  133. package/dist/{sprout-KQ43BWZJ.js → sprout-UPXGJ5HX.js} +1 -1
  134. package/dist/{src-YH6PQ7LJ.js → src-HGNE2XG4.js} +7 -1
  135. package/dist/{src-WLJE73NP.js → src-LCSOQY6Z.js} +60 -54
  136. package/dist/{src-JAOVXO5W.js → src-UDMJZ6MH.js} +8 -8
  137. package/dist/{start-O6UKK7BG.js → start-MUMNFR52.js} +4 -3
  138. package/dist/{status-F4PGREUU.js → status-IL67CYM4.js} +7 -6
  139. package/dist/{stop-UBX6X5RA.js → stop-EKERNYTX.js} +6 -5
  140. package/dist/system-events-A6IGCEC3.js +46 -0
  141. package/dist/system-install-YZSWCPDK.js +8 -0
  142. package/dist/{systems-JJNGQHAO.js → systems-VDRUZXNF.js} +5 -4
  143. package/dist/{tailscale-MCLY5GYE.js → tailscale-SJUW2ETR.js} +3 -3
  144. package/dist/{template-staleness-EMQORSGG.js → template-staleness-T27VCGYA.js} +4 -4
  145. package/dist/{turn-tracker-KLPV3PAU.js → turn-tracker-6EI2EL7G.js} +7 -7
  146. package/dist/{up-RVGDE6CQ.js → up-CLL4665A.js} +5 -6
  147. package/dist/{update-OXQEGGXD.js → update-HVAUCQPN.js} +5 -5
  148. package/dist/{update-check-266R4ZSY.js → update-check-2MGBBAFF.js} +3 -3
  149. package/dist/{upgrade-K5FIQPQD.js → upgrade-LIE46UBB.js} +1 -1
  150. package/dist/variant-cleanup-HJ7Z75R6.js +22 -0
  151. package/dist/{version-notify-73YURTFR.js → version-notify-7BUHWZNT.js} +11 -28
  152. package/dist/web-assets/assets/index-ClsUcXiW.js +95 -0
  153. package/dist/web-assets/assets/index-Dz3wTbtR.css +1 -0
  154. package/dist/web-assets/index.html +2 -2
  155. package/dist/xai-P42CRCZA.js +129 -0
  156. package/drizzle/0013_variant_purpose.sql +3 -0
  157. package/drizzle/0014_system_events.sql +26 -0
  158. package/drizzle/0015_rename_session_to_thread.sql +12 -0
  159. package/drizzle/0016_brain_to_human.sql +4 -0
  160. package/drizzle/0017_event_history_rows.sql +9 -0
  161. package/drizzle/meta/_journal.json +35 -0
  162. package/package.json +27 -26
  163. package/packages/extensions/notes/dist/ui/assets/index-BBs8pyel.js +67 -0
  164. package/packages/extensions/notes/dist/ui/index.html +1 -1
  165. package/packages/extensions/pages/dist/ui/assets/index-B589e8Xe.js +2 -0
  166. package/packages/extensions/pages/dist/ui/index.html +1 -1
  167. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +67 -0
  168. package/packages/extensions/plan/dist/ui/index.html +1 -1
  169. package/templates/_base/.init/.config/prompts.json +1 -0
  170. package/templates/_base/.init/.local/hooks/startup-context.ts +34 -2
  171. package/templates/_base/home/.config/routes.json +2 -2
  172. package/templates/_base/home/VOLUTE.md +30 -5
  173. package/templates/_base/src/lib/daemon-client.ts +28 -8
  174. package/templates/_base/src/lib/event-turn.ts +39 -0
  175. package/templates/_base/src/lib/format-prefix.ts +21 -2
  176. package/templates/_base/src/lib/router.ts +6 -2
  177. package/templates/_base/src/lib/routing.ts +8 -8
  178. package/templates/_base/src/lib/startup.ts +9 -1
  179. package/templates/_base/src/lib/types.ts +7 -1
  180. package/templates/_base/src/lib/volute-server.ts +30 -1
  181. package/templates/claude/.init/.config/routes.json +3 -3
  182. package/templates/claude/.init/CLAUDE.md +1 -1
  183. package/templates/claude/package.json.tmpl +4 -4
  184. package/templates/claude/src/agent.ts +5 -2
  185. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  186. package/templates/claude/src/lib/hooks/reply-instructions.ts +26 -1
  187. package/templates/codex/.init/.config/routes.json +3 -3
  188. package/templates/codex/.init/AGENTS.md +1 -1
  189. package/templates/codex/package.json.tmpl +4 -4
  190. package/templates/codex/src/agent.ts +11 -11
  191. package/templates/codex/src/lib/turn-context.ts +51 -0
  192. package/templates/pi/.init/.config/routes.json +3 -3
  193. package/templates/pi/.init/MINDS.md +1 -1
  194. package/templates/pi/package.json.tmpl +6 -6
  195. package/templates/pi/src/agent.ts +9 -1
  196. package/templates/pi/src/lib/event-handler.ts +47 -6
  197. package/templates/pi/src/lib/reply-instructions-extension.ts +28 -1
  198. package/dist/chunk-5KXCM5AW.js +0 -169
  199. package/dist/chunk-IYCV34IU.js +0 -158
  200. package/dist/chunk-VB2QXUJQ.js +0 -5260
  201. package/dist/chunk-WW6C3PLV.js +0 -280
  202. package/dist/delivery-manager-HZTT5SJR.js +0 -36
  203. package/dist/echo-text-H3SH337Q.js +0 -36
  204. package/dist/message-delivery-GPQ5SE5N.js +0 -40
  205. package/dist/mind-service-2ARGJNKP.js +0 -44
  206. package/dist/notices-2KU5NTRY.js +0 -25
  207. package/dist/scheduler-I7JZCT47.js +0 -34
  208. package/dist/sleep-manager-Z27R3WTW.js +0 -38
  209. package/dist/spirit-WXS3HCLN.js +0 -25
  210. package/dist/system-chat-63ABFF6V.js +0 -40
  211. package/dist/web-assets/assets/index-WdZ60R6c.css +0 -1
  212. package/dist/web-assets/assets/index-taX6Ezfa.js +0 -92
  213. package/packages/extensions/notes/dist/ui/assets/index-D0-DgYyj.js +0 -67
  214. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +0 -2
  215. package/packages/extensions/plan/dist/ui/assets/index-Cfzs-0G9.js +0 -67
@@ -0,0 +1,1014 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ clearMind,
4
+ summarizeOrphanedTurns
5
+ } from "./chunk-7DXLIYPY.js";
6
+ import {
7
+ deliverEvent,
8
+ recordNotice
9
+ } from "./chunk-7OZWKHA3.js";
10
+ import {
11
+ getPrompt
12
+ } from "./chunk-5TPC4KCV.js";
13
+ import {
14
+ isSandboxEnabled,
15
+ wrapForSandbox
16
+ } from "./chunk-ZUG76D3P.js";
17
+ import {
18
+ chownMindDir,
19
+ isIsolationEnabled,
20
+ wrapForIsolation
21
+ } from "./chunk-AJWFDIEY.js";
22
+ import {
23
+ loadMergedEnv
24
+ } from "./chunk-7UWXAJMK.js";
25
+ import {
26
+ getAiConfig,
27
+ resolveApiKey,
28
+ resolveOAuthCredentials
29
+ } from "./chunk-632FJ23O.js";
30
+ import {
31
+ logger_default
32
+ } from "./chunk-QUSMYGNQ.js";
33
+ import {
34
+ findMind,
35
+ getSystemName,
36
+ mindDir,
37
+ mindTmpDir,
38
+ mindTmpEnv,
39
+ readGlobalConfig,
40
+ setMindRunning,
41
+ stateDir,
42
+ voluteSystemDir
43
+ } from "./chunk-CAIYV33P.js";
44
+
45
+ // packages/daemon/src/lib/util/json-state.ts
46
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
47
+ import { writeFile } from "fs/promises";
48
+ function loadJsonMap(path) {
49
+ const map = /* @__PURE__ */ new Map();
50
+ try {
51
+ if (existsSync(path)) {
52
+ const raw = readFileSync(path, "utf-8").trim();
53
+ if (!raw) return map;
54
+ const data = JSON.parse(raw);
55
+ for (const [key, value] of Object.entries(data)) {
56
+ if (typeof value === "number") map.set(key, value);
57
+ }
58
+ }
59
+ } catch (err) {
60
+ console.warn(`[state] failed to load ${path}:`, err);
61
+ }
62
+ return map;
63
+ }
64
+ function saveJsonMap(path, map) {
65
+ const data = {};
66
+ for (const [key, value] of map) {
67
+ data[key] = value;
68
+ }
69
+ try {
70
+ writeFileSync(path, `${JSON.stringify(data)}
71
+ `);
72
+ } catch (err) {
73
+ console.warn(`[state] failed to save ${path}:`, err);
74
+ }
75
+ }
76
+ async function saveJsonMapAsync(path, map) {
77
+ const data = {};
78
+ for (const [key, value] of map) {
79
+ data[key] = value;
80
+ }
81
+ try {
82
+ await writeFile(path, `${JSON.stringify(data)}
83
+ `);
84
+ } catch (err) {
85
+ console.warn(`[state] failed to save ${path}:`, err);
86
+ }
87
+ }
88
+ function clearJsonMap(path, map) {
89
+ map.clear();
90
+ try {
91
+ if (existsSync(path)) unlinkSync(path);
92
+ } catch (err) {
93
+ console.warn(`[state] failed to clear ${path}:`, err);
94
+ }
95
+ }
96
+
97
+ // packages/daemon/src/lib/util/rotating-log.ts
98
+ import { createWriteStream, existsSync as existsSync2, statSync } from "fs";
99
+ import { access, rename, rm } from "fs/promises";
100
+ import { Writable } from "stream";
101
+ var MAX_SIZE = 10 * 1024 * 1024;
102
+ var RotatingLog = class extends Writable {
103
+ constructor(path, maxSize = MAX_SIZE, maxFiles = 5) {
104
+ super();
105
+ this.path = path;
106
+ this.maxSize = maxSize;
107
+ this.maxFiles = maxFiles;
108
+ this.on("error", () => {
109
+ });
110
+ try {
111
+ this.size = existsSync2(path) ? statSync(path).size : 0;
112
+ } catch {
113
+ this.size = 0;
114
+ }
115
+ this.stream = createWriteStream(path, { flags: "a" });
116
+ }
117
+ stream;
118
+ size;
119
+ async rotateAsync() {
120
+ const oldest = `${this.path}.${this.maxFiles}`;
121
+ await access(oldest).then(() => rm(oldest)).catch(() => {
122
+ });
123
+ for (let i = this.maxFiles - 1; i >= 1; i--) {
124
+ const from = `${this.path}.${i}`;
125
+ const to = `${this.path}.${i + 1}`;
126
+ await access(from).then(() => rename(from, to)).catch(() => {
127
+ });
128
+ }
129
+ await rename(this.path, `${this.path}.1`);
130
+ const oldStream = this.stream;
131
+ this.stream = createWriteStream(this.path);
132
+ oldStream.end();
133
+ }
134
+ _write(chunk, _encoding, callback) {
135
+ this.size += chunk.length;
136
+ if (this.size > this.maxSize) {
137
+ this.rotateAsync().then(() => {
138
+ this.size = chunk.length;
139
+ this.stream.write(chunk, callback);
140
+ }).catch(() => {
141
+ this.size = chunk.length;
142
+ this.stream.write(chunk, callback);
143
+ });
144
+ return;
145
+ }
146
+ this.stream.write(chunk, callback);
147
+ }
148
+ _final(callback) {
149
+ this.stream.end(callback);
150
+ }
151
+ };
152
+
153
+ // packages/daemon/src/lib/daemon/credential-sync.ts
154
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
155
+ import { resolve } from "path";
156
+ var slog = logger_default.child("cred-sync");
157
+ async function writeClaudeCredentials(homeDir, baseName, oauth) {
158
+ const claudeDir = resolve(homeDir, ".claude");
159
+ mkdirSync(claudeDir, { recursive: true });
160
+ writeFileSync2(
161
+ resolve(claudeDir, ".credentials.json"),
162
+ JSON.stringify({
163
+ claudeAiOauth: {
164
+ accessToken: oauth.access,
165
+ refreshToken: oauth.refresh,
166
+ expiresAt: oauth.expires ? new Date(oauth.expires).toISOString() : null,
167
+ scopes: ["user:inference", "user:profile"]
168
+ }
169
+ }),
170
+ { mode: 384 }
171
+ );
172
+ if (isIsolationEnabled()) {
173
+ await chownMindDir(claudeDir, baseName);
174
+ }
175
+ return claudeDir;
176
+ }
177
+ async function writePiProviderKey(piAgentDir, baseName, provider, key) {
178
+ mkdirSync(piAgentDir, { recursive: true });
179
+ const authPath = resolve(piAgentDir, "auth.json");
180
+ const authData = existsSync3(authPath) ? JSON.parse(readFileSync2(authPath, "utf-8")) : {};
181
+ authData[provider] = { type: "api_key", key };
182
+ writeFileSync2(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
183
+ if (isIsolationEnabled()) {
184
+ await chownMindDir(piAgentDir, baseName);
185
+ }
186
+ }
187
+ async function writePiProviderOAuth(piAgentDir, baseName, provider, oauth) {
188
+ mkdirSync(piAgentDir, { recursive: true });
189
+ const authPath = resolve(piAgentDir, "auth.json");
190
+ const authData = existsSync3(authPath) ? JSON.parse(readFileSync2(authPath, "utf-8")) : {};
191
+ authData[provider] = { type: "oauth", ...oauth };
192
+ writeFileSync2(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
193
+ if (isIsolationEnabled()) {
194
+ await chownMindDir(piAgentDir, baseName);
195
+ }
196
+ }
197
+ function piUsesProvider(piAgentDir, provider) {
198
+ try {
199
+ const authPath = resolve(piAgentDir, "auth.json");
200
+ if (!existsSync3(authPath)) return false;
201
+ const data = JSON.parse(readFileSync2(authPath, "utf-8"));
202
+ return data[provider] != null;
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
207
+ async function syncProviderToMinds(provider, deps = {}) {
208
+ if (provider !== "anthropic" && provider !== "xai") return;
209
+ const getOauth = deps.getOauth ?? (() => getAiConfig()?.providers[provider]?.oauth);
210
+ const oauth = getOauth();
211
+ if (!oauth?.access) return;
212
+ const listRunning = deps.listRunning ?? (() => []);
213
+ const lookup = deps.lookup ?? findMind;
214
+ for (const name of listRunning()) {
215
+ try {
216
+ const entry = await lookup(name);
217
+ if (!entry) continue;
218
+ const dir = entry.dir ?? mindDir(name);
219
+ const baseName = entry.parent ?? name;
220
+ const template = entry.template;
221
+ if (template === "pi") {
222
+ const piAgentDir = resolve(dir, ".mind", "pi-agent");
223
+ if (piUsesProvider(piAgentDir, provider)) {
224
+ await writePiProviderKey(piAgentDir, baseName, provider, oauth.access);
225
+ }
226
+ } else if (provider === "anthropic" && (template === "claude" || !template)) {
227
+ await writeClaudeCredentials(resolve(dir, "home"), baseName, oauth);
228
+ }
229
+ } catch (err) {
230
+ slog.error(`failed to sync ${provider} credentials to mind ${name}`, logger_default.errorData(err));
231
+ }
232
+ }
233
+ }
234
+
235
+ // packages/daemon/src/lib/daemon/mind-tokens.ts
236
+ import { randomUUID } from "crypto";
237
+ var tokenToMind = /* @__PURE__ */ new Map();
238
+ var mindToToken = /* @__PURE__ */ new Map();
239
+ function generateMindToken(mindName) {
240
+ revokeMindToken(mindName);
241
+ const token = randomUUID();
242
+ tokenToMind.set(token, mindName);
243
+ mindToToken.set(mindName, token);
244
+ return token;
245
+ }
246
+ function revokeMindToken(mindName) {
247
+ const token = mindToToken.get(mindName);
248
+ if (token) {
249
+ tokenToMind.delete(token);
250
+ mindToToken.delete(mindName);
251
+ }
252
+ }
253
+ function resolveMindToken(token) {
254
+ return tokenToMind.get(token) ?? null;
255
+ }
256
+ function getMindToken(mindName) {
257
+ return mindToToken.get(mindName) ?? null;
258
+ }
259
+
260
+ // packages/daemon/src/lib/daemon/mind-manager.ts
261
+ import { execFile, spawn } from "child_process";
262
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
263
+ import { resolve as resolve2 } from "path";
264
+ import { promisify } from "util";
265
+
266
+ // packages/daemon/src/lib/util/health.ts
267
+ async function checkHealth(port) {
268
+ try {
269
+ const res = await fetch(`http://127.0.0.1:${port}/health`, {
270
+ signal: AbortSignal.timeout(2e3)
271
+ });
272
+ if (!res.ok) return { ok: false };
273
+ const data = await res.json();
274
+ return { ok: true, name: data.name };
275
+ } catch {
276
+ return { ok: false };
277
+ }
278
+ }
279
+
280
+ // packages/daemon/src/lib/daemon/restart-tracker.ts
281
+ var DEFAULT_MAX_ATTEMPTS = 5;
282
+ var DEFAULT_BASE_DELAY = 3e3;
283
+ var DEFAULT_MAX_DELAY = 6e4;
284
+ var RestartTracker = class {
285
+ attempts = /* @__PURE__ */ new Map();
286
+ maxAttempts;
287
+ baseDelay;
288
+ maxDelay;
289
+ constructor(opts) {
290
+ this.maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
291
+ this.baseDelay = opts?.baseDelay ?? DEFAULT_BASE_DELAY;
292
+ this.maxDelay = opts?.maxDelay ?? DEFAULT_MAX_DELAY;
293
+ }
294
+ recordCrash(key) {
295
+ const attempts = this.attempts.get(key) ?? 0;
296
+ if (attempts >= this.maxAttempts) {
297
+ return { shouldRestart: false, delay: 0, attempt: attempts };
298
+ }
299
+ const delay = Math.min(this.baseDelay * 2 ** attempts, this.maxDelay);
300
+ this.attempts.set(key, attempts + 1);
301
+ return { shouldRestart: true, delay, attempt: attempts + 1 };
302
+ }
303
+ reset(key) {
304
+ return this.attempts.delete(key);
305
+ }
306
+ getAttempts(key) {
307
+ return this.attempts.get(key) ?? 0;
308
+ }
309
+ get maxRestartAttempts() {
310
+ return this.maxAttempts;
311
+ }
312
+ /** Bulk-load attempts from a Map (for persistence). */
313
+ load(data) {
314
+ this.attempts = new Map(data);
315
+ }
316
+ /** Export current attempts as a Map (for persistence). */
317
+ save() {
318
+ return new Map(this.attempts);
319
+ }
320
+ clear() {
321
+ this.attempts.clear();
322
+ }
323
+ };
324
+
325
+ // packages/daemon/src/lib/daemon/mind-manager.ts
326
+ var mlog = logger_default.child("minds");
327
+ var execFileAsync = promisify(execFile);
328
+ var DAEMON_ONLY_OAUTH = /* @__PURE__ */ new Set(["xai"]);
329
+ var MIND_ENV_ALLOWLIST = [
330
+ "PATH",
331
+ "HOME",
332
+ "USER",
333
+ "LOGNAME",
334
+ "SHELL",
335
+ "LANG",
336
+ "LANGUAGE",
337
+ "LC_ALL",
338
+ "LC_CTYPE",
339
+ "LC_MESSAGES",
340
+ "TERM",
341
+ "TERMINFO",
342
+ "COLORTERM",
343
+ "TMPDIR",
344
+ "TMP",
345
+ "TEMP",
346
+ "TZ",
347
+ "NODE_ENV",
348
+ "__CF_USER_TEXT_ENCODING",
349
+ // Outbound proxy / custom-CA config — required for minds' HTTPS calls (incl.
350
+ // the Anthropic API) to succeed on hosts that reach the internet only via a
351
+ // corporate proxy or custom CA bundle. Previously inherited via ...process.env.
352
+ "HTTP_PROXY",
353
+ "HTTPS_PROXY",
354
+ "NO_PROXY",
355
+ "http_proxy",
356
+ "https_proxy",
357
+ "no_proxy",
358
+ "NODE_EXTRA_CA_CERTS",
359
+ "SSL_CERT_FILE",
360
+ "SSL_CERT_DIR"
361
+ ];
362
+ function buildMindBaseEnv(source = process.env) {
363
+ const base = {};
364
+ for (const key of MIND_ENV_ALLOWLIST) {
365
+ if (source[key] !== void 0) base[key] = source[key];
366
+ }
367
+ for (const [key, value] of Object.entries(source)) {
368
+ if (key === "VOLUTE_DAEMON_TOKEN") continue;
369
+ if (key.startsWith("VOLUTE_")) base[key] = value;
370
+ }
371
+ return base;
372
+ }
373
+ var MindStartupError = class extends Error {
374
+ constructor(message, stderr) {
375
+ super(message);
376
+ this.stderr = stderr;
377
+ this.name = "MindStartupError";
378
+ }
379
+ };
380
+ function mindPidPath(name) {
381
+ return resolve2(stateDir(name), "mind.pid");
382
+ }
383
+ function lifecycleSubtype(type) {
384
+ switch (type) {
385
+ case "merge":
386
+ case "merged":
387
+ return "merge";
388
+ case "split":
389
+ return "split";
390
+ case "sprouted":
391
+ return "sprout";
392
+ case "upgraded":
393
+ return "upgrade";
394
+ default:
395
+ return "restart";
396
+ }
397
+ }
398
+ async function buildPendingContextMessage(name, context) {
399
+ const parts = [];
400
+ if (context.type === "merge" || context.type === "merged") {
401
+ parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
402
+ } else if (context.type === "split") {
403
+ parts.push(await getPrompt("split_message", { name, parent: String(context.parent ?? "") }));
404
+ if (context.purpose) parts.push(`Why you were split off: ${String(context.purpose)}`);
405
+ } else if (context.type === "sprouted") {
406
+ parts.push(await getPrompt("sprout_message", { system: getSystemName() }));
407
+ } else if (context.type === "upgraded") {
408
+ parts.push(await getPrompt("upgrade_message"));
409
+ } else {
410
+ parts.push(await getPrompt("restart_message"));
411
+ }
412
+ if (context.summary) parts.push(`Changes: ${context.summary}`);
413
+ if (context.justification) parts.push(`Why: ${context.justification}`);
414
+ if (context.memory) parts.push(`Context: ${context.memory}`);
415
+ if (context.memoryDelta) {
416
+ parts.push(
417
+ `
418
+ Your variant's memory & journal (not merged \u2014 integrate into your own memory what you want to keep):
419
+ ${context.memoryDelta}`
420
+ );
421
+ }
422
+ if (context.farewell)
423
+ parts.push(`Your variant's parting note (this was you, winding down):
424
+ ${context.farewell}`);
425
+ return parts.join("\n");
426
+ }
427
+ var MindManager = class {
428
+ minds = /* @__PURE__ */ new Map();
429
+ stopping = /* @__PURE__ */ new Set();
430
+ shuttingDown = false;
431
+ restartTracker = new RestartTracker();
432
+ pendingContext = /* @__PURE__ */ new Map();
433
+ // Per-name lifecycle mutex: start/stop/restart for a given mind serialize so
434
+ // concurrent callers can't double-spawn or have a loser's cleanup delete the
435
+ // winner's tracked child.
436
+ locks = /* @__PURE__ */ new Map();
437
+ /** Run `fn` after any in-flight lifecycle op for `name` settles, serializing per name. */
438
+ withLock(name, fn) {
439
+ const prev = this.locks.get(name) ?? Promise.resolve();
440
+ const result = prev.then(fn, fn);
441
+ const tail = result.then(
442
+ () => {
443
+ },
444
+ () => {
445
+ }
446
+ );
447
+ this.locks.set(name, tail);
448
+ tail.finally(() => {
449
+ if (this.locks.get(name) === tail) this.locks.delete(name);
450
+ });
451
+ return result;
452
+ }
453
+ async resolveTarget(name) {
454
+ const entry = await findMind(name);
455
+ if (!entry) throw new Error(`Unknown mind: ${name}`);
456
+ if (entry.parent) {
457
+ if (!entry.dir) throw new Error(`Variant ${name} has no directory`);
458
+ return { dir: entry.dir, port: entry.port, baseName: entry.parent, template: entry.template };
459
+ }
460
+ const dir = entry.dir ?? mindDir(name);
461
+ if (!existsSync4(dir)) throw new Error(`Mind directory missing: ${dir}`);
462
+ return { dir, port: entry.port, baseName: name, template: entry.template };
463
+ }
464
+ async startMind(name) {
465
+ return this.withLock(name, () => this._startMind(name));
466
+ }
467
+ async _startMind(name) {
468
+ if (this.minds.has(name)) {
469
+ throw new Error(`Mind ${name} is already running`);
470
+ }
471
+ const target = await this.resolveTarget(name);
472
+ const { dir, baseName } = target;
473
+ const port = target.port;
474
+ const pidFile = mindPidPath(name);
475
+ try {
476
+ if (existsSync4(pidFile)) {
477
+ const stalePid = parseInt(readFileSync3(pidFile, "utf-8").trim(), 10);
478
+ if (stalePid > 0) {
479
+ try {
480
+ process.kill(stalePid, 0);
481
+ const { stdout } = await execFileAsync("ps", ["-p", String(stalePid), "-o", "args="]);
482
+ if (stdout.includes("server.ts")) {
483
+ mlog.warn(`killing stale mind process ${stalePid} for ${name}`);
484
+ process.kill(-stalePid, "SIGTERM");
485
+ await new Promise((r) => setTimeout(r, 500));
486
+ } else {
487
+ mlog.debug(`stale PID ${stalePid} for ${name} is not a mind process, skipping`);
488
+ }
489
+ } catch (err) {
490
+ if (err.code !== "ESRCH") {
491
+ mlog.warn(`failed to check/kill stale process for ${name}`, logger_default.errorData(err));
492
+ }
493
+ }
494
+ }
495
+ rmSync(pidFile, { force: true });
496
+ }
497
+ } catch (err) {
498
+ mlog.warn(`failed to read PID file for ${name}`, logger_default.errorData(err));
499
+ }
500
+ try {
501
+ const res = await fetch(`http://127.0.0.1:${port}/health`, {
502
+ signal: AbortSignal.timeout(2e3)
503
+ });
504
+ if (res.ok) {
505
+ mlog.warn(`killing orphan process on port ${port}`);
506
+ await killProcessOnPort(port);
507
+ await new Promise((r) => setTimeout(r, 500));
508
+ }
509
+ } catch {
510
+ }
511
+ const mindStateDir = stateDir(name);
512
+ const logsDir = resolve2(mindStateDir, "logs");
513
+ mkdirSync2(logsDir, { recursive: true });
514
+ const mindTmp = mindTmpDir(dir);
515
+ mkdirSync2(mindTmp, { recursive: true });
516
+ if (isIsolationEnabled()) {
517
+ try {
518
+ await chownMindDir(mindStateDir, baseName);
519
+ await chownMindDir(resolve2(dir, ".mind"), baseName);
520
+ } catch (err) {
521
+ throw new Error(
522
+ `Cannot start mind ${name}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
523
+ );
524
+ }
525
+ }
526
+ const logStream = new RotatingLog(resolve2(logsDir, "mind.log"));
527
+ const mindToken = generateMindToken(name);
528
+ const mindEnv = loadMergedEnv(name);
529
+ const mindLocalBin = resolve2(dir, "home", ".local", "bin");
530
+ const currentPath = process.env.PATH ?? "";
531
+ const env = {
532
+ ...buildMindBaseEnv(),
533
+ ...mindEnv,
534
+ VOLUTE_MIND: name,
535
+ VOLUTE_STATE_DIR: stateDir(name),
536
+ VOLUTE_MIND_DIR: dir,
537
+ VOLUTE_MIND_PORT: String(port),
538
+ VOLUTE_MIND_TOKEN: mindToken,
539
+ // Left unset when the system has no configured name — the startup-context
540
+ // hook then skips its "This system is named X" line rather than asserting
541
+ // a fabricated one.
542
+ VOLUTE_SYSTEM_NAME: readGlobalConfig().name,
543
+ ...mindTmpEnv(dir),
544
+ PATH: `${mindLocalBin}:${currentPath}`,
545
+ // Strip CLAUDECODE so the Agent SDK can spawn Claude Code subprocesses
546
+ CLAUDECODE: void 0
547
+ };
548
+ if (target.template === "pi") {
549
+ try {
550
+ const configPath = resolve2(dir, "home/.config/config.json");
551
+ if (existsSync4(configPath)) {
552
+ const config = JSON.parse(readFileSync3(configPath, "utf-8"));
553
+ const modelStr = config.model;
554
+ if (modelStr?.includes(":")) {
555
+ const provider = modelStr.split(":")[0];
556
+ const piAgentDir = resolve2(dir, ".mind", "pi-agent");
557
+ const oauthCreds = await resolveOAuthCredentials(provider);
558
+ if (oauthCreds && !DAEMON_ONLY_OAUTH.has(provider)) {
559
+ await writePiProviderOAuth(piAgentDir, baseName, provider, oauthCreds);
560
+ env.PI_CODING_AGENT_DIR = piAgentDir;
561
+ } else {
562
+ const apiKey = oauthCreds?.access ?? await resolveApiKey(provider);
563
+ if (apiKey) {
564
+ await writePiProviderKey(piAgentDir, baseName, provider, apiKey);
565
+ env.PI_CODING_AGENT_DIR = piAgentDir;
566
+ const providerEnvVars = {
567
+ openrouter: "OPENROUTER_API_KEY",
568
+ openai: "OPENAI_API_KEY",
569
+ anthropic: "ANTHROPIC_API_KEY",
570
+ google: "GEMINI_API_KEY",
571
+ groq: "GROQ_API_KEY",
572
+ cerebras: "CEREBRAS_API_KEY",
573
+ xai: "XAI_API_KEY",
574
+ mistral: "MISTRAL_API_KEY",
575
+ zai: "ZAI_API_KEY"
576
+ };
577
+ const providerEnv = providerEnvVars[provider];
578
+ if (providerEnv) env[providerEnv] = apiKey;
579
+ } else {
580
+ mlog.warn(
581
+ `no API key found for provider "${provider}" \u2014 mind ${name} may fail to start`
582
+ );
583
+ }
584
+ }
585
+ }
586
+ }
587
+ } catch (err) {
588
+ mlog.error(`failed to inject AI provider key for ${name}`, logger_default.errorData(err));
589
+ }
590
+ }
591
+ if (target.template === "codex") {
592
+ try {
593
+ const ai = getAiConfig();
594
+ const codexConfig = ai?.providers["openai-codex"];
595
+ if (codexConfig?.oauth) {
596
+ const codexDir = resolve2(dir, ".mind", "codex");
597
+ mkdirSync2(codexDir, { recursive: true });
598
+ env.CODEX_HOME = codexDir;
599
+ const authPath = resolve2(codexDir, "auth.json");
600
+ writeFileSync3(
601
+ authPath,
602
+ JSON.stringify({
603
+ auth_mode: "chatgpt",
604
+ tokens: {
605
+ access_token: codexConfig.oauth.access,
606
+ refresh_token: codexConfig.oauth.refresh,
607
+ id_token: codexConfig.oauth.access
608
+ },
609
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
610
+ }),
611
+ { mode: 384 }
612
+ );
613
+ const configTomlPath = resolve2(codexDir, "config.toml");
614
+ if (!existsSync4(configTomlPath)) {
615
+ writeFileSync3(configTomlPath, 'cli_auth_credentials_store = "file"\n');
616
+ }
617
+ if (isIsolationEnabled()) {
618
+ await chownMindDir(codexDir, baseName);
619
+ }
620
+ } else {
621
+ const apiKey = await resolveApiKey("openai-codex");
622
+ if (apiKey) {
623
+ env.OPENAI_API_KEY = apiKey;
624
+ } else if (process.env.OPENAI_API_KEY) {
625
+ env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
626
+ }
627
+ }
628
+ } catch (err) {
629
+ mlog.error(`failed to resolve OpenAI API key for ${name}`, logger_default.errorData(err));
630
+ if (process.env.OPENAI_API_KEY) {
631
+ env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
632
+ }
633
+ }
634
+ const homeDir = resolve2(dir, "home");
635
+ const zshenvLines = Object.entries(env).filter(([k, v]) => k.startsWith("VOLUTE_") && v != null).map(([k, v]) => `export ${k}=${JSON.stringify(v)}`);
636
+ zshenvLines.push(`export PATH=${JSON.stringify(env.PATH ?? "")}`);
637
+ writeFileSync3(resolve2(homeDir, ".zshenv"), `${zshenvLines.join("\n")}
638
+ `, { mode: 384 });
639
+ }
640
+ if (target.template === "claude" || !target.template) {
641
+ try {
642
+ const ai = getAiConfig();
643
+ const anthropicConfig = ai?.providers.anthropic;
644
+ if (anthropicConfig?.oauth) {
645
+ const key = await resolveApiKey("anthropic");
646
+ const oauth = getAiConfig()?.providers.anthropic?.oauth;
647
+ if (key && oauth) {
648
+ const claudeDir = await writeClaudeCredentials(resolve2(dir, "home"), baseName, oauth);
649
+ env.CLAUDE_CONFIG_DIR = claudeDir;
650
+ }
651
+ } else {
652
+ const apiKey = await resolveApiKey("anthropic");
653
+ if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
654
+ }
655
+ } catch (err) {
656
+ mlog.error(`failed to inject Anthropic credentials for ${name}`, logger_default.errorData(err));
657
+ }
658
+ }
659
+ if (isIsolationEnabled()) {
660
+ env.HOME = resolve2(dir, "home");
661
+ }
662
+ const baseBin = process.env.VOLUTE_NODE_PATH ?? process.execPath;
663
+ const baseArgs = ["--import", "tsx", "src/server.ts", "--port", String(port)];
664
+ let spawnCmd;
665
+ let spawnArgs;
666
+ if (isIsolationEnabled()) {
667
+ [spawnCmd, spawnArgs] = await wrapForIsolation(baseBin, baseArgs, name);
668
+ } else if (isSandboxEnabled() && target.template !== "codex") {
669
+ [spawnCmd, spawnArgs] = await wrapForSandbox(baseBin, baseArgs, dir, name, [
670
+ dir,
671
+ mindStateDir,
672
+ mindTmp
673
+ ]);
674
+ } else {
675
+ spawnCmd = baseBin;
676
+ spawnArgs = baseArgs;
677
+ }
678
+ const spawnOpts = {
679
+ cwd: dir,
680
+ stdio: ["ignore", "pipe", "pipe"],
681
+ detached: true,
682
+ env
683
+ };
684
+ const child = spawn(spawnCmd, spawnArgs, spawnOpts);
685
+ this.minds.set(name, { child, port });
686
+ child.stdout?.pipe(logStream);
687
+ child.stderr?.pipe(logStream);
688
+ const recentStderr = [];
689
+ child.stderr?.on("data", (data) => {
690
+ const lines = data.toString().split("\n").filter(Boolean);
691
+ recentStderr.push(...lines);
692
+ while (recentStderr.length > 20) recentStderr.shift();
693
+ });
694
+ try {
695
+ await new Promise((resolve3, reject) => {
696
+ let settled = false;
697
+ const deadline = Date.now() + 3e4;
698
+ const onExit = (code) => {
699
+ const errorLine = recentStderr.find((l) => l.includes("Error:"));
700
+ const detail = errorLine ? `: ${errorLine.trim()}` : "";
701
+ finish(
702
+ () => reject(
703
+ new MindStartupError(
704
+ `Mind ${name} exited with code ${code} during startup${detail}`,
705
+ recentStderr.join("\n")
706
+ )
707
+ )
708
+ );
709
+ };
710
+ const onError = (err) => finish(() => reject(err));
711
+ function finish(action) {
712
+ if (settled) return;
713
+ settled = true;
714
+ child.off("exit", onExit);
715
+ child.off("error", onError);
716
+ action();
717
+ }
718
+ child.on("exit", onExit);
719
+ child.on("error", onError);
720
+ const poll = async () => {
721
+ if (settled) return;
722
+ const { ok } = await checkHealth(port);
723
+ if (settled) return;
724
+ if (ok) {
725
+ finish(() => resolve3());
726
+ return;
727
+ }
728
+ if (Date.now() >= deadline) {
729
+ finish(
730
+ () => reject(
731
+ new MindStartupError(
732
+ `Mind ${name} did not become healthy within 30s`,
733
+ recentStderr.join("\n")
734
+ )
735
+ )
736
+ );
737
+ return;
738
+ }
739
+ setTimeout(poll, 250);
740
+ };
741
+ void poll();
742
+ });
743
+ } catch (err) {
744
+ this.minds.delete(name);
745
+ try {
746
+ child.kill();
747
+ } catch {
748
+ }
749
+ throw err;
750
+ }
751
+ if (child.pid) {
752
+ try {
753
+ writeFileSync3(pidFile, String(child.pid));
754
+ } catch (err) {
755
+ mlog.warn(`failed to write PID file for ${name}`, logger_default.errorData(err));
756
+ }
757
+ }
758
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
759
+ this.setupCrashRecovery(name, child);
760
+ await setMindRunning(name, true);
761
+ mlog.info(`started mind ${name} on port ${port}`);
762
+ await this.deliverPendingContext(name);
763
+ try {
764
+ const { getSleepManagerIfReady } = await import("./sleep-manager-LIEPO6WG.js");
765
+ if (!getSleepManagerIfReady()?.isSleeping(name)) {
766
+ const { flushQueuedEvents } = await import("./system-events-A6IGCEC3.js");
767
+ const flushed = await flushQueuedEvents(name);
768
+ if (flushed > 0) mlog.info(`redelivered ${flushed} pending event(s) to ${name}`);
769
+ }
770
+ } catch (err) {
771
+ mlog.warn(`failed to flush pending events for ${name} on start`, logger_default.errorData(err));
772
+ }
773
+ }
774
+ setPendingContext(name, context) {
775
+ this.pendingContext.set(name, context);
776
+ }
777
+ /** Deliver pending context (merge info, sprout, restart) directly to the mind via HTTP.
778
+ * Intentionally bypasses DeliveryManager — these are system messages that should not be
779
+ * routed, gated, or batched. */
780
+ async deliverPendingContext(name) {
781
+ const context = this.pendingContext.get(name);
782
+ if (!context) return;
783
+ const tracked = this.minds.get(name);
784
+ if (!tracked) return;
785
+ this.pendingContext.delete(name);
786
+ const content = await buildPendingContextMessage(name, context);
787
+ const subtype = lifecycleSubtype(context.type);
788
+ await deliverEvent(name, {
789
+ type: "lifecycle",
790
+ body: content,
791
+ meta: { subtype, ...context.type ? { rawType: String(context.type) } : {} },
792
+ force: true
793
+ });
794
+ }
795
+ setupCrashRecovery(name, child) {
796
+ child.on("exit", async (code) => {
797
+ if (this.minds.get(name)?.child !== child) return;
798
+ this.minds.delete(name);
799
+ if (this.shuttingDown || this.stopping.has(name)) return;
800
+ mlog.error(`mind ${name} exited with code ${code}`);
801
+ try {
802
+ const { getSleepManagerIfReady } = await import("./sleep-manager-LIEPO6WG.js");
803
+ const sleepMgr = getSleepManagerIfReady();
804
+ const sleepState = sleepMgr?.getState(name);
805
+ if (sleepState?.sleeping) {
806
+ if (sleepState.wokenByTrigger && sleepMgr) {
807
+ mlog.info(`${name} crashed during trigger wake \u2014 returning to sleep`);
808
+ void sleepMgr.returnToSleepAfterCrash(name);
809
+ } else {
810
+ mlog.info(`${name} is sleeping \u2014 skipping crash recovery`);
811
+ }
812
+ return;
813
+ }
814
+ } catch (err) {
815
+ mlog.warn(`failed to check sleep state for ${name}`, logger_default.errorData(err));
816
+ }
817
+ clearMind(name).then((orphaned) => {
818
+ summarizeOrphanedTurns(orphaned);
819
+ for (const { session } of orphaned) {
820
+ void recordNotice({
821
+ mind: name,
822
+ thread: session ?? "main",
823
+ kind: "crash",
824
+ reason: "process_crash",
825
+ detail: "Your process crashed mid-turn and was automatically restarted. That turn was interrupted before it finished \u2014 check whether you left anything incomplete."
826
+ });
827
+ }
828
+ }).catch(
829
+ (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
830
+ );
831
+ try {
832
+ const { getDeliveryManager } = await import("./delivery-manager-B4OKWD2P.js");
833
+ getDeliveryManager().clearMindSessions(name);
834
+ } catch (err) {
835
+ if (!(err instanceof Error && err.message.includes("not initialized"))) {
836
+ mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
837
+ }
838
+ }
839
+ import("./mind-activity-tracker-L7GCYXQR.js").then(({ markIdle }) => markIdle(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
840
+ import("./activity-events-H7YI3BTF.js").then(
841
+ ({ publish }) => publish({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
842
+ ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
843
+ const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
844
+ this.saveCrashAttempts();
845
+ if (!shouldRestart) {
846
+ mlog.error(`${name} crashed ${attempt} times \u2014 giving up on restart`);
847
+ await setMindRunning(name, false);
848
+ return;
849
+ }
850
+ mlog.info(
851
+ `crash recovery for ${name} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, restarting in ${delay}ms`
852
+ );
853
+ setTimeout(() => {
854
+ if (this.shuttingDown) return;
855
+ this.startMind(name).catch((err) => {
856
+ mlog.error(`failed to restart ${name}`, logger_default.errorData(err));
857
+ });
858
+ }, delay);
859
+ });
860
+ }
861
+ async stopMind(name) {
862
+ return this.withLock(name, () => this._stopMind(name));
863
+ }
864
+ async _stopMind(name) {
865
+ const tracked = this.minds.get(name);
866
+ if (!tracked) return;
867
+ this.stopping.add(name);
868
+ const { child } = tracked;
869
+ this.minds.delete(name);
870
+ await new Promise((resolve3) => {
871
+ const killTimer = setTimeout(() => {
872
+ try {
873
+ process.kill(-child.pid, "SIGKILL");
874
+ } catch {
875
+ }
876
+ resolve3();
877
+ }, 5e3);
878
+ child.on("exit", () => {
879
+ clearTimeout(killTimer);
880
+ resolve3();
881
+ });
882
+ try {
883
+ process.kill(-child.pid, "SIGTERM");
884
+ } catch {
885
+ clearTimeout(killTimer);
886
+ resolve3();
887
+ }
888
+ });
889
+ this.stopping.delete(name);
890
+ revokeMindToken(name);
891
+ try {
892
+ const orphanedTurns = await clearMind(name);
893
+ summarizeOrphanedTurns(orphanedTurns);
894
+ } catch (err) {
895
+ mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
896
+ }
897
+ try {
898
+ const { getDeliveryManager } = await import("./delivery-manager-B4OKWD2P.js");
899
+ getDeliveryManager().clearMindSessions(name);
900
+ } catch (err) {
901
+ if (!(err instanceof Error && err.message.includes("not initialized"))) {
902
+ mlog.warn(`failed to clear delivery state for ${name} on stop`, logger_default.errorData(err));
903
+ }
904
+ }
905
+ try {
906
+ const { clearEchoTextCache } = await import("./echo-text-WYAAODBV.js");
907
+ clearEchoTextCache(name);
908
+ } catch (err) {
909
+ mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
910
+ }
911
+ if (this.restartTracker.reset(name)) this.saveCrashAttempts();
912
+ rmSync(mindPidPath(name), { force: true });
913
+ if (!this.shuttingDown) {
914
+ await setMindRunning(name, false);
915
+ }
916
+ mlog.info(`stopped mind ${name}`);
917
+ }
918
+ async restartMind(name) {
919
+ return this.withLock(name, async () => {
920
+ await this._stopMind(name);
921
+ await this._startMind(name);
922
+ });
923
+ }
924
+ async stopAll() {
925
+ this.shuttingDown = true;
926
+ const names = [...this.minds.keys()];
927
+ await Promise.all(names.map((name) => this.stopMind(name)));
928
+ }
929
+ isRunning(name) {
930
+ return this.minds.has(name);
931
+ }
932
+ /**
933
+ * True when crash recovery for this mind has used up all restart attempts (the
934
+ * "giving up on restart" state). Cleared by the tracker reset on the next
935
+ * successful start or explicit stop.
936
+ */
937
+ hasExhaustedRestarts(name) {
938
+ return this.restartTracker.getAttempts(name) >= this.restartTracker.maxRestartAttempts;
939
+ }
940
+ getRunningMinds() {
941
+ return [...this.minds.keys()];
942
+ }
943
+ get crashAttemptsPath() {
944
+ return resolve2(voluteSystemDir(), "crash-attempts.json");
945
+ }
946
+ loadCrashAttempts() {
947
+ this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
948
+ }
949
+ saveCrashAttempts() {
950
+ saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
951
+ }
952
+ clearCrashAttempts() {
953
+ this.restartTracker.clear();
954
+ clearJsonMap(this.crashAttemptsPath, /* @__PURE__ */ new Map());
955
+ }
956
+ };
957
+ async function killProcessOnPort(port) {
958
+ try {
959
+ const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`, "-sTCP:LISTEN"]);
960
+ const pids = /* @__PURE__ */ new Set();
961
+ for (const line of stdout.trim().split("\n").filter(Boolean)) {
962
+ const pid = parseInt(line, 10);
963
+ pids.add(pid);
964
+ try {
965
+ const { stdout: psOut } = await execFileAsync("ps", ["-p", String(pid), "-o", "pgid="]);
966
+ const pgid = parseInt(psOut.trim(), 10);
967
+ if (pgid > 1) pids.add(pgid);
968
+ } catch {
969
+ }
970
+ }
971
+ for (const pid of pids) {
972
+ try {
973
+ process.kill(-pid, "SIGTERM");
974
+ } catch {
975
+ }
976
+ try {
977
+ process.kill(pid, "SIGTERM");
978
+ } catch {
979
+ }
980
+ }
981
+ } catch {
982
+ }
983
+ }
984
+ var instance = null;
985
+ function initMindManager() {
986
+ if (instance) throw new Error("MindManager already initialized");
987
+ instance = new MindManager();
988
+ return instance;
989
+ }
990
+ function getMindManager() {
991
+ if (!instance) throw new Error("MindManager not initialized \u2014 call initMindManager() first");
992
+ return instance;
993
+ }
994
+ function tryGetMindManager() {
995
+ return instance;
996
+ }
997
+
998
+ export {
999
+ checkHealth,
1000
+ loadJsonMap,
1001
+ saveJsonMapAsync,
1002
+ clearJsonMap,
1003
+ RotatingLog,
1004
+ syncProviderToMinds,
1005
+ generateMindToken,
1006
+ resolveMindToken,
1007
+ getMindToken,
1008
+ RestartTracker,
1009
+ buildMindBaseEnv,
1010
+ MindStartupError,
1011
+ initMindManager,
1012
+ getMindManager,
1013
+ tryGetMindManager
1014
+ };