volute 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +79 -328
  2. package/dist/{accept-KYNC5X3U.js → accept-5NK5XJB5.js} +2 -1
  3. package/dist/{activity-events-MIOK3IOU.js → activity-events-H7YI3BTF.js} +4 -4
  4. package/dist/{ai-service-643TNFPH.js → ai-service-V4CTNGI3.js} +6 -5
  5. package/dist/{archive-6Z4GFQPH.js → archive-ORM3ENHI.js} +3 -3
  6. package/dist/{auth-A4VGHADI.js → auth-DT7GY7EZ.js} +11 -7
  7. package/dist/{avatar-image-Y45R7LW5.js → avatar-image-QEPAP3A5.js} +4 -4
  8. package/dist/{backup-UDH4GMSW.js → backup-IE6PMXJO.js} +17 -18
  9. package/dist/{bridge-3FNSWLNQ.js → bridge-YK7NTRU5.js} +2 -1
  10. package/dist/{channels-SGQZHNQJ.js → channels-FSAHOQWI.js} +2 -1
  11. package/dist/{chat-X43VYHH2.js → chat-W5K6HCBE.js} +9 -9
  12. package/dist/{chunk-LTLFOSA4.js → chunk-3BAFMGCJ.js} +3 -3
  13. package/dist/{chunk-XVBSFLM7.js → chunk-4CN7OCJE.js} +2 -2
  14. package/dist/{chunk-HPGN6XRT.js → chunk-574M6I7F.js} +39 -6
  15. package/dist/{chunk-FJ2MZ5PH.js → chunk-5FVANDON.js} +2 -2
  16. package/dist/{chunk-2MJ7EOMT.js → chunk-5IIVOWSP.js} +28 -19
  17. package/dist/{chunk-4FCL4DVZ.js → chunk-5PKFJHCR.js} +2 -2
  18. package/dist/{chunk-MOERAQXH.js → chunk-5TPC4KCV.js} +38 -31
  19. package/dist/{chunk-YNBXEQAP.js → chunk-632FJ23O.js} +24 -20
  20. package/dist/{chunk-T4VS7UDW.js → chunk-636GFQNE.js} +12 -12
  21. package/dist/{chunk-EIQDZC43.js → chunk-6BM2I736.js} +25 -6
  22. package/dist/{chunk-WARH6GIV.js → chunk-74T3CEXK.js} +17 -15
  23. package/dist/{chunk-OQJMTHEH.js → chunk-77IZUVBB.js} +2 -2
  24. package/dist/{chunk-USD45JKR.js → chunk-7DXLIYPY.js} +77 -64
  25. package/dist/chunk-7FDWPQCU.js +289 -0
  26. package/dist/chunk-7OZWKHA3.js +548 -0
  27. package/dist/{chunk-IJPMIEVE.js → chunk-7UWXAJMK.js} +1 -1
  28. package/dist/{chunk-SZJCJVXV.js → chunk-7XDOTN4R.js} +2 -2
  29. package/dist/{chunk-YKA7DCVW.js → chunk-AFSLNUTT.js} +1 -1
  30. package/dist/{chunk-DGGZVZTM.js → chunk-AJWFDIEY.js} +17 -2
  31. package/dist/chunk-ALIVXDM4.js +1014 -0
  32. package/dist/chunk-CAIYV33P.js +523 -0
  33. package/dist/{chunk-MTRMQFCQ.js → chunk-E5S3ILVA.js} +2 -2
  34. package/dist/{chunk-SHKM5RGD.js → chunk-EEOMA4DX.js} +1 -1
  35. package/dist/chunk-EQKSRVDE.js +2857 -0
  36. package/dist/chunk-FNJCBRD3.js +31 -0
  37. package/dist/chunk-HW6Q7AMB.js +113 -0
  38. package/dist/chunk-JI67D7O2.js +1222 -0
  39. package/dist/{chunk-3YUBFTDN.js → chunk-KAEZOVP6.js} +1 -1
  40. package/dist/chunk-LNNI3MS4.js +54 -0
  41. package/dist/{chunk-BCG45AQB.js → chunk-M74I3HHF.js} +15 -5
  42. package/dist/chunk-MLZBBLMT.js +109 -0
  43. package/dist/{chunk-5T5YMX6S.js → chunk-PW65BXY5.js} +5 -0
  44. package/dist/{chunk-2OIYNHWO.js → chunk-QFVGPKJI.js} +1 -1
  45. package/dist/{chunk-T2TP6ZC6.js → chunk-QUSMYGNQ.js} +6 -0
  46. package/dist/{chunk-HNGFKPNS.js → chunk-SFRYRW37.js} +85 -36
  47. package/dist/{chunk-7KRIHYLR.js → chunk-SSNZ6ZNN.js} +2 -2
  48. package/dist/{chunk-HUNDXZBZ.js → chunk-TGJHGVBZ.js} +48 -19
  49. package/dist/{chunk-PJVPDY2Z.js → chunk-U3EJJ7JN.js} +2 -2
  50. package/dist/{chunk-ODDGGLM6.js → chunk-UC6BNMGT.js} +43 -13
  51. package/dist/{chunk-Q7HOAVFR.js → chunk-WBBKYUGF.js} +4 -6
  52. package/dist/{chunk-WQ46E33M.js → chunk-X67VT4QO.js} +2 -2
  53. package/dist/{chunk-MJRXFUMS.js → chunk-YQOYZ6G7.js} +3 -5
  54. package/dist/{chunk-VZRA5TYO.js → chunk-ZUG76D3P.js} +36 -6
  55. package/dist/cli.js +34 -27
  56. package/dist/{clock-CNRF6F7T.js → clock-LH7UFNLJ.js} +5 -4
  57. package/dist/{cloud-sync-WHKCB4CD.js → cloud-sync-IDJFVDEP.js} +23 -20
  58. package/dist/{config-RPSLA2HX.js → config-UGJS57VT.js} +3 -3
  59. package/dist/{conversations-C7IKYSHD.js → conversations-S2232Y5I.js} +9 -5
  60. package/dist/{create-GJKHABJB.js → create-2M7DAHQX.js} +2 -2
  61. package/dist/{create-VIQVP3QB.js → create-M727VUDF.js} +2 -1
  62. package/dist/{daemon-client-ZFY5MOAL.js → daemon-client-VVYFZI2D.js} +2 -1
  63. package/dist/{daemon-restart-7KUHVZIN.js → daemon-restart-2AAGYFHE.js} +6 -7
  64. package/dist/daemon.js +2166 -833
  65. package/dist/{db-FO6LXYCW.js → db-QAY45Z56.js} +2 -2
  66. package/dist/{delete-ESEP7NGQ.js → delete-O7PEFQDF.js} +1 -1
  67. package/dist/delivery-manager-B4OKWD2P.js +33 -0
  68. package/dist/{delivery-router-3QFG2ZEX.js → delivery-router-JE7SHYNU.js} +10 -6
  69. package/dist/{down-G6IADJXV.js → down-D4IRNEMJ.js} +5 -5
  70. package/dist/echo-text-WYAAODBV.js +39 -0
  71. package/dist/{env-C7Z55QCJ.js → env-ABM5IMA7.js} +8 -7
  72. package/dist/{env-6TGPO5WG.js → env-AZK5X4W7.js} +3 -3
  73. package/dist/{exec-QIFYZG4C.js → exec-2H65LMTA.js} +3 -3
  74. package/dist/{export-C2K3NIRD.js → export-3E3EL6W3.js} +4 -4
  75. package/dist/{extension-J4ZQ2GHV.js → extension-MRYJWSMV.js} +2 -1
  76. package/dist/{extensions-GEKF47A4.js → extensions-7KP3X6G7.js} +23 -20
  77. package/dist/{files-LXQSQOFG.js → files-6BMKVG5G.js} +5 -4
  78. package/dist/{import-SX3ZXHB4.js → import-UPNTEBBT.js} +9 -9
  79. package/dist/{isolation-MCT4ZYLT.js → isolation-OKDFM6WA.js} +5 -3
  80. package/dist/{join-34TR2GXQ.js → join-PYA36HEC.js} +1 -1
  81. package/dist/{list-PMUXZNAM.js → list-4BVGH7AN.js} +8 -3
  82. package/dist/{login-CL4IT2ED.js → login-MOXVLULE.js} +7 -6
  83. package/dist/{login-ARRZ4ORJ.js → login-RJJTCVR3.js} +4 -3
  84. package/dist/{logout-YZRMZ3O2.js → logout-BYV433X6.js} +2 -1
  85. package/dist/{logout-HZYJCYCA.js → logout-DWD3OQVT.js} +4 -3
  86. package/dist/message-delivery-3456PXB7.js +43 -0
  87. package/dist/migrate-thread-config-4A5LW36Z.js +90 -0
  88. package/dist/{mind-46AOK373.js → mind-NYRALSV2.js} +19 -19
  89. package/dist/{mind-activity-tracker-YVZPC4A5.js → mind-activity-tracker-L7GCYXQR.js} +5 -5
  90. package/dist/{mind-contacts-GV7XYL5D.js → mind-contacts-QYU4TPEA.js} +8 -7
  91. package/dist/{mind-history-TTGA5BHZ.js → mind-history-NXXOOTX6.js} +34 -11
  92. package/dist/{mind-list-OTHV4ZO4.js → mind-list-SYRA7CH5.js} +3 -2
  93. package/dist/{mind-profile-LUG353ZT.js → mind-profile-MRCVZPYT.js} +1 -1
  94. package/dist/mind-service-5TA7UJAT.js +47 -0
  95. package/dist/{mind-sleep-DV7KP255.js → mind-sleep-TH6BRQ5X.js} +5 -4
  96. package/dist/{mind-status-BJNPGXKC.js → mind-status-Z2LQFA6F.js} +3 -2
  97. package/dist/{mind-wake-LVTZ55EZ.js → mind-wake-LDGPRA42.js} +2 -1
  98. package/dist/{package-RTN2PS7O.js → package-XYSV7NUT.js} +27 -26
  99. package/dist/prompts-QKFPX6DQ.js +24 -0
  100. package/dist/{read-YSWUXYL7.js → read-ZXGUT4QC.js} +5 -3
  101. package/dist/{register-42VBJKTP.js → register-OX4HRSGC.js} +4 -3
  102. package/dist/{registry-UMMDRPRM.js → registry-4UF6OCOE.js} +10 -4
  103. package/dist/{reject-SXGCYN4A.js → reject-TWB4QJHG.js} +2 -1
  104. package/dist/{restart-YYEGZJDD.js → restart-AGEGNSYQ.js} +6 -5
  105. package/dist/{sandbox-5VQ454XP.js → sandbox-EGEU5GGE.js} +8 -5
  106. package/dist/scheduler-UV6RUJ7P.js +25 -0
  107. package/dist/{schema-VEVO7HG6.js → schema-C2GIPD4V.js} +3 -3
  108. package/dist/{seed-F2HXZQU4.js → seed-3IMBOAGK.js} +1 -1
  109. package/dist/{seed-check-TYD7RRZY.js → seed-check-NRJEQZST.js} +14 -5
  110. package/dist/{seed-cmd-MLCTGSX5.js → seed-cmd-U73XVQMI.js} +3 -3
  111. package/dist/{seed-create-KQ7OILWB.js → seed-create-FBLNVSUP.js} +4 -4
  112. package/dist/{seed-readiness-RBPGOLSZ.js → seed-readiness-QRA46ZJQ.js} +4 -5
  113. package/dist/{seed-sprout-HVYCFJLA.js → seed-sprout-3B4SZX7J.js} +12 -12
  114. package/dist/{send-TD2X5U3K.js → send-NBRZJXKX.js} +34 -12
  115. package/dist/{service-CVZMQYK6.js → service-XMXAXGJD.js} +4 -4
  116. package/dist/{service-install-VCRMFWX5.js → service-install-MOKTEG7B.js} +5 -5
  117. package/dist/{setup-5ECWTYKG.js → setup-CAJFOR2N.js} +12 -3
  118. package/dist/{setup-AE4CVUC4.js → setup-JGYAXXW7.js} +6 -7
  119. package/dist/{skill-UUM4JZRF.js → skill-NXIHDQCG.js} +7 -6
  120. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  121. package/dist/skills/imagegen/SKILL.md +21 -4
  122. package/dist/skills/imagegen/scripts/imagegen.ts +124 -21
  123. package/dist/skills/tending/SKILL.md +7 -7
  124. package/dist/skills/volute-mind/SKILL.md +6 -6
  125. package/dist/skills/volute-mind/references/integrations.md +2 -2
  126. package/dist/skills/volute-mind/references/routing.md +19 -18
  127. package/dist/skills/volute-mind/references/variants.md +48 -14
  128. package/dist/{skills-B3SVDCEB.js → skills-5MSMT7PT.js} +7 -6
  129. package/dist/sleep-manager-LIEPO6WG.js +41 -0
  130. package/dist/spirit-IL5EMWYZ.js +34 -0
  131. package/dist/spirit-availability-YFEGJNJ2.js +42 -0
  132. package/dist/{split-SKZ4Y2D6.js → split-BU5VWX6P.js} +5 -2
  133. package/dist/{sprout-KQ43BWZJ.js → sprout-UPXGJ5HX.js} +1 -1
  134. package/dist/{src-YH6PQ7LJ.js → src-HGNE2XG4.js} +7 -1
  135. package/dist/{src-WLJE73NP.js → src-LCSOQY6Z.js} +60 -54
  136. package/dist/{src-JAOVXO5W.js → src-UDMJZ6MH.js} +8 -8
  137. package/dist/{start-O6UKK7BG.js → start-MUMNFR52.js} +4 -3
  138. package/dist/{status-F4PGREUU.js → status-IL67CYM4.js} +7 -6
  139. package/dist/{stop-UBX6X5RA.js → stop-EKERNYTX.js} +6 -5
  140. package/dist/system-events-A6IGCEC3.js +46 -0
  141. package/dist/system-install-YZSWCPDK.js +8 -0
  142. package/dist/{systems-JJNGQHAO.js → systems-VDRUZXNF.js} +5 -4
  143. package/dist/{tailscale-MCLY5GYE.js → tailscale-SJUW2ETR.js} +3 -3
  144. package/dist/{template-staleness-EMQORSGG.js → template-staleness-T27VCGYA.js} +4 -4
  145. package/dist/{turn-tracker-KLPV3PAU.js → turn-tracker-6EI2EL7G.js} +7 -7
  146. package/dist/{up-RVGDE6CQ.js → up-CLL4665A.js} +5 -6
  147. package/dist/{update-OXQEGGXD.js → update-HVAUCQPN.js} +5 -5
  148. package/dist/{update-check-266R4ZSY.js → update-check-2MGBBAFF.js} +3 -3
  149. package/dist/{upgrade-K5FIQPQD.js → upgrade-LIE46UBB.js} +1 -1
  150. package/dist/variant-cleanup-HJ7Z75R6.js +22 -0
  151. package/dist/{version-notify-73YURTFR.js → version-notify-7BUHWZNT.js} +11 -28
  152. package/dist/web-assets/assets/index-ClsUcXiW.js +95 -0
  153. package/dist/web-assets/assets/index-Dz3wTbtR.css +1 -0
  154. package/dist/web-assets/index.html +2 -2
  155. package/dist/xai-P42CRCZA.js +129 -0
  156. package/drizzle/0013_variant_purpose.sql +3 -0
  157. package/drizzle/0014_system_events.sql +26 -0
  158. package/drizzle/0015_rename_session_to_thread.sql +12 -0
  159. package/drizzle/0016_brain_to_human.sql +4 -0
  160. package/drizzle/0017_event_history_rows.sql +9 -0
  161. package/drizzle/meta/_journal.json +35 -0
  162. package/package.json +27 -26
  163. package/packages/extensions/notes/dist/ui/assets/index-BBs8pyel.js +67 -0
  164. package/packages/extensions/notes/dist/ui/index.html +1 -1
  165. package/packages/extensions/pages/dist/ui/assets/index-B589e8Xe.js +2 -0
  166. package/packages/extensions/pages/dist/ui/index.html +1 -1
  167. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +67 -0
  168. package/packages/extensions/plan/dist/ui/index.html +1 -1
  169. package/templates/_base/.init/.config/prompts.json +1 -0
  170. package/templates/_base/.init/.local/hooks/startup-context.ts +34 -2
  171. package/templates/_base/home/.config/routes.json +2 -2
  172. package/templates/_base/home/VOLUTE.md +30 -5
  173. package/templates/_base/src/lib/daemon-client.ts +28 -8
  174. package/templates/_base/src/lib/event-turn.ts +39 -0
  175. package/templates/_base/src/lib/format-prefix.ts +21 -2
  176. package/templates/_base/src/lib/router.ts +6 -2
  177. package/templates/_base/src/lib/routing.ts +8 -8
  178. package/templates/_base/src/lib/startup.ts +9 -1
  179. package/templates/_base/src/lib/types.ts +7 -1
  180. package/templates/_base/src/lib/volute-server.ts +30 -1
  181. package/templates/claude/.init/.config/routes.json +3 -3
  182. package/templates/claude/.init/CLAUDE.md +1 -1
  183. package/templates/claude/package.json.tmpl +4 -4
  184. package/templates/claude/src/agent.ts +5 -2
  185. package/templates/claude/src/lib/hooks/identity-reload.ts +12 -3
  186. package/templates/claude/src/lib/hooks/reply-instructions.ts +26 -1
  187. package/templates/codex/.init/.config/routes.json +3 -3
  188. package/templates/codex/.init/AGENTS.md +1 -1
  189. package/templates/codex/package.json.tmpl +4 -4
  190. package/templates/codex/src/agent.ts +11 -11
  191. package/templates/codex/src/lib/turn-context.ts +51 -0
  192. package/templates/pi/.init/.config/routes.json +3 -3
  193. package/templates/pi/.init/MINDS.md +1 -1
  194. package/templates/pi/package.json.tmpl +6 -6
  195. package/templates/pi/src/agent.ts +9 -1
  196. package/templates/pi/src/lib/event-handler.ts +47 -6
  197. package/templates/pi/src/lib/reply-instructions-extension.ts +28 -1
  198. package/dist/chunk-5KXCM5AW.js +0 -169
  199. package/dist/chunk-IYCV34IU.js +0 -158
  200. package/dist/chunk-VB2QXUJQ.js +0 -5260
  201. package/dist/chunk-WW6C3PLV.js +0 -280
  202. package/dist/delivery-manager-HZTT5SJR.js +0 -36
  203. package/dist/echo-text-H3SH337Q.js +0 -36
  204. package/dist/message-delivery-GPQ5SE5N.js +0 -40
  205. package/dist/mind-service-2ARGJNKP.js +0 -44
  206. package/dist/notices-2KU5NTRY.js +0 -25
  207. package/dist/scheduler-I7JZCT47.js +0 -34
  208. package/dist/sleep-manager-Z27R3WTW.js +0 -38
  209. package/dist/spirit-WXS3HCLN.js +0 -25
  210. package/dist/system-chat-63ABFF6V.js +0 -40
  211. package/dist/web-assets/assets/index-WdZ60R6c.css +0 -1
  212. package/dist/web-assets/assets/index-taX6Ezfa.js +0 -92
  213. package/packages/extensions/notes/dist/ui/assets/index-D0-DgYyj.js +0 -67
  214. package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +0 -2
  215. package/packages/extensions/plan/dist/ui/assets/index-Cfzs-0G9.js +0 -67
