volute 0.30.1 → 0.31.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 (169) hide show
  1. package/dist/{accept-E3PAH3QJ.js → accept-GAKQ3MEH.js} +4 -4
  2. package/dist/{activity-events-BKBPPUBP.js → activity-events-T5ZRCVAL.js} +2 -2
  3. package/dist/{ai-service-VAJT5UBS.js → ai-service-UWUPM4T6.js} +5 -3
  4. package/dist/api.d.ts +238 -111
  5. package/dist/{archive-WWDBWYN2.js → archive-YBNSJYZZ.js} +2 -2
  6. package/dist/auth-T5AW2USD.js +43 -0
  7. package/dist/{bridge-RO37CUFM.js → bridge-4AJ3EY26.js} +4 -4
  8. package/dist/{chat-TCUNPFGO.js → chat-7YLT7FI3.js} +8 -8
  9. package/dist/{chunk-EFVHR7KH.js → chunk-4OUOFS23.js} +24 -5
  10. package/dist/{chunk-G3GBKZGG.js → chunk-57OKQMP3.js} +54 -2
  11. package/dist/chunk-6QIUN46C.js +38 -0
  12. package/dist/{chunk-FSM45XD5.js → chunk-AAO77TZX.js} +1 -1
  13. package/dist/{chunk-DTC6EH5I.js → chunk-BC3P3QCK.js} +1 -1
  14. package/dist/{chunk-P7VFDSSG.js → chunk-BNC43CSY.js} +2 -2
  15. package/dist/{chunk-QVAQ5454.js → chunk-BWKIHH7B.js} +3080 -2099
  16. package/dist/{chunk-IKHDUZRH.js → chunk-DAXJKPHZ.js} +2 -2
  17. package/dist/{chunk-P27RV5WM.js → chunk-EKDWA7E4.js} +3 -1
  18. package/dist/{chunk-S5LR3XYJ.js → chunk-EMPFLFTG.js} +1 -1
  19. package/dist/{chunk-2C2VXEBB.js → chunk-FAHDKPEH.js} +18 -56
  20. package/dist/{chunk-W5OOPLNP.js → chunk-HDKY4TWU.js} +3 -3
  21. package/dist/{chunk-EFP3PE6C.js → chunk-HR5JKIDG.js} +2 -12
  22. package/dist/{chunk-JGFRDMR6.js → chunk-LX6T3GKQ.js} +1 -1
  23. package/dist/{chunk-2NDZC3S7.js → chunk-NOWVQ7AL.js} +447 -299
  24. package/dist/{chunk-ZWKTUQEL.js → chunk-NV3TYNWX.js} +1 -1
  25. package/dist/{chunk-NSBFETWP.js → chunk-PNQCXLSV.js} +7 -26
  26. package/dist/chunk-R5QJBZZG.js +175 -0
  27. package/dist/{chunk-MDPCSXZ4.js → chunk-S2TZLSDH.js} +4 -4
  28. package/dist/{chunk-FXHXHI2A.js → chunk-SNVPRRT7.js} +3 -6
  29. package/dist/{chunk-UPA6COHU.js → chunk-WRS3B556.js} +5 -5
  30. package/dist/{chunk-HHTXM4JT.js → chunk-X62AXPR7.js} +36 -4
  31. package/dist/cli.js +74 -23
  32. package/dist/{clock-G3ALCMLJ.js → clock-LJCG426D.js} +10 -8
  33. package/dist/{cloud-sync-JV4LJOK3.js → cloud-sync-O3LXIRN6.js} +13 -13
  34. package/dist/{conversations-7KVQV7EZ.js → conversations-RKKGP5IA.js} +7 -7
  35. package/dist/{create-VQSQHJQW.js → create-TL623TFC.js} +1 -1
  36. package/dist/{create-JTLS7GX3.js → create-WUTIIRI2.js} +4 -4
  37. package/dist/daemon-client-CVGM25DM.js +11 -0
  38. package/dist/{daemon-restart-4JGBHEJ4.js → daemon-restart-EZP7XH3V.js} +6 -7
  39. package/dist/daemon.js +1879 -1727
  40. package/dist/{db-HMFPIRO2.js → db-SW5PL6QA.js} +1 -1
  41. package/dist/{delete-JESHKE7F.js → delete-Z6HAG35F.js} +1 -1
  42. package/dist/down-TS4XQBA4.js +13 -0
  43. package/dist/{env-CLXXT7M2.js → env-NHESNNSP.js} +4 -4
  44. package/dist/{export-EGA5M5PB.js → export-EVMP7GWY.js} +3 -3
  45. package/dist/{extension-WZ4SUPJB.js → extension-LR7EW3JF.js} +5 -6
  46. package/dist/{extensions-ECO4RPFQ.js → extensions-NGEJI7JH.js} +7 -7
  47. package/dist/{files-4VEJDASH.js → files-3SM7V33S.js} +5 -5
  48. package/dist/{history-EJMMLXDO.js → history-PQD3LXEP.js} +4 -4
  49. package/dist/{import-YCGPMBSI.js → import-PR2OCGQJ.js} +3 -3
  50. package/dist/{join-2GBJKZEN.js → join-R4EN5CWQ.js} +1 -1
  51. package/dist/{list-Q6O7FGAN.js → list-B4XNUOFO.js} +4 -4
  52. package/dist/{login-RL6AU2SM.js → login-62JVY6A2.js} +4 -4
  53. package/dist/{login-RET5WESK.js → login-URWP6S2N.js} +2 -2
  54. package/dist/{logout-CGAGJN3L.js → logout-NXJQJDLI.js} +2 -2
  55. package/dist/{logout-JRPBEMMR.js → logout-ZK2N62T3.js} +2 -2
  56. package/dist/message-delivery-UJHCLVU4.js +30 -0
  57. package/dist/{mind-LUWRQUQ5.js → mind-E2ZV2WRX.js} +17 -17
  58. package/dist/{mind-activity-tracker-VYN2ZZ2M.js → mind-activity-tracker-ASNZBMLC.js} +3 -3
  59. package/dist/{mind-list-V5WW5DUA.js → mind-list-BEI7E5WY.js} +2 -2
  60. package/dist/mind-manager-IPA6DZXD.js +26 -0
  61. package/dist/{mind-sleep-R6PTNNW4.js → mind-sleep-CANABWJI.js} +4 -4
  62. package/dist/{mind-status-I4ISFJ6I.js → mind-status-6WKZVUOP.js} +2 -2
  63. package/dist/{mind-wake-67ZQEWAV.js → mind-wake-RZKLH2IN.js} +4 -4
  64. package/dist/{package-OYUD4ZJ4.js → package-NU4CA7OU.js} +3 -3
  65. package/dist/{pages-watcher-Z3PKNROC.js → pages-watcher-72OVPRMH.js} +4 -3
  66. package/dist/read-THL362EI.js +74 -0
  67. package/dist/{register-NZDSTLP3.js → register-QAQELAS6.js} +4 -4
  68. package/dist/{registry-ODSALQQL.js → registry-ASXCQCNH.js} +1 -1
  69. package/dist/{reject-2HZOJEIJ.js → reject-AYPBNPNL.js} +4 -4
  70. package/dist/{restart-QHS3NT64.js → restart-6SKPV3T2.js} +4 -4
  71. package/dist/{sandbox-O5FUSF43.js → sandbox-6ZEWQDVU.js} +3 -3
  72. package/dist/{seed-WUQMPLDM.js → seed-OWX2AW75.js} +36 -12
  73. package/dist/{send-OAN3RYYY.js → send-ZO4BTWXK.js} +5 -5
  74. package/dist/{setup-QMDK5RZX.js → setup-7CFITEQN.js} +2 -4
  75. package/dist/{setup-XJH3E7YM.js → setup-ZXBXG7E4.js} +6 -8
  76. package/dist/{skill-FZIN4W4Q.js → skill-YFXP67A2.js} +4 -4
  77. package/dist/skills/dreaming/references/INSTALL.md +2 -3
  78. package/dist/skills/dreaming/scripts/dream.ts +3 -25
  79. package/dist/skills/volute-mind/SKILL.md +1 -1
  80. package/dist/sleep-manager-TPS6OGCA.js +30 -0
  81. package/dist/{split-EXYGGGQN.js → split-MI62KJUU.js} +1 -1
  82. package/dist/{sprout-AXQ6H5DB.js → sprout-FDVI2CGN.js} +5 -6
  83. package/dist/{start-MTOVL6SY.js → start-D64BRKPH.js} +4 -4
  84. package/dist/{status-ZRO37MWR.js → status-ZZWBYFGE.js} +4 -5
  85. package/dist/{stop-OK5WEPVC.js → stop-OP2CTXCO.js} +4 -4
  86. package/dist/system-chat-B43GIXQU.js +30 -0
  87. package/dist/{systems-W3BBMSOZ.js → systems-EQPPT4B7.js} +5 -5
  88. package/dist/{tailscale-BM72RXCJ.js → tailscale-6DJKUMNF.js} +1 -1
  89. package/dist/up-TDXEP3VA.js +16 -0
  90. package/dist/{update-PLPHMMZ2.js → update-KUJXATRS.js} +4 -5
  91. package/dist/{update-check-CVCN7MF6.js → update-check-5WVSU37T.js} +2 -2
  92. package/dist/{upgrade-I6NPCYUU.js → upgrade-KBHCWX6T.js} +1 -1
  93. package/dist/{version-notify-2NTWVEHL.js → version-notify-75ELVKPV.js} +17 -21
  94. package/dist/web-assets/assets/index-BM1cTzBg.js +72 -0
  95. package/dist/web-assets/assets/index-BfJkKTPF.css +1 -0
  96. package/dist/web-assets/ext-theme.css +1 -0
  97. package/dist/web-assets/index.html +2 -2
  98. package/drizzle/0000_baseline.sql +152 -0
  99. package/drizzle/0001_add_conversation_private.sql +1 -0
  100. package/drizzle/0002_turns.sql +21 -0
  101. package/drizzle/0003_turn_feed_links.sql +11 -0
  102. package/drizzle/meta/0000_snapshot.json +3 -223
  103. package/drizzle/meta/0001_snapshot.json +3 -294
  104. package/drizzle/meta/0002_snapshot.json +3 -335
  105. package/drizzle/meta/0003_snapshot.json +3 -413
  106. package/drizzle/meta/_journal.json +8 -106
  107. package/package.json +3 -3
  108. package/packages/extensions/notes/dist/ui/assets/{index-DgawVO5g.css → index-B8GdTnXs.css} +1 -1
  109. package/packages/extensions/notes/dist/ui/assets/index-CDpGTCWb.js +2 -0
  110. package/packages/extensions/notes/dist/ui/index.html +2 -2
  111. package/packages/extensions/notes/skills/notes/SKILL.md +8 -8
  112. package/packages/extensions/pages/skills/pages/SKILL.md +7 -4
  113. package/packages/extensions/pages/skills/pages/scripts/pages.mjs +58 -0
  114. package/templates/_base/.init/.config/bin/volute +27 -0
  115. package/templates/_base/src/lib/auto-commit.ts +82 -43
  116. package/templates/_base/src/lib/daemon-client.ts +19 -23
  117. package/templates/_base/src/lib/router.ts +17 -1
  118. package/templates/_base/src/lib/startup.ts +6 -8
  119. package/templates/_base/src/lib/volute-server.ts +2 -5
  120. package/templates/claude/src/agent.ts +2 -1
  121. package/templates/claude/src/lib/hooks/auto-commit.ts +7 -3
  122. package/templates/claude/src/server.ts +0 -9
  123. package/templates/pi/package.json.tmpl +1 -1
  124. package/templates/pi/src/agent.ts +1 -1
  125. package/templates/pi/src/lib/event-handler.ts +5 -3
  126. package/dist/chunk-7D47T4RB.js +0 -84
  127. package/dist/chunk-CVH6Y2YG.js +0 -59
  128. package/dist/chunk-LIRWLNAK.js +0 -729
  129. package/dist/daemon-client-BCTFGVCZ.js +0 -9
  130. package/dist/down-NGBMGORS.js +0 -14
  131. package/dist/message-delivery-6YMVNOEC.js +0 -28
  132. package/dist/migrate-registry-to-db-FK35IPEH.js +0 -110
  133. package/dist/mind-manager-YFCOIAAX.js +0 -18
  134. package/dist/read-WQMPTSN2.js +0 -46
  135. package/dist/sleep-manager-O7YQFCV5.js +0 -30
  136. package/dist/up-BXUAIDXB.js +0 -17
  137. package/dist/web-assets/assets/index--kREqKl9.js +0 -72
  138. package/dist/web-assets/assets/index-BXYTG0nJ.css +0 -1
  139. package/drizzle/0000_flaky_mariko_yashida.sql +0 -34
  140. package/drizzle/0001_careless_warpath.sql +0 -12
  141. package/drizzle/0002_wealthy_the_call.sql +0 -6
  142. package/drizzle/0003_clean_ego.sql +0 -12
  143. package/drizzle/0004_magical_silverclaw.sql +0 -1
  144. package/drizzle/0005_rename_agents_to_minds.sql +0 -11
  145. package/drizzle/0006_mind_history.sql +0 -20
  146. package/drizzle/0007_system_prompts.sql +0 -5
  147. package/drizzle/0008_volute_channels.sql +0 -24
  148. package/drizzle/0009_shared_skills.sql +0 -9
  149. package/drizzle/0010_delivery_queue.sql +0 -12
  150. package/drizzle/0011_rename_human_to_brain.sql +0 -1
  151. package/drizzle/0012_activity.sql +0 -11
  152. package/drizzle/0013_user_profiles.sql +0 -3
  153. package/drizzle/0014_conversation_reads.sql +0 -7
  154. package/drizzle/0015_notes.sql +0 -23
  155. package/drizzle/0016_note_reactions_and_replies.sql +0 -15
  156. package/drizzle/0017_minds.sql +0 -16
  157. package/drizzle/meta/0004_snapshot.json +0 -410
  158. package/drizzle/meta/0005_snapshot.json +0 -410
  159. package/drizzle/meta/0006_snapshot.json +0 -7
  160. package/drizzle/meta/0007_snapshot.json +0 -7
  161. package/drizzle/meta/0008_snapshot.json +0 -7
  162. package/drizzle/meta/0009_snapshot.json +0 -7
  163. package/drizzle/meta/0010_snapshot.json +0 -7
  164. package/drizzle/meta/0011_snapshot.json +0 -7
  165. package/drizzle/meta/0012_snapshot.json +0 -7
  166. package/drizzle/meta/0013_snapshot.json +0 -7
  167. package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +0 -2
  168. package/packages/extensions/notes/skills/notes/scripts/notes.mjs +0 -185
  169. package/templates/_base/home/public/.gitkeep +0 -0
