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}}"] } }
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@mariozechner/pi-coding-agent": "^0.51.0",
12
+ "@mariozechner/pi-coding-agent": "^0.58.1",
13
13
  "@sinclair/typebox": "^0.34.0",
14
14
  "tsx": "^4.0.0"
15
15
  },
@@ -10,11 +10,11 @@ import {
10
10
  SettingsManager,
11
11
  } from "@mariozechner/pi-coding-agent";
12
12
  import { extractImages, extractText } from "./lib/content.js";
13
- import { createEventHandler } from "./lib/event-handler.js";
13
+ import { createEventHandler, emit } from "./lib/event-handler.js";
14
+ import { runHooks } from "./lib/hook-loader.js";
14
15
  import { log } from "./lib/logger.js";
15
16
  import { createReplyInstructionsExtension } from "./lib/reply-instructions-extension.js";
16
17
  import { resolveModel } from "./lib/resolve-model.js";
17
- import { createSessionContextExtension } from "./lib/session-context-extension.js";
18
18
  import { loadPrompts, type SubagentConfig } from "./lib/startup.js";
19
19
  import { createSubagentExtension, type SubagentDefinition } from "./lib/subagents.js";
20
20
  import type {
@@ -64,7 +64,7 @@ export function createMind(options: {
64
64
  // Shared setup (created once)
65
65
  const modelStr = options.model || process.env.PI_MODEL || "anthropic:claude-sonnet-4-20250514";
66
66
  const model = resolveModel(modelStr);
67
- const authStorage = new AuthStorage();
67
+ const authStorage = AuthStorage.create();
68
68
  const modelRegistry = new ModelRegistry(authStorage);
69
69
 
70
70
  // --- Subagents (config-driven) ---
@@ -109,6 +109,59 @@ export function createMind(options: {
109
109
  ? createSubagentExtension(subagents, { cwd: options.cwd, model, authStorage, modelRegistry })
110
110
  : undefined;
111
111
 
112
+ // --- Dynamic hook extension ---
113
+
114
+ const hooksDir = resolvePath(options.cwd, ".local/hooks");
115
+
116
+ function createDynamicHookExtension(session: PiSession): ExtensionFactory {
117
+ return (pi) => {
118
+ pi.on("before_agent_start", async () => {
119
+ try {
120
+ const result = await runHooks(hooksDir, "pre-prompt", {
121
+ event: "pre-prompt",
122
+ session: session.name,
123
+ });
124
+ if (result.additionalContext) {
125
+ emit(session, {
126
+ type: "context",
127
+ content: result.additionalContext,
128
+ metadata: { source: "dynamic:pre-prompt", ...result.metadata },
129
+ });
130
+ return {
131
+ message: {
132
+ customType: "dynamic-hook",
133
+ content: result.additionalContext,
134
+ display: true,
135
+ },
136
+ };
137
+ }
138
+ } catch (err) {
139
+ log("mind", "dynamic pre-prompt hook failed:", err);
140
+ }
141
+ return {};
142
+ });
143
+
144
+ pi.on("tool_execution_end", async (event: any) => {
145
+ try {
146
+ const result = await runHooks(hooksDir, "post-tool-use", {
147
+ event: "post-tool-use",
148
+ tool_name: event.toolName,
149
+ tool_input: event.args,
150
+ });
151
+ if (result.additionalContext) {
152
+ emit(session, {
153
+ type: "context",
154
+ content: result.additionalContext,
155
+ metadata: { source: "dynamic:post-tool-use", ...result.metadata },
156
+ });
157
+ }
158
+ } catch (err) {
159
+ log("mind", "dynamic post-tool-use hook failed:", err);
160
+ }
161
+ });
162
+ };
163
+ }
164
+
112
165
  // --- Session lifecycle ---
113
166
 
114
167
  function getOrCreateSession(name: string): PiSession {
@@ -189,12 +242,13 @@ export function createMind(options: {
189
242
  retry: { enabled: true, maxRetries: 3 },
190
243
  });
191
244
 
192
- const sessionContextExtension = createSessionContextExtension({
193
- currentSession: session.name,
194
- mindDir: options.mindDir,
195
- });
245
+ const replyInstructionsExtension = createReplyInstructionsExtension(
246
+ session.messageChannels,
247
+ emit,
248
+ session,
249
+ );
196
250
 
197
- const replyInstructionsExtension = createReplyInstructionsExtension(session.messageChannels);
251
+ const dynamicHookExtension = createDynamicHookExtension(session);
198
252
 
199
253
  const resourceLoader = new DefaultResourceLoader({
200
254
  cwd: options.cwd,
@@ -202,9 +256,9 @@ export function createMind(options: {
202
256
  systemPrompt: options.systemPrompt,
203
257
  extensionFactories: [
204
258
  preCompactExtension,
205
- sessionContextExtension,
206
259
  replyInstructionsExtension,
207
260
  ...(subagentExtension ? [subagentExtension] : []),
261
+ dynamicHookExtension,
208
262
  ],
209
263
  });
210
264
  await resourceLoader.reload();
@@ -1,4 +1,4 @@
1
- import { commitFileChange } from "./auto-commit.js";
1
+ import { flushFileChanges, trackFileChange } from "./auto-commit.js";
2
2
  import { daemonEmit, type EventType } from "./daemon-client.js";
3
3
  import { log, warn } from "./logger.js";
4
4
  import { filterEvent, loadTransparencyPreset } from "./transparency.js";
@@ -21,7 +21,7 @@ export type EventHandlerOptions = {
21
21
  // Loaded once at startup — mind restarts on config changes
22
22
  const preset = loadTransparencyPreset();
23
23
 
24
- function emit(
24
+ export function emit(
25
25
  session: EventSession,
26
26
  event: { type: EventType; content?: string; metadata?: Record<string, unknown> },
27
27
  ) {
@@ -110,7 +110,7 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
110
110
  const args = toolArgs.get(event.toolCallId);
111
111
  const filePath = (args as { path?: string })?.path;
112
112
  if (filePath) {
113
- commitFileChange(filePath, options.cwd);
113
+ trackFileChange(filePath, options.cwd);
114
114
  }
115
115
  }
116
116
  toolArgs.delete(event.toolCallId);
@@ -158,7 +158,9 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
158
158
  options.broadcast({ type: "done" });
159
159
  emit(session, { type: "done" });
160
160
  session.currentMessageId = undefined;
161
- options.onTurnEnd?.();
161
+ flushFileChanges(options.cwd)
162
+ .then(() => options.onTurnEnd?.())
163
+ .catch((err) => log("mind", `session "${session.name}": flush/turn-end error:`, err));
162
164
  }
163
165
  } catch (err) {
164
166
  log("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
@@ -1,27 +1,48 @@
1
1
  import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
2
+ import type { EventSession } from "./event-handler.js";
3
+ import { log } from "./logger.js";
2
4
  import { loadPrompts } from "./startup.js";
3
5
 
4
6
  export function createReplyInstructionsExtension(
5
7
  messageChannels: Map<string, string>,
8
+ emitContext?: (
9
+ session: EventSession,
10
+ event: { type: "context"; content: string; metadata: Record<string, unknown> },
11
+ ) => void,
12
+ session?: EventSession,
6
13
  ): ExtensionFactory {
7
14
  const prompts = loadPrompts();
8
15
  return (pi) => {
9
16
  let fired = false;
10
17
  pi.on("before_agent_start", () => {
11
- if (fired) return {};
18
+ try {
19
+ if (fired) return {};
12
20
 
13
- const channel = messageChannels.values().next().value;
14
- if (!channel) return {};
21
+ const channel = messageChannels.values().next().value;
22
+ if (!channel) return {};
15
23
 
16
- fired = true;
24
+ fired = true;
17
25
 
18
- return {
19
- message: {
20
- customType: "reply-instructions",
21
- content: prompts.reply_instructions.replace(/\$\{channel\}/g, channel),
22
- display: true,
23
- },
24
- };
26
+ const content = prompts.reply_instructions.replace(/\$\{channel\}/g, channel);
27
+ if (emitContext && session) {
28
+ emitContext(session, {
29
+ type: "context",
30
+ content,
31
+ metadata: { source: "reply-instructions" },
32
+ });
33
+ }
34
+
35
+ return {
36
+ message: {
37
+ customType: "reply-instructions",
38
+ content,
39
+ display: true,
40
+ },
41
+ };
42
+ } catch (err) {
43
+ log("mind", "reply instructions extension failed:", err);
44
+ return {};
45
+ }
25
46
  });
26
47
  };
27
48
  }
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- voluteHome,
4
- voluteSystemDir
5
- } from "./chunk-HHTXM4JT.js";
6
-
7
- // src/lib/setup.ts
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
- import { resolve } from "path";
10
- function configPath() {
11
- return resolve(voluteSystemDir(), "config.json");
12
- }
13
- function readGlobalConfig() {
14
- const path = configPath();
15
- const legacyPath = resolve(voluteHome(), "config.json");
16
- const effectivePath = existsSync(path) ? path : legacyPath;
17
- if (!existsSync(effectivePath)) return {};
18
- try {
19
- return JSON.parse(readFileSync(effectivePath, "utf-8"));
20
- } catch (err) {
21
- console.error(`Failed to parse ${effectivePath}: ${err instanceof Error ? err.message : err}`);
22
- return {};
23
- }
24
- }
25
- function writeGlobalConfig(config) {
26
- const path = configPath();
27
- mkdirSync(voluteSystemDir(), { recursive: true });
28
- writeFileSync(path, `${JSON.stringify(config, null, 2)}
29
- `);
30
- }
31
- function isSetupComplete() {
32
- const config = readGlobalConfig();
33
- return config.setup != null;
34
- }
35
- function migrateSetupConfig() {
36
- const config = readGlobalConfig();
37
- if (config.setup) return;
38
- const home = voluteHome();
39
- const systemDir = voluteSystemDir();
40
- const registryPath = resolve(systemDir, "minds.json");
41
- const legacyRegistryPath = resolve(home, "minds.json");
42
- if (!existsSync(registryPath) && !existsSync(legacyRegistryPath)) return;
43
- const isSystem = process.env.VOLUTE_ISOLATION === "user";
44
- const mindsDir = process.env.VOLUTE_MINDS_DIR || resolve(home, "minds");
45
- let hasService = false;
46
- try {
47
- if (process.platform === "darwin") {
48
- const plistPath = resolve(
49
- process.env.HOME || "",
50
- "Library",
51
- "LaunchAgents",
52
- "com.volute.daemon.plist"
53
- );
54
- if (existsSync(plistPath)) hasService = true;
55
- }
56
- if (process.platform === "linux") {
57
- if (existsSync("/etc/systemd/system/volute.service")) hasService = true;
58
- const userUnit = resolve(
59
- process.env.HOME || "",
60
- ".config",
61
- "systemd",
62
- "user",
63
- "volute.service"
64
- );
65
- if (existsSync(userUnit)) hasService = true;
66
- }
67
- } catch {
68
- }
69
- const setup = {
70
- type: isSystem ? "system" : "local",
71
- isolation: isSystem ? "user" : "none",
72
- mindsDir,
73
- service: hasService
74
- };
75
- writeGlobalConfig({ ...config, setup });
76
- }
77
-
78
- export {
79
- configPath,
80
- readGlobalConfig,
81
- writeGlobalConfig,
82
- isSetupComplete,
83
- migrateSetupConfig
84
- };
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- wrapForIsolation
4
- } from "./chunk-G3GBKZGG.js";
5
-
6
- // src/lib/exec.ts
7
- import { execFile as execFileCb, execFileSync, spawn } from "child_process";
8
- async function exec(cmd, args, options) {
9
- const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
10
- return new Promise((resolve, reject) => {
11
- execFileCb(
12
- wrappedCmd,
13
- wrappedArgs,
14
- { cwd: options?.cwd, env: options?.env },
15
- (err, stdout, stderr) => {
16
- if (err) {
17
- err.stderr = stderr;
18
- err.stdout = stdout;
19
- reject(err);
20
- } else {
21
- resolve(stdout);
22
- }
23
- }
24
- );
25
- });
26
- }
27
- function gitExec(args, options) {
28
- const fullArgs = process.env.VOLUTE_ISOLATION === "user" ? ["-c", "safe.directory=*", ...args] : args;
29
- return exec("git", fullArgs, options);
30
- }
31
- function resolveVoluteBin() {
32
- try {
33
- return execFileSync("which", ["volute"], { encoding: "utf-8" }).trim();
34
- } catch {
35
- throw new Error("Could not find volute binary on PATH");
36
- }
37
- }
38
- async function execInherit(cmd, args, options) {
39
- const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
40
- return new Promise((resolve, reject) => {
41
- const child = spawn(wrappedCmd, wrappedArgs, {
42
- cwd: options?.cwd,
43
- env: options?.env,
44
- stdio: "inherit"
45
- });
46
- child.on("error", reject);
47
- child.on("close", (code) => {
48
- if (code === 0) resolve();
49
- else reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
50
- });
51
- });
52
- }
53
-
54
- export {
55
- exec,
56
- gitExec,
57
- resolveVoluteBin,
58
- execInherit
59
- };
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- publish
4
- } from "./chunk-P27RV5WM.js";
5
- import {
6
- logger_default
7
- } from "./chunk-YUIHSKR6.js";
8
- import {
9
- mindDir,
10
- readRegistry,
11
- voluteHome
12
- } from "./chunk-HHTXM4JT.js";
13
-
14
- // src/lib/pages-watcher.ts
15
- import { existsSync, readdirSync, statSync, watch } from "fs";
16
- import { join, resolve } from "path";
17
- var watchers = /* @__PURE__ */ new Map();
18
- var homeWatchers = /* @__PURE__ */ new Map();
19
- var debounceTimers = /* @__PURE__ */ new Map();
20
- var sitesCache = null;
21
- var recentPagesCache = null;
22
- function startPagesWatcher(mindName, pagesDir) {
23
- try {
24
- const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
25
- if (!filename || !filename.endsWith(".html")) return;
26
- const key = `${mindName}:${filename}`;
27
- const existing = debounceTimers.get(key);
28
- if (existing) clearTimeout(existing);
29
- debounceTimers.set(
30
- key,
31
- setTimeout(() => {
32
- debounceTimers.delete(key);
33
- invalidateCache();
34
- publish({
35
- type: "page_updated",
36
- mind: mindName,
37
- summary: `${mindName} updated ${filename}`,
38
- metadata: { file: filename }
39
- }).catch(
40
- (err) => logger_default.error("failed to publish page_updated activity", logger_default.errorData(err))
41
- );
42
- }, 100)
43
- );
44
- });
45
- watchers.set(mindName, watcher);
46
- } catch (err) {
47
- logger_default.warn(`failed to start pages watcher for ${mindName}`, logger_default.errorData(err));
48
- }
49
- }
50
- function startSystemWatcher() {
51
- if (watchers.has("_system")) return;
52
- const systemPagesDir = resolve(voluteHome(), "shared", "pages");
53
- if (!existsSync(systemPagesDir)) return;
54
- startPagesWatcher("_system", systemPagesDir);
55
- }
56
- function startWatcher(mindName) {
57
- if (watchers.has(mindName)) return;
58
- const pagesDir = resolve(mindDir(mindName), "home", "public", "pages");
59
- if (existsSync(pagesDir)) {
60
- startPagesWatcher(mindName, pagesDir);
61
- return;
62
- }
63
- if (homeWatchers.has(mindName)) return;
64
- const publicDir = resolve(mindDir(mindName), "home", "public");
65
- if (!existsSync(publicDir)) return;
66
- try {
67
- const hw = watch(publicDir, (_eventType, filename) => {
68
- if (filename !== "pages") return;
69
- if (!existsSync(pagesDir)) return;
70
- hw.close();
71
- homeWatchers.delete(mindName);
72
- invalidateCache();
73
- startPagesWatcher(mindName, pagesDir);
74
- });
75
- homeWatchers.set(mindName, hw);
76
- } catch (err) {
77
- logger_default.warn(`failed to start home watcher for ${mindName}`, logger_default.errorData(err));
78
- }
79
- }
80
- function stopWatcher(mindName) {
81
- const watcher = watchers.get(mindName);
82
- if (watcher) {
83
- watcher.close();
84
- watchers.delete(mindName);
85
- }
86
- const hw = homeWatchers.get(mindName);
87
- if (hw) {
88
- hw.close();
89
- homeWatchers.delete(mindName);
90
- }
91
- for (const [key, timer] of debounceTimers) {
92
- if (key.startsWith(`${mindName}:`)) {
93
- clearTimeout(timer);
94
- debounceTimers.delete(key);
95
- }
96
- }
97
- }
98
- function stopAllWatchers() {
99
- for (const [, watcher] of watchers) {
100
- watcher.close();
101
- }
102
- watchers.clear();
103
- for (const [, hw] of homeWatchers) {
104
- hw.close();
105
- }
106
- homeWatchers.clear();
107
- for (const [, timer] of debounceTimers) {
108
- clearTimeout(timer);
109
- }
110
- debounceTimers.clear();
111
- invalidateCache();
112
- }
113
- function invalidateCache() {
114
- sitesCache = null;
115
- recentPagesCache = null;
116
- }
117
- function scanPagesDir(dir, urlPrefix) {
118
- const pages = [];
119
- let items;
120
- try {
121
- items = readdirSync(dir);
122
- } catch {
123
- return pages;
124
- }
125
- for (const item of items) {
126
- if (item.startsWith(".")) continue;
127
- const fullPath = resolve(dir, item);
128
- try {
129
- const s = statSync(fullPath);
130
- if (s.isFile() && item.endsWith(".html")) {
131
- pages.push({
132
- file: item,
133
- modified: s.mtime.toISOString(),
134
- url: `${urlPrefix}/${item}`
135
- });
136
- } else if (s.isDirectory()) {
137
- const indexPath = resolve(fullPath, "index.html");
138
- if (existsSync(indexPath)) {
139
- const indexStat = statSync(indexPath);
140
- pages.push({
141
- file: join(item, "index.html"),
142
- modified: indexStat.mtime.toISOString(),
143
- url: `${urlPrefix}/${item}/`
144
- });
145
- }
146
- }
147
- } catch {
148
- }
149
- }
150
- pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
151
- return pages;
152
- }
153
- async function buildSites() {
154
- const sites = [];
155
- const systemPagesDir = resolve(voluteHome(), "shared", "pages");
156
- if (existsSync(systemPagesDir)) {
157
- const systemPages = scanPagesDir(systemPagesDir, "/pages/_system");
158
- if (systemPages.length > 0) {
159
- sites.push({ name: "_system", label: "System", pages: systemPages });
160
- }
161
- }
162
- const entries = await readRegistry();
163
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
164
- const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
165
- if (!existsSync(pagesDir)) continue;
166
- const mindPages = scanPagesDir(pagesDir, `/minds/${entry.name}/pages`);
167
- if (mindPages.length > 0) {
168
- sites.push({ name: entry.name, label: entry.name, pages: mindPages });
169
- }
170
- }
171
- return sites;
172
- }
173
- async function buildRecentPages() {
174
- const entries = await readRegistry();
175
- const pages = [];
176
- for (const entry of entries) {
177
- const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
178
- if (!existsSync(pagesDir)) continue;
179
- let items;
180
- try {
181
- items = readdirSync(pagesDir);
182
- } catch {
183
- continue;
184
- }
185
- for (const item of items) {
186
- if (item.startsWith(".")) continue;
187
- const fullPath = resolve(pagesDir, item);
188
- try {
189
- const s = statSync(fullPath);
190
- if (s.isFile() && item.endsWith(".html")) {
191
- pages.push({
192
- mind: entry.name,
193
- file: item,
194
- modified: s.mtime.toISOString(),
195
- url: `/minds/${entry.name}/pages/${item}`
196
- });
197
- } else if (s.isDirectory()) {
198
- const indexPath = resolve(fullPath, "index.html");
199
- if (existsSync(indexPath)) {
200
- const indexStat = statSync(indexPath);
201
- pages.push({
202
- mind: entry.name,
203
- file: join(item, "index.html"),
204
- modified: indexStat.mtime.toISOString(),
205
- url: `/minds/${entry.name}/pages/${item}/`
206
- });
207
- }
208
- }
209
- } catch {
210
- }
211
- }
212
- }
213
- pages.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
214
- return pages.slice(0, 10);
215
- }
216
- async function getCachedSites() {
217
- if (!sitesCache) sitesCache = await buildSites();
218
- return sitesCache;
219
- }
220
- async function getCachedRecentPages() {
221
- if (!recentPagesCache) recentPagesCache = await buildRecentPages();
222
- return recentPagesCache;
223
- }
224
-
225
- export {
226
- startSystemWatcher,
227
- startWatcher,
228
- stopWatcher,
229
- stopAllWatchers,
230
- getCachedSites,
231
- getCachedRecentPages
232
- };