@@ -137,15 +137,28 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
137
137
 
138
138
  if (event.type === "agent_end") {
139
139
  flushBuffers();
140
- if (session.currentMessageId) {
141
- session.messageChannels.delete(session.currentMessageId);
140
+ // Capture the turn's message context before clearing it: the ordered error/done
141
+ // emits below run asynchronously, but currentMessageId must be reset synchronously
142
+ // so the next turn shifts a fresh id (see the currentMessageId guard above).
143
+ const messageId = session.currentMessageId;
144
+ if (messageId) {
145
+ session.messageChannels.delete(messageId);
142
146
  }
147
+ const channel = messageId ? session.messageChannels.get(messageId)?.channel : undefined;
143
148
  log("mind", `session "${session.name}": turn done`);
144
- // Log any error messages from the agent
149
+ // Collect any agent-level errors (e.g. a provider 401 reported inside agent_end).
150
+ // Warn-logging alone left the failure invisible at every host surface (#619);
151
+ // we emit a turn_error to the daemon too — mirroring the dispatch-rejection path in
152
+ // agent.ts — so classify() tags it and chat status + lastError surface it. Dedupe
153
+ // identical messages so a turn with several failed messages records one notice.
154
+ const errorMessages: string[] = [];
145
155
  if (event.messages) {
156
+ const seen = new Set<string>();
146
157
  for (const msg of event.messages as AgentEndMessage[]) {
147
- if (msg.errorMessage) {
158
+ if (msg.errorMessage && !seen.has(msg.errorMessage)) {
159
+ seen.add(msg.errorMessage);
148
160
  warn("mind", `session "${session.name}": agent error: ${msg.errorMessage}`);
161
+ errorMessages.push(msg.errorMessage);
149
162
  }
150
163
  }
151
164
  }
@@ -175,14 +188,42 @@ export function createEventHandler(session: EventSession, options: EventHandlerO
175
188
  }
176
189
  }
177
190
  options.broadcast({ type: "done" });
178
- emit(session, { type: "done" });
179
191
  session.currentMessageId = undefined;
192
+ // Emit any errors BEFORE done, awaiting each in turn. daemonEmit is fire-and-forget,
193
+ // so without ordered awaits the daemon could process `done` before `error`:
194
+ // markErrored would land after done consumed the errored flag, leaking it into the
195
+ // next turn (whose clean done then wrongly skips clearing delivered notices). This
196
+ // mirrors agent.ts's awaited error-then-done ordering. Message context is captured
197
+ // above rather than read from the (now-cleared) session.
198
+ void (async () => {
199
+ for (const content of errorMessages) {
200
+ const ev = filterEvent(preset, {
201
+ type: "error",
202
+ session: session.name,
203
+ channel,
204
+ messageId,
205
+ content,
206
+ });
207
+ if (ev) await daemonEmit(ev);
208
+ }
209
+ const doneEv = filterEvent(preset, {
210
+ type: "done",
211
+ session: session.name,
212
+ channel,
213
+ messageId,
214
+ });
215
+ if (doneEv) await daemonEmit(doneEv);
216
+ })().catch((err) =>
217
+ warn("mind", `session "${session.name}": error/done emit failed:`, err),
218
+ );
180
219
  flushFileChanges(options.cwd)
181
220
  .then(() => options.onTurnEnd?.())
182
221
  .catch((err) => log("mind", `session "${session.name}": flush/turn-end error:`, err));
183
222
  }
184
223
  } catch (err) {
185
- log("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
224
+ // warn-level: this block now drives turn-completion (error/done emission), so a throw
225
+ // here can hang a turn — it must not be buried at debug level.
226
+ warn("mind", `session "${session.name}": event handler error (${event?.type}):`, err);
186
227
  }
187
228
  };