@@ -1,729 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- isSandboxEnabled,
4
- wrapForSandbox
5
- } from "./chunk-IKHDUZRH.js";
6
- import {
7
- loadMergedEnv
8
- } from "./chunk-ZWKTUQEL.js";
9
- import {
10
- logger_default
11
- } from "./chunk-YUIHSKR6.js";
12
- import {
13
- chownMindDir,
14
- isIsolationEnabled,
15
- wrapForIsolation
16
- } from "./chunk-G3GBKZGG.js";
17
- import {
18
- findMind,
19
- getDb,
20
- mindDir,
21
- mindHistory,
22
- setMindRunning,
23
- stateDir,
24
- systemPrompts,
25
- voluteSystemDir
26
- } from "./chunk-HHTXM4JT.js";
27
-
28
- // src/lib/daemon/mind-manager.ts
29
- import { execFile, spawn } from "child_process";
30
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
31
- import { resolve } from "path";
32
- import { promisify } from "util";
33
-
34
- // src/lib/json-state.ts
35
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
36
- function loadJsonMap(path) {
37
- const map = /* @__PURE__ */ new Map();
38
- try {
39
- if (existsSync(path)) {
40
- const data = JSON.parse(readFileSync(path, "utf-8"));
41
- for (const [key, value] of Object.entries(data)) {
42
- if (typeof value === "number") map.set(key, value);
43
- }
44
- }
45
- } catch (err) {
46
- console.warn(`[state] failed to load ${path}:`, err);
47
- }
48
- return map;
49
- }
50
- function saveJsonMap(path, map) {
51
- const data = {};
52
- for (const [key, value] of map) {
53
- data[key] = value;
54
- }
55
- try {
56
- writeFileSync(path, `${JSON.stringify(data)}
57
- `);
58
- } catch (err) {
59
- console.warn(`[state] failed to save ${path}:`, err);
60
- }
61
- }
62
- function clearJsonMap(path, map) {
63
- map.clear();
64
- try {
65
- if (existsSync(path)) unlinkSync(path);
66
- } catch (err) {
67
- console.warn(`[state] failed to clear ${path}:`, err);
68
- }
69
- }
70
-
71
- // src/lib/prompts.ts
72
- import { eq } from "drizzle-orm";
73
- var PROMPT_KEYS = [
74
- "seed_soul",
75
- "default_soul",
76
- "default_memory",
77
- "sprout_message",
78
- "restart_message",
79
- "merge_message",
80
- "compaction_warning",
81
- "compaction_instructions",
82
- "reply_instructions",
83
- "channel_invite",
84
- "pre_sleep",
85
- "wake_summary",
86
- "turn_summary"
87
- ];
88
- var PROMPT_DEFAULTS = {
89
- seed_soul: {
90
- content: `You are \${name}.
91
-
92
- You don't have a soul yet. You're a seed \u2014 a new mind in the process of discovering who you are.
93
- \${description}
94
- Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute mind sprout\` to complete the transformation.
95
- `,
96
- description: "SOUL.md for seed minds",
97
- variables: ["name", "description"],
98
- category: "creation"
99
- },
100
- default_soul: {
101
- content: `You are {{name}}.
102
- `,
103
- description: (
104
- // biome-ignore lint/suspicious/noTemplateCurlyInString: describing template syntax, not using it
105
- "SOUL.md for sprouted minds. Uses {{name}} placeholder (replaced at creation, not by ${var} system)"
106
- ),
107
- variables: ["name"],
108
- category: "creation"
109
- },
110
- default_memory: {
111
- content: "",
112
- description: "Initial MEMORY.md for new minds",
113
- variables: [],
114
- category: "creation"
115
- },
116
- sprout_message: {
117
- content: "[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details.",
118
- description: "Sent when a seed mind sprouts",
119
- variables: [],
120
- category: "system"
121
- },
122
- restart_message: {
123
- content: "[system] You have been restarted.",
124
- description: "Generic restart notification",
125
- variables: [],
126
- category: "system"
127
- },
128
- merge_message: {
129
- content: '[system] Variant "${name}" has been merged and you have been restarted.',
130
- description: "Variant merge notification",
131
- variables: ["name"],
132
- category: "system"
133
- },
134
- compaction_warning: {
135
- content: `Context is getting long \u2014 compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/\${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.`,
136
- description: "Pre-compaction save reminder sent to the mind",
137
- variables: ["date"],
138
- category: "mind"
139
- },
140
- compaction_instructions: {
141
- content: "Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
142
- description: "Custom instructions for the compaction summarizer",
143
- variables: [],
144
- category: "mind"
145
- },
146
- reply_instructions: {
147
- content: 'To reply to this message, use: volute chat send ${channel} "your message"',
148
- description: "First-message reply hint injected via hook",
149
- variables: ["channel"],
150
- category: "mind"
151
- },
152
- channel_invite: {
153
- content: `[Channel Invite]
154
- \${headers}
155
-
156
- [\${sender} \u2014 \${time}]
157
- \${preview}
158
-
159
- Further messages will be saved to \${filePath}
160
-
161
- To accept, add to .config/routes.json:
162
- Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
163
- \${batchRecommendation}To respond, use: volute chat send \${channel} "your message"
164
- To reject, delete \${filePath}`,
165
- description: "New channel notification template",
166
- variables: [
167
- "headers",
168
- "sender",
169
- "time",
170
- "preview",
171
- "filePath",
172
- "channel",
173
- "suggestedSession",
174
- "batchRecommendation"
175
- ],
176
- category: "mind"
177
- },
178
- pre_sleep: {
179
- content: "Time to rest. You have this turn to wind down however feels right \u2014 reflect on your day, update your journal or memory, finish any threads of thought, or simply settle.\n\nYour current session will be archived and a fresh one will begin when you wake. Anything in session context that isn't saved to files will be lost.\n\nYou'll wake at ${wakeTime}.",
180
- description: "Pre-sleep message sent before stopping the mind",
181
- variables: ["wakeTime"],
182
- category: "system"
183
- },
184
- wake_summary: {
185
- content: "Good morning \u2014 it's ${currentDate}. You slept from ${sleepTime} to now (${duration}).\n\n${sleepActivity}",
186
- description: "Wake-up summary after scheduled sleep",
187
- variables: ["currentDate", "sleepTime", "duration", "sleepActivity"],
188
- category: "system"
189
- },
190
- turn_summary: {
191
- content: "Summarize what this AI mind did in this turn in 1-2 concise sentences. Focus on what was accomplished, not the mechanics.",
192
- description: "System prompt for AI-generated turn summaries",
193
- variables: [],
194
- category: "system"
195
- }
196
- };
197
- function isValidKey(key) {
198
- return PROMPT_KEYS.includes(key);
199
- }
200
- function substitute(template, vars) {
201
- return template.replace(/\$\{(\w+)\}/g, (match, name) => {
202
- return name in vars ? vars[name] : match;
203
- });
204
- }
205
- async function getPrompt(key, vars) {
206
- if (!isValidKey(key)) return "";
207
- let content = PROMPT_DEFAULTS[key].content;
208
- try {
209
- const db = await getDb();
210
- const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
211
- if (row) content = row.content;
212
- } catch (err) {
213
- console.error(`[prompts] failed to read DB override for "${key}":`, err);
214
- }
215
- return vars ? substitute(content, vars) : content;
216
- }
217
- async function getPromptIfCustom(key) {
218
- if (!isValidKey(key)) return null;
219
- try {
220
- const db = await getDb();
221
- const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key)).get();
222
- return row?.content ?? null;
223
- } catch (err) {
224
- console.error(`[prompts] failed to check DB customization for "${key}":`, err);
225
- return null;
226
- }
227
- }
228
- var MIND_PROMPT_KEYS = PROMPT_KEYS.filter((k) => PROMPT_DEFAULTS[k].category === "mind");
229
- async function getMindPromptDefaults() {
230
- const result = {};
231
- for (const key of MIND_PROMPT_KEYS) {
232
- result[key] = PROMPT_DEFAULTS[key].content;
233
- }
234
- try {
235
- const db = await getDb();
236
- const rows = await db.select().from(systemPrompts).all();
237
- for (const row of rows) {
238
- if (MIND_PROMPT_KEYS.includes(row.key)) {
239
- result[row.key] = row.content;
240
- }
241
- }
242
- } catch (err) {
243
- console.error("[prompts] failed to read DB overrides for mind prompt defaults:", err);
244
- }
245
- return result;
246
- }
247
-
248
- // src/lib/rotating-log.ts
249
- import {
250
- createWriteStream,
251
- existsSync as existsSync2,
252
- renameSync,
253
- rmSync,
254
- statSync
255
- } from "fs";
256
- import { Writable } from "stream";
257
- var MAX_SIZE = 10 * 1024 * 1024;
258
- var RotatingLog = class extends Writable {
259
- constructor(path, maxSize = MAX_SIZE, maxFiles = 5) {
260
- super();
261
- this.path = path;
262
- this.maxSize = maxSize;
263
- this.maxFiles = maxFiles;
264
- this.on("error", () => {
265
- });
266
- try {
267
- this.size = existsSync2(path) ? statSync(path).size : 0;
268
- } catch {
269
- this.size = 0;
270
- }
271
- this.stream = createWriteStream(path, { flags: "a" });
272
- }
273
- stream;
274
- size;
275
- _write(chunk, _encoding, callback) {
276
- this.size += chunk.length;
277
- if (this.size > this.maxSize) {
278
- try {
279
- const oldest = `${this.path}.${this.maxFiles}`;
280
- if (existsSync2(oldest)) rmSync(oldest);
281
- for (let i = this.maxFiles - 1; i >= 1; i--) {
282
- const from = `${this.path}.${i}`;
283
- const to = `${this.path}.${i + 1}`;
284
- if (existsSync2(from)) renameSync(from, to);
285
- }
286
- renameSync(this.path, `${this.path}.1`);
287
- const oldStream = this.stream;
288
- this.stream = createWriteStream(this.path);
289
- this.size = chunk.length;
290
- oldStream.end();
291
- } catch {
292
- }
293
- }
294
- this.stream.write(chunk, callback);
295
- }
296
- _final(callback) {
297
- this.stream.end(callback);
298
- }
299
- };
300
-
301
- // src/lib/daemon/mind-tokens.ts
302
- import { randomUUID } from "crypto";
303
- var tokenToMind = /* @__PURE__ */ new Map();
304
- var mindToToken = /* @__PURE__ */ new Map();
305
- function generateMindToken(mindName) {
306
- revokeMindToken(mindName);
307
- const token = randomUUID();
308
- tokenToMind.set(token, mindName);
309
- mindToToken.set(mindName, token);
310
- return token;
311
- }
312
- function revokeMindToken(mindName) {
313
- const token = mindToToken.get(mindName);
314
- if (token) {
315
- tokenToMind.delete(token);
316
- mindToToken.delete(mindName);
317
- }
318
- }
319
- function resolveMindToken(token) {
320
- return tokenToMind.get(token) ?? null;
321
- }
322
-
323
- // src/lib/daemon/restart-tracker.ts
324
- var DEFAULT_MAX_ATTEMPTS = 5;
325
- var DEFAULT_BASE_DELAY = 3e3;
326
- var DEFAULT_MAX_DELAY = 6e4;
327
- var RestartTracker = class {
328
- attempts = /* @__PURE__ */ new Map();
329
- maxAttempts;
330
- baseDelay;
331
- maxDelay;
332
- constructor(opts) {
333
- this.maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
334
- this.baseDelay = opts?.baseDelay ?? DEFAULT_BASE_DELAY;
335
- this.maxDelay = opts?.maxDelay ?? DEFAULT_MAX_DELAY;
336
- }
337
- recordCrash(key) {
338
- const attempts = this.attempts.get(key) ?? 0;
339
- if (attempts >= this.maxAttempts) {
340
- return { shouldRestart: false, delay: 0, attempt: attempts };
341
- }
342
- const delay = Math.min(this.baseDelay * 2 ** attempts, this.maxDelay);
343
- this.attempts.set(key, attempts + 1);
344
- return { shouldRestart: true, delay, attempt: attempts + 1 };
345
- }
346
- reset(key) {
347
- return this.attempts.delete(key);
348
- }
349
- getAttempts(key) {
350
- return this.attempts.get(key) ?? 0;
351
- }
352
- get maxRestartAttempts() {
353
- return this.maxAttempts;
354
- }
355
- /** Bulk-load attempts from a Map (for persistence). */
356
- load(data) {
357
- this.attempts = new Map(data);
358
- }
359
- /** Export current attempts as a Map (for persistence). */
360
- save() {
361
- return new Map(this.attempts);
362
- }
363
- clear() {
364
- this.attempts.clear();
365
- }
366
- };
367
-
368
- // src/lib/daemon/mind-manager.ts
369
- var mlog = logger_default.child("minds");
370
- var execFileAsync = promisify(execFile);
371
- function mindPidPath(name) {
372
- return resolve(stateDir(name), "mind.pid");
373
- }
374
- var MindManager = class {
375
- minds = /* @__PURE__ */ new Map();
376
- stopping = /* @__PURE__ */ new Set();
377
- shuttingDown = false;
378
- restartTracker = new RestartTracker();
379
- pendingContext = /* @__PURE__ */ new Map();
380
- async resolveTarget(name) {
381
- const entry = await findMind(name);
382
- if (!entry) throw new Error(`Unknown mind: ${name}`);
383
- if (entry.parent) {
384
- if (!entry.dir) throw new Error(`Variant ${name} has no directory`);
385
- return { dir: entry.dir, port: entry.port, baseName: entry.parent };
386
- }
387
- const dir = mindDir(name);
388
- if (!existsSync3(dir)) throw new Error(`Mind directory missing: ${dir}`);
389
- return { dir, port: entry.port, baseName: name };
390
- }
391
- async startMind(name) {
392
- if (this.minds.has(name)) {
393
- throw new Error(`Mind ${name} is already running`);
394
- }
395
- const target = await this.resolveTarget(name);
396
- const { dir, baseName } = target;
397
- const port = target.port;
398
- const pidFile = mindPidPath(name);
399
- try {
400
- if (existsSync3(pidFile)) {
401
- const stalePid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
402
- if (stalePid > 0) {
403
- try {
404
- process.kill(stalePid, 0);
405
- const { stdout } = await execFileAsync("ps", ["-p", String(stalePid), "-o", "args="]);
406
- if (stdout.includes("server.ts")) {
407
- mlog.warn(`killing stale mind process ${stalePid} for ${name}`);
408
- process.kill(-stalePid, "SIGTERM");
409
- await new Promise((r) => setTimeout(r, 500));
410
- } else {
411
- mlog.debug(`stale PID ${stalePid} for ${name} is not a mind process, skipping`);
412
- }
413
- } catch (err) {
414
- if (err.code !== "ESRCH") {
415
- mlog.warn(`failed to check/kill stale process for ${name}`, logger_default.errorData(err));
416
- }
417
- }
418
- }
419
- rmSync2(pidFile, { force: true });
420
- }
421
- } catch (err) {
422
- mlog.warn(`failed to read PID file for ${name}`, logger_default.errorData(err));
423
- }
424
- try {
425
- const res = await fetch(`http://127.0.0.1:${port}/health`);
426
- if (res.ok) {
427
- mlog.warn(`killing orphan process on port ${port}`);
428
- await killProcessOnPort(port);
429
- await new Promise((r) => setTimeout(r, 500));
430
- }
431
- } catch {
432
- }
433
- const mindStateDir = stateDir(name);
434
- const logsDir = resolve(mindStateDir, "logs");
435
- mkdirSync(logsDir, { recursive: true });
436
- if (isIsolationEnabled()) {
437
- try {
438
- chownMindDir(mindStateDir, baseName);
439
- } catch (err) {
440
- throw new Error(
441
- `Cannot start mind ${name}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
442
- );
443
- }
444
- }
445
- const logStream = new RotatingLog(resolve(logsDir, "mind.log"));
446
- const mindToken = generateMindToken(name);
447
- const mindEnv = loadMergedEnv(name);
448
- const env = {
449
- ...process.env,
450
- ...mindEnv,
451
- VOLUTE_MIND: name,
452
- VOLUTE_STATE_DIR: stateDir(name),
453
- VOLUTE_MIND_DIR: dir,
454
- VOLUTE_MIND_PORT: String(port),
455
- VOLUTE_DAEMON_TOKEN: mindToken,
456
- // Strip CLAUDECODE so the Agent SDK can spawn Claude Code subprocesses
457
- CLAUDECODE: void 0
458
- };
459
- if (isIsolationEnabled()) {
460
- env.HOME = resolve(dir, "home");
461
- }
462
- const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
463
- const tsxArgs = ["src/server.ts", "--port", String(port)];
464
- let spawnCmd;
465
- let spawnArgs;
466
- if (isIsolationEnabled()) {
467
- [spawnCmd, spawnArgs] = await wrapForIsolation(tsxBin, tsxArgs, name);
468
- } else if (isSandboxEnabled()) {
469
- [spawnCmd, spawnArgs] = await wrapForSandbox(tsxBin, tsxArgs, dir, name, [
470
- dir,
471
- mindStateDir,
472
- "/tmp"
473
- ]);
474
- } else {
475
- spawnCmd = tsxBin;
476
- spawnArgs = tsxArgs;
477
- }
478
- const spawnOpts = {
479
- cwd: dir,
480
- stdio: ["ignore", "pipe", "pipe"],
481
- detached: true,
482
- env
483
- };
484
- const child = spawn(spawnCmd, spawnArgs, spawnOpts);
485
- this.minds.set(name, { child, port });
486
- child.stdout?.pipe(logStream);
487
- child.stderr?.pipe(logStream);
488
- try {
489
- await new Promise((resolve2, reject) => {
490
- const timeout = setTimeout(() => {
491
- reject(new Error(`Mind ${name} did not start within 30s`));
492
- }, 3e4);
493
- function checkOutput(data) {
494
- if (data.toString().match(/listening on :\d+/)) {
495
- clearTimeout(timeout);
496
- resolve2();
497
- }
498
- }
499
- child.stdout?.on("data", checkOutput);
500
- child.stderr?.on("data", checkOutput);
501
- child.on("error", (err) => {
502
- clearTimeout(timeout);
503
- reject(err);
504
- });
505
- child.on("exit", (code) => {
506
- clearTimeout(timeout);
507
- reject(new Error(`Mind ${name} exited with code ${code} during startup`));
508
- });
509
- });
510
- } catch (err) {
511
- this.minds.delete(name);
512
- try {
513
- child.kill();
514
- } catch {
515
- }
516
- throw err;
517
- }
518
- if (child.pid) {
519
- try {
520
- writeFileSync2(pidFile, String(child.pid));
521
- } catch (err) {
522
- mlog.warn(`failed to write PID file for ${name}`, logger_default.errorData(err));
523
- }
524
- }
525
- if (this.restartTracker.reset(name)) this.saveCrashAttempts();
526
- this.setupCrashRecovery(name, child);
527
- await setMindRunning(name, true);
528
- mlog.info(`started mind ${name} on port ${port}`);
529
- await this.deliverPendingContext(name);
530
- }
531
- setPendingContext(name, context) {
532
- this.pendingContext.set(name, context);
533
- }
534
- /** Deliver pending context (merge info, sprout, restart) directly to the mind via HTTP.
535
- * Intentionally bypasses DeliveryManager — these are system messages that should not be
536
- * routed, gated, or batched. */
537
- async deliverPendingContext(name) {
538
- const context = this.pendingContext.get(name);
539
- if (!context) return;
540
- const tracked = this.minds.get(name);
541
- if (!tracked) return;
542
- this.pendingContext.delete(name);
543
- const parts = [];
544
- if (context.type === "merge" || context.type === "merged") {
545
- parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
546
- } else if (context.type === "sprouted") {
547
- parts.push(await getPrompt("sprout_message"));
548
- } else {
549
- parts.push(await getPrompt("restart_message"));
550
- }
551
- if (context.summary) parts.push(`Changes: ${context.summary}`);
552
- if (context.justification) parts.push(`Why: ${context.justification}`);
553
- if (context.memory) parts.push(`Context: ${context.memory}`);
554
- const content = parts.join("\n");
555
- try {
556
- const db = await getDb();
557
- await db.insert(mindHistory).values({
558
- mind: name,
559
- type: "inbound",
560
- channel: "system",
561
- content
562
- });
563
- } catch (err) {
564
- mlog.error(`failed to persist pending context for ${name}`, logger_default.errorData(err));
565
- }
566
- try {
567
- await fetch(`http://127.0.0.1:${tracked.port}/message`, {
568
- method: "POST",
569
- headers: { "Content-Type": "application/json" },
570
- body: JSON.stringify({
571
- content: [{ type: "text", text: content }],
572
- channel: "system"
573
- })
574
- });
575
- } catch (err) {
576
- mlog.warn(`failed to deliver pending context to ${name}`, logger_default.errorData(err));
577
- }
578
- }
579
- setupCrashRecovery(name, child) {
580
- child.on("exit", async (code) => {
581
- this.minds.delete(name);
582
- if (this.shuttingDown || this.stopping.has(name)) return;
583
- mlog.error(`mind ${name} exited with code ${code}`);
584
- try {
585
- const { getSleepManagerIfReady } = await import("./sleep-manager-O7YQFCV5.js");
586
- const sleepState = getSleepManagerIfReady()?.getState(name);
587
- if (sleepState?.sleeping) {
588
- mlog.info(`${name} is sleeping \u2014 skipping crash recovery`);
589
- return;
590
- }
591
- } catch (err) {
592
- mlog.warn(`failed to check sleep state for ${name}`, logger_default.errorData(err));
593
- }
594
- import("./mind-activity-tracker-VYN2ZZ2M.js").then(({ markIdle }) => markIdle(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
595
- import("./activity-events-BKBPPUBP.js").then(
596
- ({ publish }) => publish({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
597
- ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
598
- const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
599
- this.saveCrashAttempts();
600
- if (!shouldRestart) {
601
- mlog.error(`${name} crashed ${attempt} times \u2014 giving up on restart`);
602
- await setMindRunning(name, false);
603
- return;
604
- }
605
- mlog.info(
606
- `crash recovery for ${name} \u2014 attempt ${attempt}/${this.restartTracker.maxRestartAttempts}, restarting in ${delay}ms`
607
- );
608
- setTimeout(() => {
609
- if (this.shuttingDown) return;
610
- this.startMind(name).catch((err) => {
611
- mlog.error(`failed to restart ${name}`, logger_default.errorData(err));
612
- });
613
- }, delay);
614
- });
615
- }
616
- async stopMind(name) {
617
- const tracked = this.minds.get(name);
618
- if (!tracked) return;
619
- this.stopping.add(name);
620
- const { child } = tracked;
621
- this.minds.delete(name);
622
- await new Promise((resolve2) => {
623
- child.on("exit", () => resolve2());
624
- try {
625
- process.kill(-child.pid, "SIGTERM");
626
- } catch {
627
- resolve2();
628
- }
629
- setTimeout(() => {
630
- try {
631
- process.kill(-child.pid, "SIGKILL");
632
- } catch {
633
- }
634
- resolve2();
635
- }, 5e3);
636
- });
637
- this.stopping.delete(name);
638
- revokeMindToken(name);
639
- if (this.restartTracker.reset(name)) this.saveCrashAttempts();
640
- rmSync2(mindPidPath(name), { force: true });
641
- if (!this.shuttingDown) {
642
- await setMindRunning(name, false);
643
- }
644
- mlog.info(`stopped mind ${name}`);
645
- }
646
- async restartMind(name) {
647
- await this.stopMind(name);
648
- await this.startMind(name);
649
- }
650
- async stopAll() {
651
- this.shuttingDown = true;
652
- const names = [...this.minds.keys()];
653
- await Promise.all(names.map((name) => this.stopMind(name)));
654
- }
655
- isRunning(name) {
656
- return this.minds.has(name);
657
- }
658
- getRunningMinds() {
659
- return [...this.minds.keys()];
660
- }
661
- get crashAttemptsPath() {
662
- return resolve(voluteSystemDir(), "crash-attempts.json");
663
- }
664
- loadCrashAttempts() {
665
- this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
666
- }
667
- saveCrashAttempts() {
668
- saveJsonMap(this.crashAttemptsPath, this.restartTracker.save());
669
- }
670
- clearCrashAttempts() {
671
- this.restartTracker.clear();
672
- clearJsonMap(this.crashAttemptsPath, /* @__PURE__ */ new Map());
673
- }
674
- };
675
- async function killProcessOnPort(port) {
676
- try {
677
- const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`, "-sTCP:LISTEN"]);
678
- const pids = /* @__PURE__ */ new Set();
679
- for (const line of stdout.trim().split("\n").filter(Boolean)) {
680
- const pid = parseInt(line, 10);
681
- pids.add(pid);
682
- try {
683
- const { stdout: psOut } = await execFileAsync("ps", ["-p", String(pid), "-o", "pgid="]);
684
- const pgid = parseInt(psOut.trim(), 10);
685
- if (pgid > 1) pids.add(pgid);
686
- } catch {
687
- }
688
- }
689
- for (const pid of pids) {
690
- try {
691
- process.kill(-pid, "SIGTERM");
692
- } catch {
693
- }
694
- try {
695
- process.kill(pid, "SIGTERM");
696
- } catch {
697
- }
698
- }
699
- } catch {
700
- }
701
- }
702
- var instance = null;
703
- function initMindManager() {
704
- if (instance) throw new Error("MindManager already initialized");
705
- instance = new MindManager();
706
- return instance;
707
- }
708
- function getMindManager() {
709
- if (!instance) throw new Error("MindManager not initialized \u2014 call initMindManager() first");
710
- return instance;
711
- }
712
-
713
- export {
714
- RotatingLog,
715
- RestartTracker,
716
- PROMPT_KEYS,
717
- PROMPT_DEFAULTS,
718
- substitute,
719
- getPrompt,
720
- getPromptIfCustom,
721
- getMindPromptDefaults,
722
- loadJsonMap,
723
- saveJsonMap,
724
- clearJsonMap,
725
- resolveMindToken,
726
- MindManager,
727
- initMindManager,
728
- getMindManager
729
- };