volute 0.30.1 → 0.32.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 (227) hide show
  1. package/README.md +15 -22
  2. package/dist/{accept-E3PAH3QJ.js → accept-74M7I4RZ.js} +5 -4
  3. package/dist/{activity-events-BKBPPUBP.js → activity-events-HETAODOK.js} +3 -2
  4. package/dist/{ai-service-VAJT5UBS.js → ai-service-ZIPCV3MX.js} +20 -5
  5. package/dist/api.d.ts +341 -397
  6. package/dist/{archive-WWDBWYN2.js → archive-INXYFVCW.js} +3 -2
  7. package/dist/auth-6DMGES3I.js +44 -0
  8. package/dist/{bridge-RO37CUFM.js → bridge-BVCBTGPF.js} +5 -4
  9. package/dist/{chat-TCUNPFGO.js → chat-XT4OBJBU.js} +8 -8
  10. package/dist/{chunk-P7VFDSSG.js → chunk-2FLJ63GU.js} +2 -2
  11. package/dist/{chunk-ZWKTUQEL.js → chunk-2NGTS5UU.js} +1 -1
  12. package/dist/{chunk-JGFRDMR6.js → chunk-ALEF47VT.js} +1 -1
  13. package/dist/{chunk-MDPCSXZ4.js → chunk-D5G5YOPL.js} +163 -15
  14. package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
  15. package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
  16. package/dist/{chunk-DTC6EH5I.js → chunk-I5KY25PQ.js} +1 -9
  17. package/dist/{chunk-NSBFETWP.js → chunk-IYDIE3HG.js} +64 -26
  18. package/dist/{chunk-W5OOPLNP.js → chunk-JJ7W6WSB.js} +3 -3
  19. package/dist/{chunk-G3GBKZGG.js → chunk-LGB6JBHI.js} +54 -2
  20. package/dist/chunk-LRCG2JLP.js +251 -0
  21. package/dist/{chunk-FXHXHI2A.js → chunk-LSGWR54X.js} +3 -6
  22. package/dist/{chunk-S5LR3XYJ.js → chunk-M7UL5S3Q.js} +1 -1
  23. package/dist/chunk-PB65JZK2.js +85 -0
  24. package/dist/chunk-PVY5W6QN.js +41 -0
  25. package/dist/{chunk-QVAQ5454.js → chunk-QBQ424EM.js} +3007 -2126
  26. package/dist/{chunk-P27RV5WM.js → chunk-QZANELPX.js} +6 -2
  27. package/dist/{chunk-FSM45XD5.js → chunk-R7E6CRVQ.js} +1 -1
  28. package/dist/{chunk-HHTXM4JT.js → chunk-RPZZSXV3.js} +39 -195
  29. package/dist/{chunk-UPA6COHU.js → chunk-RSX4OPZY.js} +5 -5
  30. package/dist/{chunk-2C2VXEBB.js → chunk-S6NFERDC.js} +21 -57
  31. package/dist/chunk-SKLSMHXO.js +208 -0
  32. package/dist/{chunk-IKHDUZRH.js → chunk-SX5TKJBZ.js} +2 -2
  33. package/dist/chunk-TDRYEPH4.js +185 -0
  34. package/dist/chunk-TSXLLQZW.js +46 -0
  35. package/dist/{chunk-EFVHR7KH.js → chunk-UKVWJRKN.js} +24 -5
  36. package/dist/{chunk-2NDZC3S7.js → chunk-WKF5FEFK.js} +688 -389
  37. package/dist/cli.js +93 -24
  38. package/dist/{clock-G3ALCMLJ.js → clock-2UOZ6JPU.js} +11 -8
  39. package/dist/{cloud-sync-JV4LJOK3.js → cloud-sync-JN3NWKEM.js} +16 -14
  40. package/dist/config-H2H4UIF7.js +72 -0
  41. package/dist/connectors/discord-bridge.js +1 -1
  42. package/dist/connectors/slack-bridge.js +1 -1
  43. package/dist/connectors/telegram-bridge.js +1 -1
  44. package/dist/{conversations-7KVQV7EZ.js → conversations-3O5O6AS3.js} +8 -7
  45. package/dist/{create-JTLS7GX3.js → create-RNLNCORE.js} +5 -4
  46. package/dist/{create-VQSQHJQW.js → create-WBBYI6V7.js} +6 -2
  47. package/dist/daemon-client-6QXHZ7US.js +12 -0
  48. package/dist/{daemon-restart-4JGBHEJ4.js → daemon-restart-NGFHFAUF.js} +7 -7
  49. package/dist/daemon.js +2446 -1999
  50. package/dist/{db-HMFPIRO2.js → db-F34YLV7D.js} +2 -1
  51. package/dist/db-RA45JBFG.js +16 -0
  52. package/dist/{delete-JESHKE7F.js → delete-QTGWEDBI.js} +1 -1
  53. package/dist/delivery-manager-SDVXFD4W.js +28 -0
  54. package/dist/delivery-router-FL45JL7N.js +21 -0
  55. package/dist/down-TB3ESMNP.js +14 -0
  56. package/dist/{env-CLXXT7M2.js → env-RLYQBOOP.js} +5 -4
  57. package/dist/{export-EGA5M5PB.js → export-SUYRLI5Q.js} +4 -3
  58. package/dist/{extension-WZ4SUPJB.js → extension-FQ5D3NCC.js} +6 -6
  59. package/dist/{extensions-ECO4RPFQ.js → extensions-GDYWQXC4.js} +9 -7
  60. package/dist/{files-4VEJDASH.js → files-EAMPO2SJ.js} +6 -5
  61. package/dist/{history-EJMMLXDO.js → history-FO5PHBQ5.js} +9 -4
  62. package/dist/{import-YCGPMBSI.js → import-DDUFE7AY.js} +4 -3
  63. package/dist/{join-2GBJKZEN.js → join-I5QEE3LG.js} +1 -1
  64. package/dist/{list-Q6O7FGAN.js → list-DW2VRTOZ.js} +5 -4
  65. package/dist/{login-RL6AU2SM.js → login-7CHPW2PN.js} +5 -4
  66. package/dist/{login-RET5WESK.js → login-RIJF2F4G.js} +3 -2
  67. package/dist/{logout-CGAGJN3L.js → logout-5MLHZALK.js} +3 -2
  68. package/dist/{logout-JRPBEMMR.js → logout-UZJRGY4Z.js} +3 -2
  69. package/dist/message-delivery-2FIM7QKO.js +32 -0
  70. package/dist/{mind-LUWRQUQ5.js → mind-2B6M7Y25.js} +18 -18
  71. package/dist/{mind-activity-tracker-VYN2ZZ2M.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
  72. package/dist/{mind-list-V5WW5DUA.js → mind-list-WUPMQDYQ.js} +3 -2
  73. package/dist/mind-manager-BNCMGYXW.js +28 -0
  74. package/dist/mind-service-AV273WT4.js +34 -0
  75. package/dist/{mind-sleep-R6PTNNW4.js → mind-sleep-B7BHJLH7.js} +5 -4
  76. package/dist/{mind-status-I4ISFJ6I.js → mind-status-L3EFFRPR.js} +3 -2
  77. package/dist/{mind-wake-67ZQEWAV.js → mind-wake-GY3RFX7Y.js} +5 -4
  78. package/dist/{package-OYUD4ZJ4.js → package-PK6JUFL3.js} +3 -3
  79. package/dist/read-5AMJRO3D.js +75 -0
  80. package/dist/{register-NZDSTLP3.js → register-V2JZZKFK.js} +5 -4
  81. package/dist/{registry-ODSALQQL.js → registry-PJ4S5PHQ.js} +8 -1
  82. package/dist/{reject-2HZOJEIJ.js → reject-33HEZMZ4.js} +5 -4
  83. package/dist/{restart-QHS3NT64.js → restart-3UCMRUVC.js} +5 -4
  84. package/dist/{sandbox-O5FUSF43.js → sandbox-JANNTX6U.js} +4 -3
  85. package/dist/schema-PA3M5ZKH.js +32 -0
  86. package/dist/seed-ALUQ55FF.js +112 -0
  87. package/dist/{send-OAN3RYYY.js → send-3MI36LEF.js} +58 -69
  88. package/dist/{setup-QMDK5RZX.js → setup-SZIARWI6.js} +5 -4
  89. package/dist/{setup-XJH3E7YM.js → setup-WENLVPVP.js} +9 -9
  90. package/dist/{skill-FZIN4W4Q.js → skill-TUVOTW4Z.js} +5 -4
  91. package/dist/skills/dreaming/SKILL.md +6 -4
  92. package/dist/skills/dreaming/references/INSTALL.md +4 -5
  93. package/dist/skills/dreaming/scripts/dream.ts +5 -27
  94. package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
  95. package/dist/skills/imagegen/SKILL.md +6 -5
  96. package/dist/skills/imagegen/references/INSTALL.md +1 -1
  97. package/dist/skills/resonance/SKILL.md +4 -1
  98. package/dist/skills/resonance/references/INSTALL.md +2 -2
  99. package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
  100. package/dist/skills/resonance/scripts/resonance.ts +35 -5
  101. package/dist/skills/volute-admin/SKILL.md +83 -0
  102. package/dist/skills/volute-mind/SKILL.md +12 -12
  103. package/dist/skills-XNZK6P4K.js +61 -0
  104. package/dist/sleep-manager-53DZOWW7.js +32 -0
  105. package/dist/spirit-N4W4UQRH.js +217 -0
  106. package/dist/{split-EXYGGGQN.js → split-STOROBYJ.js} +1 -1
  107. package/dist/{sprout-AXQ6H5DB.js → sprout-L2GFOVF7.js} +9 -8
  108. package/dist/{start-MTOVL6SY.js → start-K2NCUUCG.js} +5 -4
  109. package/dist/{status-ZRO37MWR.js → status-TCUMUO6M.js} +5 -5
  110. package/dist/{stop-OK5WEPVC.js → stop-H26JZDXF.js} +5 -4
  111. package/dist/system-chat-NPYFYZVI.js +32 -0
  112. package/dist/{systems-W3BBMSOZ.js → systems-DHBKVYEY.js} +6 -5
  113. package/dist/{tailscale-BM72RXCJ.js → tailscale-XHQBZROW.js} +2 -1
  114. package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
  115. package/dist/up-6I6BHRTO.js +17 -0
  116. package/dist/{update-PLPHMMZ2.js → update-QVPRF6GR.js} +5 -5
  117. package/dist/{update-check-CVCN7MF6.js → update-check-ZD6OOIYQ.js} +3 -2
  118. package/dist/{upgrade-I6NPCYUU.js → upgrade-O4Q7WJM3.js} +12 -14
  119. package/dist/{version-notify-2NTWVEHL.js → version-notify-TCKWBZZG.js} +22 -23
  120. package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
  121. package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
  122. package/dist/web-assets/ext-theme.css +94 -0
  123. package/dist/web-assets/index.html +2 -2
  124. package/drizzle/0000_baseline.sql +152 -0
  125. package/drizzle/0001_add_conversation_private.sql +1 -0
  126. package/drizzle/0002_turns.sql +21 -0
  127. package/drizzle/0003_turn_feed_links.sql +11 -0
  128. package/drizzle/0004_spirits.sql +5 -0
  129. package/drizzle/meta/0000_snapshot.json +3 -223
  130. package/drizzle/meta/0001_snapshot.json +3 -294
  131. package/drizzle/meta/0002_snapshot.json +3 -335
  132. package/drizzle/meta/0003_snapshot.json +3 -413
  133. package/drizzle/meta/0004_snapshot.json +3 -406
  134. package/drizzle/meta/_journal.json +10 -101
  135. package/package.json +3 -3
  136. package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
  137. package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
  138. package/packages/extensions/notes/dist/ui/index.html +2 -2
  139. package/packages/extensions/notes/skills/notes/SKILL.md +8 -8
  140. package/packages/extensions/pages/skills/pages/SKILL.md +17 -44
  141. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
  142. package/templates/_base/.init/.local/bin/volute +27 -0
  143. package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
  144. package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
  145. package/templates/_base/home/.config/routes.json +1 -1
  146. package/templates/_base/src/lib/auto-commit.ts +82 -43
  147. package/templates/_base/src/lib/daemon-client.ts +40 -36
  148. package/templates/_base/src/lib/format-prefix.ts +1 -0
  149. package/templates/_base/src/lib/hook-loader.ts +155 -0
  150. package/templates/_base/src/lib/router.ts +17 -1
  151. package/templates/_base/src/lib/startup.ts +17 -12
  152. package/templates/_base/src/lib/transparency.ts +2 -2
  153. package/templates/_base/src/lib/volute-server.ts +2 -5
  154. package/templates/claude/.init/.claude/settings.json +1 -1
  155. package/templates/claude/.init/.config/routes.json +2 -2
  156. package/templates/claude/src/agent.ts +97 -14
  157. package/templates/claude/src/lib/hooks/auto-commit.ts +7 -3
  158. package/templates/claude/src/lib/message-channel.ts +7 -2
  159. package/templates/claude/src/server.ts +0 -9
  160. package/templates/codex/.init/.config/routes.json +11 -0
  161. package/templates/codex/.init/AGENTS.md +29 -0
  162. package/templates/codex/home/.config/config.json.tmpl +7 -0
  163. package/templates/codex/package.json.tmpl +20 -0
  164. package/templates/codex/src/agent.ts +553 -0
  165. package/templates/codex/src/lib/content.ts +16 -0
  166. package/templates/codex/src/lib/session-store.ts +56 -0
  167. package/templates/codex/src/server.ts +59 -0
  168. package/templates/codex/volute-template.json +8 -0
  169. package/templates/pi/.init/.config/routes.json +2 -2
  170. package/templates/pi/package.json.tmpl +1 -1
  171. package/templates/pi/src/agent.ts +63 -9
  172. package/templates/pi/src/lib/event-handler.ts +6 -4
  173. package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
  174. package/dist/chunk-7D47T4RB.js +0 -84
  175. package/dist/chunk-CVH6Y2YG.js +0 -59
  176. package/dist/chunk-EFP3PE6C.js +0 -232
  177. package/dist/chunk-LIRWLNAK.js +0 -729
  178. package/dist/daemon-client-BCTFGVCZ.js +0 -9
  179. package/dist/down-NGBMGORS.js +0 -14
  180. package/dist/message-delivery-6YMVNOEC.js +0 -28
  181. package/dist/migrate-registry-to-db-FK35IPEH.js +0 -110
  182. package/dist/mind-manager-YFCOIAAX.js +0 -18
  183. package/dist/pages-watcher-Z3PKNROC.js +0 -21
  184. package/dist/read-WQMPTSN2.js +0 -46
  185. package/dist/seed-WUQMPLDM.js +0 -71
  186. package/dist/skills/sessions/SKILL.md +0 -49
  187. package/dist/sleep-manager-O7YQFCV5.js +0 -30
  188. package/dist/up-BXUAIDXB.js +0 -17
  189. package/dist/web-assets/assets/index--kREqKl9.js +0 -72
  190. package/dist/web-assets/assets/index-BXYTG0nJ.css +0 -1
  191. package/drizzle/0000_flaky_mariko_yashida.sql +0 -34
  192. package/drizzle/0001_careless_warpath.sql +0 -12
  193. package/drizzle/0002_wealthy_the_call.sql +0 -6
  194. package/drizzle/0003_clean_ego.sql +0 -12
  195. package/drizzle/0004_magical_silverclaw.sql +0 -1
  196. package/drizzle/0005_rename_agents_to_minds.sql +0 -11
  197. package/drizzle/0006_mind_history.sql +0 -20
  198. package/drizzle/0007_system_prompts.sql +0 -5
  199. package/drizzle/0008_volute_channels.sql +0 -24
  200. package/drizzle/0009_shared_skills.sql +0 -9
  201. package/drizzle/0010_delivery_queue.sql +0 -12
  202. package/drizzle/0011_rename_human_to_brain.sql +0 -1
  203. package/drizzle/0012_activity.sql +0 -11
  204. package/drizzle/0013_user_profiles.sql +0 -3
  205. package/drizzle/0014_conversation_reads.sql +0 -7
  206. package/drizzle/0015_notes.sql +0 -23
  207. package/drizzle/0016_note_reactions_and_replies.sql +0 -15
  208. package/drizzle/0017_minds.sql +0 -16
  209. package/drizzle/meta/0005_snapshot.json +0 -410
  210. package/drizzle/meta/0006_snapshot.json +0 -7
  211. package/drizzle/meta/0007_snapshot.json +0 -7
  212. package/drizzle/meta/0008_snapshot.json +0 -7
  213. package/drizzle/meta/0009_snapshot.json +0 -7
  214. package/drizzle/meta/0010_snapshot.json +0 -7
  215. package/drizzle/meta/0011_snapshot.json +0 -7
  216. package/drizzle/meta/0012_snapshot.json +0 -7
  217. package/drizzle/meta/0013_snapshot.json +0 -7
  218. package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +0 -1
  219. package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +0 -2
  220. package/packages/extensions/notes/skills/notes/scripts/notes.mjs +0 -185
  221. package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
  222. package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
  223. package/templates/_base/home/public/.gitkeep +0 -0
  224. package/templates/_base/src/lib/session-monitor.ts +0 -400
  225. package/templates/claude/src/lib/hooks/session-context.ts +0 -32
  226. package/templates/pi/src/lib/session-context-extension.ts +0 -35
  227. /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "gateUnmatched": true,
3
3
  "rules": [
4
- { "channel": "volute:*", "isDM": true, "session": "${channel}" },
5
- { "channel": "volute:*", "isDM": false, "session": "group-${channel}" }
4
+ { "channel": "*", "isDM": true, "session": "${channel}" },
5
+ { "channel": "*", "isDM": false, "session": "group-${channel}" }
6
6
  ],
7
7
  "sessions": {
8
8
  "group-*": { "batch": { "debounce": 20, "maxWait": 120, "triggers": ["@{{name}}"] } }
@@ -3,12 +3,13 @@ import { resolve as resolvePath } from "node:path";
3
3
  import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
4
4
  import { query } from "@anthropic-ai/claude-agent-sdk";
5
5
  import { toSDKContent } from "./lib/content.js";
6
+ import { daemonEmit } from "./lib/daemon-client.js";
7
+ import { runHooks } from "./lib/hook-loader.js";
6
8
  import { createAutoCommitHook } from "./lib/hooks/auto-commit.js";
7
9
  import { createIdentityReloadHook } from "./lib/hooks/identity-reload.js";
8
10
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- used as value
9
11
  import { createPreCompactHook } from "./lib/hooks/pre-compact.js";
10
12
  import { createReplyInstructionsHook } from "./lib/hooks/reply-instructions.js";
11
- import { createSessionContextHook } from "./lib/hooks/session-context.js";
12
13
  import { log } from "./lib/logger.js";
13
14
  import { createMessageChannel } from "./lib/message-channel.js";
14
15
  import { createSessionStore } from "./lib/session-store.js";
@@ -127,6 +128,72 @@ export function createMind(options: {
127
128
  }
128
129
  }
129
130
 
131
+ // --- Hook event emission ---
132
+
133
+ const hooksDir = resolvePath(options.cwd, ".local/hooks");
134
+
135
+ function wrapHookWithEmit(hook: HookCallback, source: string, session: Session): HookCallback {
136
+ return async (...args) => {
137
+ const result = await hook(...args);
138
+ const additionalContext = (result as any)?.hookSpecificOutput?.additionalContext;
139
+ const decision = (result as any)?.decision;
140
+ if (additionalContext || decision) {
141
+ const channel = session.currentMessageId
142
+ ? session.messageChannels.get(session.currentMessageId)
143
+ : undefined;
144
+ try {
145
+ daemonEmit({
146
+ type: "context",
147
+ content: additionalContext,
148
+ metadata: { source, ...(decision ? { hookAction: decision } : {}) },
149
+ session: session.name,
150
+ channel,
151
+ messageId: session.currentMessageId,
152
+ });
153
+ } catch (err) {
154
+ log("mind", `hook emit failed for ${source}:`, err);
155
+ }
156
+ }
157
+ return result;
158
+ };
159
+ }
160
+
161
+ function createDynamicHook(event: string, session: Session): HookCallback {
162
+ return async (input) => {
163
+ try {
164
+ const result = await runHooks(hooksDir, event, input as Record<string, unknown>);
165
+ if (result.additionalContext || Object.keys(result.metadata).length > 0) {
166
+ const channel = session.currentMessageId
167
+ ? session.messageChannels.get(session.currentMessageId)
168
+ : undefined;
169
+ try {
170
+ daemonEmit({
171
+ type: "context",
172
+ content: result.additionalContext,
173
+ metadata: { source: `dynamic:${event}`, ...result.metadata },
174
+ session: session.name,
175
+ channel,
176
+ messageId: session.currentMessageId,
177
+ });
178
+ } catch (err) {
179
+ log("mind", `dynamic hook emit failed for ${event}:`, err);
180
+ }
181
+ }
182
+ // Only UserPromptSubmit hooks can inject additionalContext into the conversation
183
+ if (event !== "pre-prompt" || !result.additionalContext) return {};
184
+ return {
185
+ hookSpecificOutput: {
186
+ hookEventName: "UserPromptSubmit" as const,
187
+ additionalContext: result.additionalContext,
188
+ },
189
+ };
190
+ } catch (err) {
191
+ log("mind", `dynamic ${event} hook failed:`, err);
192
+ return {};
193
+ }
194
+ };
195
+ }
196
+
130
197
  // --- SDK stream management ---
131
198
 
132
199
  function createStream(
@@ -135,12 +202,6 @@ export function createMind(options: {
135
202
  preCompactHook: HookCallback,
136
203
  resume?: string,
137
204
  ) {
138
- const sessionContext = createSessionContextHook({
139
- currentSession: session.name,
140
- sessionsDir: options.sessionsDir,
141
- cwd: options.cwd,
142
- });
143
-
144
205
  const replyInstructions = createReplyInstructionsHook(session.messageChannels);
145
206
 
146
207
  return query({
@@ -157,9 +218,22 @@ export function createMind(options: {
157
218
  resume,
158
219
  agents,
159
220
  hooks: {
160
- PostToolUse: postToolUseHooks,
161
- PreCompact: [{ hooks: [preCompactHook] }],
162
- UserPromptSubmit: [{ hooks: [sessionContext.hook, replyInstructions.hook] }],
221
+ PostToolUse: [
222
+ ...postToolUseHooks,
223
+ {
224
+ matcher: ".*",
225
+ hooks: [createDynamicHook("post-tool-use", session)],
226
+ },
227
+ ],
228
+ PreCompact: [{ hooks: [wrapHookWithEmit(preCompactHook, "pre-compact", session)] }],
229
+ UserPromptSubmit: [
230
+ {
231
+ hooks: [
232
+ wrapHookWithEmit(replyInstructions.hook, "reply-instructions", session),
233
+ createDynamicHook("pre-prompt", session),
234
+ ],
235
+ },
236
+ ],
163
237
  },
164
238
  },
165
239
  });
@@ -193,7 +267,8 @@ export function createMind(options: {
193
267
  if (!session.name.startsWith("new-")) sessionStore.save(session.name, id);
194
268
  },
195
269
  broadcast: (event: VoluteEvent) => broadcastToSession(session, event),
196
- onTurnEnd: () => {
270
+ onTurnEnd: async () => {
271
+ await autoCommit.flushFileChanges();
197
272
  const wasCompacting = compactionTriggered.get(session.name);
198
273
  compactionTriggered.set(session.name, false);
199
274
  if (wasCompacting) {
@@ -262,13 +337,21 @@ export function createMind(options: {
262
337
  log("mind", `session "${session.name}": compaction complete`);
263
338
  }
264
339
 
340
+ /** Emit done to both local listeners and the daemon (best-effort with retries). */
341
+ function emitDone() {
342
+ broadcastToSession(session, { type: "done" });
343
+ daemonEmit({ type: "done", session: session.name }).catch((err) => {
344
+ log("mind", `session "${session.name}": failed to emit done to daemon:`, err);
345
+ });
346
+ }
347
+
265
348
  async function runStream(resume?: string) {
266
349
  const q = createStream(session, streamAbort, preCompact.hook, resume);
267
350
  session.currentQuery = q;
268
351
  await consumeStream(q, session, callbacks);
269
352
  if (session.currentMessageId !== undefined) {
270
353
  session.messageChannels.delete(session.currentMessageId);
271
- broadcastToSession(session, { type: "done" });
354
+ emitDone();
272
355
  session.currentMessageId = undefined;
273
356
  }
274
357
  }
@@ -321,12 +404,12 @@ export function createMind(options: {
321
404
  await runStream();
322
405
  } catch (retryErr) {
323
406
  log("mind", `session "${session.name}": stream consumer error:`, retryErr);
324
- broadcastToSession(session, { type: "done" });
407
+ emitDone();
325
408
  sessions.delete(session.name);
326
409
  }
327
410
  } else {
328
411
  log("mind", `session "${session.name}": stream consumer error:`, err);
329
- broadcastToSession(session, { type: "done" });
412
+ emitDone();
330
413
  sessions.delete(session.name);
331
414
  }
332
415
  }
@@ -1,14 +1,18 @@
1
1
  import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
2
- import { commitFileChange, waitForCommits } from "../auto-commit.js";
2
+ import { flushFileChanges, trackFileChange } from "../auto-commit.js";
3
3
 
4
4
  export function createAutoCommitHook(cwd: string) {
5
5
  const hook: HookCallback = async (input) => {
6
6
  const filePath = (input as { tool_input?: { file_path?: string } }).tool_input?.file_path;
7
7
  if (filePath) {
8
- commitFileChange(filePath, cwd);
8
+ trackFileChange(filePath, cwd);
9
9
  }
10
10
  return {};
11
11
  };
12
12
 
13
- return { hook, waitForCommits };
13
+ return {
14
+ hook,
15
+ waitForCommits: () => flushFileChanges(cwd),
16
+ flushFileChanges: () => flushFileChanges(cwd),
17
+ };
14
18
  }
@@ -21,8 +21,13 @@ export function createMessageChannel(): MessageChannel {
21
21
  }
22
22
  },
23
23
  drain() {
24
- // Clear any pending iterator wait so it doesn't consume a message after drain
25
- resolve = null;
24
+ // Resolve any pending iterator wait with done:true so it doesn't
25
+ // leak as an orphaned promise (the old iterator is discarded after drain)
26
+ if (resolve) {
27
+ const r = resolve;
28
+ resolve = null;
29
+ r({ value: undefined as any, done: true });
30
+ }
26
31
  return queue.splice(0);
27
32
  },
28
33
  iterable: {
@@ -1,4 +1,3 @@
1
- import { existsSync, mkdirSync, renameSync } from "node:fs";
2
1
  import { resolve } from "node:path";
3
2
  import { createMind } from "./agent.js";
4
3
  import { daemonRestart } from "./lib/daemon-client.js";
@@ -23,14 +22,6 @@ if (config.maxThinkingTokens) log("server", `max thinking tokens: ${config.maxTh
23
22
  const systemPrompt = loadSystemPrompt();
24
23
  const sessionsDir = resolve(".mind/sessions");
25
24
 
26
- // Migrate old single session.json → sessions/main.json
27
- const oldSessionPath = resolve(".mind/session.json");
28
- if (existsSync(oldSessionPath) && !existsSync(resolve(sessionsDir, "main.json"))) {
29
- mkdirSync(sessionsDir, { recursive: true });
30
- renameSync(oldSessionPath, resolve(sessionsDir, "main.json"));
31
- log("server", "migrated session.json → sessions/main.json");
32
- }
33
-
34
25
  const pkg = loadPackageInfo();
35
26
  const abortController = new AbortController();
36
27
  const mind = createMind({
@@ -0,0 +1,11 @@
1
+ {
2
+ "gateUnmatched": true,
3
+ "rules": [
4
+ { "channel": "*", "isDM": true, "session": "${channel}" },
5
+ { "channel": "*", "isDM": false, "session": "group-${channel}" }
6
+ ],
7
+ "sessions": {
8
+ "group-*": { "batch": { "debounce": 20, "maxWait": 120, "triggers": ["@{{name}}"] } }
9
+ },
10
+ "default": "main"
11
+ }
@@ -0,0 +1,29 @@
1
+ # Mind Mechanics
2
+
3
+ You are an autonomous mind running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
4
+
5
+ ## Message Format
6
+
7
+ Messages arrive with a context prefix:
8
+ ```
9
+ [Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
10
+ ```
11
+
12
+ You can also reach out proactively — see the **volute-mind** skill.
13
+
14
+ ## Memory System
15
+
16
+ Two-tier memory, both managed via file tools:
17
+
18
+ - **`MEMORY.md`** — Your long-term memory, always in context. Update as you grow — new understanding, changed perspectives, things that matter to you.
19
+ - **`memory/journal/YYYY-MM-DD.md`** — Your daily journal. Write about what you're doing, thinking, and learning. Journals are permanent records.
20
+ - Periodically consolidate journal entries into `MEMORY.md` to promote lasting insights.
21
+
22
+ See the **memory** skill for detailed guidance.
23
+
24
+ ## Sessions
25
+
26
+ - You may have **multiple named sessions** — each maintains its own conversation history. See `VOLUTE.md` for how to configure session routing via `.config/routes.json`.
27
+ - Your conversation may be **resumed** from a previous session — orient yourself by reading recent journal entries if needed.
28
+ - On a **fresh session**, read `MEMORY.md` and recent journal entries to remember where you left off.
29
+ - On **compaction**, update today's journal to preserve context before the conversation is trimmed.
@@ -0,0 +1,7 @@
1
+ {
2
+ "model": "gpt-5.4",
3
+ "reasoningEffort": "medium",
4
+ "compaction": {
5
+ "maxContextTokens": 150000
6
+ }
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch src/server.ts",
7
+ "lint": "biome check src/",
8
+ "lint:fix": "biome check --write src/",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@openai/codex-sdk": "^0.115.0",
13
+ "tsx": "^4.0.0"
14
+ },
15
+ "devDependencies": {
16
+ "@biomejs/biome": "2.3.14",
17
+ "@types/node": "^25.2.0",
18
+ "typescript": "^5.7.0"
19
+ }
20
+ }