188
229
  }
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
2
2
  import type { EventSession } from "./event-handler.js";
3
+ import { firstReplyableEntry, isEventTurn } from "./event-turn.js";
3
4
  import { log } from "./logger.js";
4
5
  import { loadPrompts } from "./startup.js";
5
6
 
@@ -14,11 +15,37 @@ export function createReplyInstructionsExtension(
14
15
  const prompts = loadPrompts();
15
16
  return (pi) => {
16
17
  let fired = false;
18
+ let eventNoteFired = false;
17
19
  pi.on("before_agent_start", () => {
18
20
  try {
21
+ const entries = [...messageChannels.values()];
22
+
23
+ // System-event turn: ambient, from the environment, with no channel to reply to.
24
+ // The note fires once per session (see event-turn.ts for why).
25
+ if (isEventTurn(entries)) {
26
+ if (eventNoteFired) return {};
27
+ eventNoteFired = true;
28
+ const content = prompts.event_instructions;
29
+ if (emitContext && session) {
30
+ emitContext(session, {
31
+ type: "context",
32
+ content,
33
+ metadata: { source: "event-instructions" },
34
+ });
35
+ }
36
+ return {
37
+ message: {
38
+ customType: "event-instructions",
39
+ content,
40
+ display: true,
41
+ },
42
+ };
43
+ }
44
+
19
45
  if (fired) return {};
20
46
 
21
- const entry = messageChannels.values().next().value;
47
+ // Reply instructions must name a channel the mind can actually send to.
48
+ const entry = firstReplyableEntry(entries);
22
49
  if (!entry) return {};
23
50
 
24
51
  fired = true;
@@ -1,169 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- voluteSystemDir
4
- } from "./chunk-WW6C3PLV.js";
5
-
6
- // packages/daemon/src/lib/config/setup.ts
7
- import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
8
- import { resolve } from "path";
9
- function configPath() {
10
- return resolve(voluteSystemDir(), "config.json");
11
- }
12
- function secretsPath() {
13
- return resolve(voluteSystemDir(), "secrets.json");
14
- }
15
- function hasEntries(obj) {
16
- return !!obj && Object.keys(obj).length > 0;
17
- }
18
- function splitConfig(config) {
19
- const publicConfig = { ...config };
20
- const secrets = {};
21
- if (config.ai) {
22
- const { providers, ...rest } = config.ai;
23
- publicConfig.ai = { ...rest, providers: {} };
24
- if (hasEntries(providers)) secrets.ai = { providers };
25
- }
26
- if (config.imagegen) {
27
- const { providers, ...rest } = config.imagegen;
28
- publicConfig.imagegen = { ...rest };
29
- if (providers && Object.keys(providers).length > 0) secrets.imagegen = { providers };
30
- }
31
- if (config.backup) {
32
- const { password, env, ...rest } = config.backup;
33
- publicConfig.backup = { ...rest };
34
- if (password || hasEntries(env)) {
35
- secrets.backup = {};
36
- if (password) secrets.backup.password = password;
37
- if (hasEntries(env)) secrets.backup.env = env;
38
- }
39
- }
40
- return { publicConfig, secrets };
41
- }
42
- function mergeSecrets(publicConfig, secrets) {
43
- const merged = { ...publicConfig };
44
- if (secrets.ai?.providers) {
45
- merged.ai = { ...merged.ai ?? { providers: {} }, providers: secrets.ai.providers };
46
- }
47
- if (secrets.imagegen?.providers) {
48
- merged.imagegen = { ...merged.imagegen ?? {}, providers: secrets.imagegen.providers };
49
- }
50
- if (secrets.backup) {
51
- merged.backup = { ...merged.backup ?? {}, ...secrets.backup };
52
- }
53
- return merged;
54
- }
55
- function readSecrets() {
56
- const path = secretsPath();
57
- if (!existsSync(path)) return {};
58
- try {
59
- return JSON.parse(readFileSync(path, "utf-8"));
60
- } catch {
61
- return {};
62
- }
63
- }
64
- var _cachedConfig = null;
65
- var CONFIG_CACHE_TTL = 2e3;
66
- function _resetConfigCache() {
67
- _cachedConfig = null;
68
- }
69
- function readGlobalConfig() {
70
- if (_cachedConfig && Date.now() - _cachedConfig.ts < CONFIG_CACHE_TTL) {
71
- return { ..._cachedConfig.config };
72
- }
73
- const path = configPath();
74
- if (!existsSync(path)) {
75
- _cachedConfig = null;
76
- return {};
77
- }
78
- let publicConfig;
79
- try {
80
- publicConfig = JSON.parse(readFileSync(path, "utf-8"));
81
- } catch (err) {
82
- console.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
83
- return {};
84
- }
85
- const config = mergeSecrets(publicConfig, readSecrets());
86
- _cachedConfig = { config, ts: Date.now() };
87
- return config;
88
- }
89
- function writeGlobalConfig(config) {
90
- mkdirSync(voluteSystemDir(), { recursive: true });
91
- const { publicConfig, secrets } = splitConfig(config);
92
- const cfgPath = configPath();
93
- writeFileSync(cfgPath, `${JSON.stringify(publicConfig, null, 2)}
94
- `, { mode: 420 });
95
- chmodSync(cfgPath, 420);
96
- const secPath = secretsPath();
97
- writeFileSync(secPath, `${JSON.stringify(secrets, null, 2)}
98
- `, { mode: 384 });
99
- chmodSync(secPath, 384);
100
- _cachedConfig = { config, ts: Date.now() };
101
- }
102
- function migrateConfigSecrets() {
103
- const path = configPath();
104
- if (!existsSync(path)) return;
105
- let raw;
106
- try {
107
- raw = readFileSync(path, "utf-8");
108
- } catch {
109
- return;
110
- }
111
- let parsed;
112
- try {
113
- parsed = JSON.parse(raw);
114
- } catch {
115
- return;
116
- }
117
- const embedsSecrets = hasEntries(parsed.ai?.providers) || hasEntries(parsed.imagegen?.providers) || !!parsed.backup?.password || hasEntries(parsed.backup?.env);
118
- const mode = statSync(path).mode & 511;
119
- if (!embedsSecrets && mode === 420) return;
120
- _resetConfigCache();
121
- writeGlobalConfig(readGlobalConfig());
122
- }
123
- function setupStatus() {
124
- const config = readGlobalConfig();
125
- if (config.setupCompleted === true) return "complete";
126
- if (config.setup != null && config.setupCompleted == null) return "complete";
127
- if (config.setup != null) return "in-progress";
128
- return "none";
129
- }
130
- function isSetupComplete() {
131
- return setupStatus() === "complete";
132
- }
133
- function setupUrl() {
134
- const config = readGlobalConfig();
135
- const port = config.port || 1618;
136
- let host = config.hostname || "127.0.0.1";
137
- if (host === "0.0.0.0" || host === "::") host = "localhost";
138
- if (host.includes(":") && !host.startsWith("[")) host = `[${host}]`;
139
- return `http://${host}:${port}`;
140
- }
141
- function isImagegenEnabled() {
142
- const config = readGlobalConfig();
143
- const ig = config.imagegen;
144
- if (!ig) return false;
145
- if (ig.enabled === true) return true;
146
- if (ig.providers && Object.keys(ig.providers).length > 0) return true;
147
- return false;
148
- }
149
- function migrateSetupCompleted() {
150
- const config = readGlobalConfig();
151
- if (config.setup != null && config.setupCompleted == null) {
152
- config.setupCompleted = true;
153
- writeGlobalConfig(config);
154
- }
155
- }
156
-
157
- export {
158
- configPath,
159
- secretsPath,
160
- _resetConfigCache,
161
- readGlobalConfig,
162
- writeGlobalConfig,
163
- migrateConfigSecrets,
164
- setupStatus,
165
- isSetupComplete,
166
- setupUrl,
167
- isImagegenEnabled,
168
- migrateSetupCompleted
169
- };
@@ -1,158 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- logger_default
4
- } from "./chunk-T2TP6ZC6.js";
5
- import {
6
- getDb
7
- } from "./chunk-WW6C3PLV.js";
8
- import {
9
- mindNotices,
10
- turns
11
- } from "./chunk-2MJ7EOMT.js";
12
-
13
- // packages/daemon/src/lib/daemon/notices.ts
14
- import { and, asc, desc, eq, gt, inArray, lte, or, sql } from "drizzle-orm";
15
- var nlog = logger_default.child("notices");
16
- var MIND_LEVEL_SESSION = "";
17
- var MAX_NOTICES_PER_SESSION = 100;
18
- async function recordNotice(input) {
19
- try {
20
- const db = await getDb();
21
- await db.insert(mindNotices).values({
22
- mind: input.mind,
23
- session: input.session,
24
- kind: input.kind,
25
- reason: input.reason,
26
- detail: input.detail,
27
- raw: input.raw ?? null
28
- });
29
- await db.run(
30
- sql`DELETE FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} AND id NOT IN (SELECT id FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} ORDER BY id DESC LIMIT ${MAX_NOTICES_PER_SESSION})`
31
- );
32
- } catch (err) {
33
- nlog.error(`failed to record notice for ${input.mind}:${input.session}`, logger_default.errorData(err));
34
- }
35
- }
36
- async function drainNotices(mind, session, limit = MAX_NOTICES_PER_SESSION) {
37
- const db = await getDb();
38
- return db.select().from(mindNotices).where(
39
- and(
40
- eq(mindNotices.mind, mind),
41
- or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION))
42
- )
43
- ).orderBy(asc(mindNotices.id)).limit(limit);
44
- }
45
- var FAILURE_KINDS = ["turn_error", "crash", "startup"];
46
- var failureReadErrorLogged = false;
47
- async function latestFailureNotice(mind) {
48
- try {
49
- const db = await getDb();
50
- const rows = await db.select().from(mindNotices).where(and(eq(mindNotices.mind, mind), inArray(mindNotices.kind, [...FAILURE_KINDS]))).orderBy(desc(mindNotices.id)).limit(1);
51
- const n = rows[0];
52
- if (!n) return null;
53
- const recovered = await db.select({ id: turns.id }).from(turns).where(
54
- and(eq(turns.mind, mind), eq(turns.status, "complete"), gt(turns.created_at, n.created_at))
55
- ).limit(1);
56
- if (recovered.length > 0) return null;
57
- return {
58
- kind: n.kind,
59
- reason: n.reason,
60
- detail: n.detail,
61
- at: n.created_at
62
- };
63
- } catch (err) {
64
- if (!failureReadErrorLogged) {
65
- failureReadErrorLogged = true;
66
- nlog.error(`failed to read latest failure notice for ${mind}`, logger_default.errorData(err));
67
- }
68
- return null;
69
- }
70
- }
71
- async function latestNotice(mind) {
72
- const db = await getDb();
73
- const row = await db.select().from(mindNotices).where(eq(mindNotices.mind, mind)).orderBy(desc(mindNotices.id)).limit(1).get();
74
- return row ?? null;
75
- }
76
- async function hasUndeliveredNotice(mind, reason) {
77
- const db = await getDb();
78
- const row = await db.select({ id: mindNotices.id }).from(mindNotices).where(and(eq(mindNotices.mind, mind), eq(mindNotices.reason, reason))).limit(1).get();
79
- return row != null;
80
- }
81
- async function clearDeliveredNotices(mind, session, uptoId) {
82
- try {
83
- const db = await getDb();
84
- await db.delete(mindNotices).where(
85
- and(
86
- eq(mindNotices.mind, mind),
87
- or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION)),
88
- lte(mindNotices.id, uptoId)
89
- )
90
- );
91
- } catch (err) {
92
- nlog.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err));
93
- }
94
- }
95
- var NOTICE_HEADER = "[Notices] While you were unavailable, one or more turns failed. You're back now:";
96
- function formatNotices(notices) {
97
- if (notices.length === 0) return null;
98
- const failures = notices.filter((n) => n.kind !== "extension" && n.kind !== "budget");
99
- const budgets = notices.filter((n) => n.kind === "budget");
100
- const extensions = notices.filter((n) => n.kind === "extension");
101
- const blocks = [];
102
- if (failures.length > 0) {
103
- const groups = /* @__PURE__ */ new Map();
104
- for (const n of failures) {
105
- const time = localHM(n.created_at);
106
- const g = groups.get(n.reason);
107
- if (g) {
108
- g.count += 1;
109
- g.detail = n.detail;
110
- g.last = time;
111
- } else {
112
- groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
113
- }
114
- }
115
- const lines = [...groups.values()].map((g) => {
116
- const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
117
- const plural = g.count === 1 ? "turn" : "turns";
118
- return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
119
- });
120
- blocks.push(`${NOTICE_HEADER}
121
- ${lines.join("\n")}`);
122
- }
123
- if (budgets.length > 0) {
124
- const lines = budgets.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
125
- blocks.push(`[Budget]
126
- ${lines.join("\n")}`);
127
- }
128
- if (extensions.length > 0) {
129
- const byExt = /* @__PURE__ */ new Map();
130
- for (const n of extensions) {
131
- const arr = byExt.get(n.reason);
132
- if (arr) arr.push(n);
133
- else byExt.set(n.reason, [n]);
134
- }
135
- for (const [ext, items] of byExt) {
136
- const header = `[${ext.charAt(0).toUpperCase()}${ext.slice(1)}]`;
137
- const lines = items.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
138
- blocks.push(`${header}
139
- ${lines.join("\n")}`);
140
- }
141
- }
142
- return blocks.join("\n\n");
143
- }
144
- function localHM(createdAt) {
145
- const iso = createdAt.endsWith("Z") ? createdAt : `${createdAt.replace(" ", "T")}Z`;
146
- return new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
147
- }
148
-
149
- export {
150
- MIND_LEVEL_SESSION,
151
- recordNotice,
152
- drainNotices,
153
- latestFailureNotice,
154
- latestNotice,
155
- hasUndeliveredNotice,
156
- clearDeliveredNotices,
157
- formatNotices
158
- };