volute 0.50.0 → 0.52.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 (145) hide show
  1. package/dist/{activity-events-H7YI3BTF.js → activity-events-LQII6PZS.js} +3 -3
  2. package/dist/{ai-service-V4CTNGI3.js → ai-service-YW5SWCSB.js} +7 -3
  3. package/dist/{archive-ORM3ENHI.js → archive-FOHGXHPT.js} +3 -3
  4. package/dist/{auth-DT7GY7EZ.js → auth-KRDSM55Q.js} +6 -4
  5. package/dist/{avatar-image-QEPAP3A5.js → avatar-image-52WGXLHV.js} +3 -3
  6. package/dist/{backup-IE6PMXJO.js → backup-YVGV2ZNM.js} +7 -7
  7. package/dist/{chat-W5K6HCBE.js → chat-VAZ25KVT.js} +4 -4
  8. package/dist/{chunk-5IIVOWSP.js → chunk-2RNMBXD2.js} +16 -0
  9. package/dist/{chunk-U3EJJ7JN.js → chunk-5N32EN4O.js} +2 -2
  10. package/dist/{chunk-ZUG76D3P.js → chunk-5Y54AE3L.js} +1 -1
  11. package/dist/{chunk-5FVANDON.js → chunk-6NLW72HP.js} +2 -2
  12. package/dist/{chunk-ALIVXDM4.js → chunk-724DVYHU.js} +95 -51
  13. package/dist/{chunk-UC6BNMGT.js → chunk-7LT3YZUR.js} +15 -3
  14. package/dist/{chunk-PW65BXY5.js → chunk-7YJY3F2R.js} +11 -1
  15. package/dist/{chunk-7OZWKHA3.js → chunk-AXFLY5GU.js} +28 -5
  16. package/dist/{chunk-SSNZ6ZNN.js → chunk-CV6BNGFK.js} +3 -3
  17. package/dist/{chunk-MLZBBLMT.js → chunk-CW7BQKF6.js} +4 -4
  18. package/dist/{chunk-QFVGPKJI.js → chunk-DMK5ASKV.js} +1 -1
  19. package/dist/{chunk-WBBKYUGF.js → chunk-EDR47UE2.js} +2 -2
  20. package/dist/{chunk-632FJ23O.js → chunk-EGV6R6UF.js} +26 -7
  21. package/dist/{chunk-KAEZOVP6.js → chunk-FLIEXG2T.js} +1 -1
  22. package/dist/{chunk-5TPC4KCV.js → chunk-FXWBFDQR.js} +2 -2
  23. package/dist/{chunk-74T3CEXK.js → chunk-G7A5LEVI.js} +2 -2
  24. package/dist/{chunk-7UWXAJMK.js → chunk-GLM7TVWP.js} +1 -1
  25. package/dist/{chunk-CAIYV33P.js → chunk-H6X6CKDN.js} +2 -1
  26. package/dist/{chunk-C3KLHJDB.js → chunk-HTRSSOHY.js} +1 -1
  27. package/dist/{chunk-77IZUVBB.js → chunk-I7U5EEZC.js} +2 -2
  28. package/dist/{chunk-X67VT4QO.js → chunk-ICXY54ZC.js} +1 -1
  29. package/dist/{chunk-HW6Q7AMB.js → chunk-ITK6HV47.js} +6 -7
  30. package/dist/{chunk-5PKFJHCR.js → chunk-JHKBALS2.js} +2 -2
  31. package/dist/{chunk-SFRYRW37.js → chunk-K2F237KS.js} +9 -9
  32. package/dist/{chunk-7XDOTN4R.js → chunk-L7BKTZ5U.js} +1 -1
  33. package/dist/{chunk-AFSLNUTT.js → chunk-LAUCO2HC.js} +1 -1
  34. package/dist/{chunk-AJWFDIEY.js → chunk-M77B73VV.js} +1 -1
  35. package/dist/{chunk-4CN7OCJE.js → chunk-OXWUEU5V.js} +2 -2
  36. package/dist/{chunk-TGJHGVBZ.js → chunk-RPPTGPYF.js} +4 -4
  37. package/dist/{chunk-JI67D7O2.js → chunk-RU74YG46.js} +120 -25
  38. package/dist/{chunk-7FDWPQCU.js → chunk-SW2U6HRB.js} +6 -6
  39. package/dist/{chunk-574M6I7F.js → chunk-TPX6E6PL.js} +3 -3
  40. package/dist/{chunk-3BAFMGCJ.js → chunk-U54PJPTB.js} +2 -2
  41. package/dist/{chunk-6BM2I736.js → chunk-UDZLPR34.js} +1 -1
  42. package/dist/{chunk-E5S3ILVA.js → chunk-UFTDRTCP.js} +1 -1
  43. package/dist/{chunk-YQOYZ6G7.js → chunk-VPOMJMV6.js} +2 -2
  44. package/dist/{chunk-RRZ4RQQU.js → chunk-X67BDDZR.js} +5 -7
  45. package/dist/{chunk-636GFQNE.js → chunk-XRWUQPCA.js} +8 -7
  46. package/dist/{chunk-7DXLIYPY.js → chunk-YVR6EHWL.js} +6 -6
  47. package/dist/{chunk-EQKSRVDE.js → chunk-ZEZQNP2G.js} +113 -49
  48. package/dist/cli.js +19 -19
  49. package/dist/{clock-LH7UFNLJ.js → clock-ZT5YT6HA.js} +2 -2
  50. package/dist/{cloud-sync-IDJFVDEP.js → cloud-sync-H7EWZASD.js} +22 -22
  51. package/dist/{conversations-S2232Y5I.js → conversations-ZL6PHROD.js} +4 -4
  52. package/dist/{create-2M7DAHQX.js → create-G2RIKQYK.js} +1 -1
  53. package/dist/{daemon-restart-2AAGYFHE.js → daemon-restart-4FWUA5JF.js} +6 -6
  54. package/dist/daemon.js +622 -340
  55. package/dist/{db-QAY45Z56.js → db-BONH4SUG.js} +2 -2
  56. package/dist/{delete-O7PEFQDF.js → delete-CYVZUAV2.js} +4 -0
  57. package/dist/delivery-manager-VYXVKUMP.js +35 -0
  58. package/dist/{delivery-router-JE7SHYNU.js → delivery-router-RHANNTAC.js} +3 -3
  59. package/dist/{down-D4IRNEMJ.js → down-QSNEMTTX.js} +5 -5
  60. package/dist/echo-text-PEEV7FQU.js +39 -0
  61. package/dist/{env-ABM5IMA7.js → env-IBW2QI6K.js} +1 -1
  62. package/dist/{env-AZK5X4W7.js → env-SQFCWW52.js} +3 -3
  63. package/dist/{exec-2H65LMTA.js → exec-ABD3DYA2.js} +3 -3
  64. package/dist/{export-3E3EL6W3.js → export-VBNWM7I4.js} +3 -3
  65. package/dist/{extensions-7KP3X6G7.js → extensions-N6L5NYTK.js} +22 -22
  66. package/dist/{files-6BMKVG5G.js → files-BRY5RRIM.js} +3 -3
  67. package/dist/{import-UPNTEBBT.js → import-DB2KULRO.js} +6 -6
  68. package/dist/{isolation-OKDFM6WA.js → isolation-GVV2IBAU.js} +3 -3
  69. package/dist/{join-PYA36HEC.js → join-KLRU72JJ.js} +10 -2
  70. package/dist/{list-4BVGH7AN.js → list-UQGIW7OF.js} +1 -1
  71. package/dist/{login-MOXVLULE.js → login-SB5OENMV.js} +2 -2
  72. package/dist/{logout-DWD3OQVT.js → logout-YFZAJRPY.js} +2 -2
  73. package/dist/message-delivery-7URC52ME.js +43 -0
  74. package/dist/{migrate-thread-config-4A5LW36Z.js → migrate-thread-config-EQSEP2XG.js} +2 -2
  75. package/dist/{mind-NYRALSV2.js → mind-NEXGT3KE.js} +9 -9
  76. package/dist/{mind-activity-tracker-L7GCYXQR.js → mind-activity-tracker-QF6DCX2X.js} +4 -4
  77. package/dist/{mind-history-NXXOOTX6.js → mind-history-OGNR4L7W.js} +1 -1
  78. package/dist/mind-service-E26ZQXDS.js +47 -0
  79. package/dist/{mind-sleep-TH6BRQ5X.js → mind-sleep-LZAFBDZD.js} +2 -2
  80. package/dist/{package-XYSV7NUT.js → package-2W2XKSIP.js} +2 -2
  81. package/dist/{prompts-QKFPX6DQ.js → prompts-NBO3HRK3.js} +3 -3
  82. package/dist/{read-ZXGUT4QC.js → read-RJ5NAW5J.js} +3 -14
  83. package/dist/{registry-4UF6OCOE.js → registry-TRC2HJXR.js} +2 -2
  84. package/dist/{sandbox-EGEU5GGE.js → sandbox-NNWCTHRI.js} +3 -3
  85. package/dist/scheduler-JTTWZDYU.js +25 -0
  86. package/dist/{schema-C2GIPD4V.js → schema-ZZQVM4GR.js} +3 -1
  87. package/dist/{seed-3IMBOAGK.js → seed-64XEHRUP.js} +1 -1
  88. package/dist/{seed-cmd-U73XVQMI.js → seed-cmd-UEL2QB3Q.js} +2 -2
  89. package/dist/{seed-create-FBLNVSUP.js → seed-create-4JRQDPGA.js} +3 -3
  90. package/dist/{seed-readiness-QRA46ZJQ.js → seed-readiness-DYOVCYHN.js} +4 -4
  91. package/dist/{seed-sprout-3B4SZX7J.js → seed-sprout-NIDIOJXT.js} +7 -7
  92. package/dist/{send-NBRZJXKX.js → send-2YKOZJJ5.js} +81 -49
  93. package/dist/{service-XMXAXGJD.js → service-XQPMZAGI.js} +4 -4
  94. package/dist/{service-install-MOKTEG7B.js → service-install-2EWD7KXD.js} +5 -5
  95. package/dist/{setup-JGYAXXW7.js → setup-4IX6PCD3.js} +6 -6
  96. package/dist/{setup-CAJFOR2N.js → setup-WZSXHHRJ.js} +2 -2
  97. package/dist/{skill-NXIHDQCG.js → skill-B5OJEQUA.js} +1 -1
  98. package/dist/skills/volute-mind/references/variants.md +1 -1
  99. package/dist/{skills-5MSMT7PT.js → skills-MPUALV3S.js} +4 -4
  100. package/dist/sleep-manager-OFP2GFXF.js +41 -0
  101. package/dist/{spirit-IL5EMWYZ.js → spirit-4ECYIVGE.js} +7 -7
  102. package/dist/spirit-availability-Y54Y3675.js +42 -0
  103. package/dist/{sprout-UPXGJ5HX.js → sprout-GJMZFRGZ.js} +1 -1
  104. package/dist/{status-IL67CYM4.js → status-MWLE3RVC.js} +5 -5
  105. package/dist/{system-events-A6IGCEC3.js → system-events-V43HDA3T.js} +5 -3
  106. package/dist/{tailscale-SJUW2ETR.js → tailscale-KHNZFC7L.js} +2 -2
  107. package/dist/{template-hash-GSOUARW4.js → template-hash-YKWMVYMM.js} +2 -2
  108. package/dist/{template-staleness-T27VCGYA.js → template-staleness-PY2CULH6.js} +5 -5
  109. package/dist/{turn-tracker-6EI2EL7G.js → turn-tracker-C5PVIC3B.js} +5 -5
  110. package/dist/{up-CLL4665A.js → up-H6LJZDRK.js} +5 -5
  111. package/dist/{update-HVAUCQPN.js → update-CIWI2JYA.js} +5 -5
  112. package/dist/{update-check-2MGBBAFF.js → update-check-SZ7B5IFI.js} +3 -3
  113. package/dist/variant-cleanup-STC6B5HR.js +22 -0
  114. package/dist/{version-notify-7BUHWZNT.js → version-notify-LBVHH5YV.js} +8 -8
  115. package/dist/web-assets/assets/index-B_eYTa3R.js +97 -0
  116. package/dist/web-assets/assets/index-DhZrzddW.css +1 -0
  117. package/dist/web-assets/index.html +2 -2
  118. package/drizzle/0018_api_tokens.sql +20 -0
  119. package/drizzle/meta/_journal.json +7 -0
  120. package/package.json +2 -2
  121. package/templates/_base/{biome.json.tmpl → biome.json} +2 -1
  122. package/templates/_base/gitignore +44 -0
  123. package/templates/_base/home/public/.gitkeep +0 -0
  124. package/templates/_base/src/lib/auto-commit.ts +49 -11
  125. package/templates/_base/src/lib/format-prefix.ts +15 -8
  126. package/templates/_base/src/lib/router.ts +13 -8
  127. package/templates/claude/volute-template.json +1 -3
  128. package/templates/codex/volute-template.json +1 -3
  129. package/templates/pi/{package.json.tmpl → package.json} +2 -2
  130. package/templates/pi/volute-template.json +1 -3
  131. package/dist/delivery-manager-B4OKWD2P.js +0 -33
  132. package/dist/echo-text-WYAAODBV.js +0 -39
  133. package/dist/message-delivery-3456PXB7.js +0 -43
  134. package/dist/mind-service-5TA7UJAT.js +0 -47
  135. package/dist/scheduler-UV6RUJ7P.js +0 -25
  136. package/dist/sleep-manager-LIEPO6WG.js +0 -41
  137. package/dist/spirit-availability-YFEGJNJ2.js +0 -42
  138. package/dist/variant-cleanup-HJ7Z75R6.js +0 -22
  139. package/dist/web-assets/assets/index-ClsUcXiW.js +0 -95
  140. package/dist/web-assets/assets/index-Dz3wTbtR.css +0 -1
  141. /package/templates/_base/home/.config/{config.json.tmpl → config.json} +0 -0
  142. /package/templates/claude/{package.json.tmpl → package.json} +0 -0
  143. /package/templates/codex/home/.config/{config.json.tmpl → config.json} +0 -0
  144. /package/templates/codex/{package.json.tmpl → package.json} +0 -0
  145. /package/templates/pi/home/.config/{config.json.tmpl → config.json} +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  tryGetMindManager
4
- } from "./chunk-ALIVXDM4.js";
4
+ } from "./chunk-724DVYHU.js";
5
5
  import {
6
6
  extractTextContent,
7
7
  getRoutingConfig,
@@ -9,23 +9,27 @@ import {
9
9
  resolveRoute,
10
10
  setRoutesChangeListener,
11
11
  shouldGate
12
- } from "./chunk-6BM2I736.js";
12
+ } from "./chunk-UDZLPR34.js";
13
13
  import {
14
14
  linkInboundToActiveTurn
15
- } from "./chunk-7DXLIYPY.js";
15
+ } from "./chunk-YVR6EHWL.js";
16
+ import {
17
+ MIND_LEVEL_THREAD,
18
+ newEphemeralSession
19
+ } from "./chunk-AXFLY5GU.js";
16
20
  import {
17
21
  publish
18
22
  } from "./chunk-LNNI3MS4.js";
19
23
  import {
20
24
  getParticipants,
21
25
  publish as publish2
22
- } from "./chunk-574M6I7F.js";
26
+ } from "./chunk-TPX6E6PL.js";
23
27
  import {
24
28
  onMindEvent
25
- } from "./chunk-E5S3ILVA.js";
29
+ } from "./chunk-UFTDRTCP.js";
26
30
  import {
27
31
  renderAvatarBlock
28
- } from "./chunk-X67VT4QO.js";
32
+ } from "./chunk-ICXY54ZC.js";
29
33
  import {
30
34
  readVoluteConfig
31
35
  } from "./chunk-WCFSBH33.js";
@@ -38,13 +42,13 @@ import {
38
42
  getDb,
39
43
  mindDir,
40
44
  voluteHome
41
- } from "./chunk-CAIYV33P.js";
45
+ } from "./chunk-H6X6CKDN.js";
42
46
  import {
43
47
  channelGates,
44
48
  deliveryQueue,
45
49
  messages,
46
50
  mindHistory
47
- } from "./chunk-5IIVOWSP.js";
51
+ } from "./chunk-2RNMBXD2.js";
48
52
 
49
53
  // packages/daemon/src/lib/delivery/delivery-manager.ts
50
54
  import { realpath } from "fs/promises";
@@ -214,6 +218,7 @@ var REDRIVE_INTERVAL_MS = 15e3;
214
218
  var RETRY_BASE_MS = 5e3;
215
219
  var RETRY_MAX_MS = 5 * 6e4;
216
220
  var REDRIVE_BATCH_LIMIT = 200;
221
+ var MAX_DELIVERY_ATTEMPTS = 10;
217
222
  var GATED_RELEASE_LIMIT_PER_CHANNEL = 10;
218
223
  var GATED_NOTIFY_EVERY = 10;
219
224
  var mentionRegexCache = /* @__PURE__ */ new Map();
@@ -238,9 +243,14 @@ var DeliveryManager = class {
238
243
  isMindRunning = (name) => tryGetMindManager()?.isRunning(name) ?? false;
239
244
  /** Delivers a channel event to a mind (invites, release summaries); overridable in tests. */
240
245
  notify = async (mindName, text) => {
241
- const { deliverEvent } = await import("./system-events-A6IGCEC3.js");
246
+ const { deliverEvent } = await import("./system-events-V43HDA3T.js");
242
247
  await deliverEvent(mindName, { type: "channel", body: text });
243
248
  };
249
+ /** Surfaces a dead-lettered delivery as a next-turn failure notice; overridable in tests. */
250
+ notifyFailure = async (input) => {
251
+ const { recordNotice } = await import("./system-events-V43HDA3T.js");
252
+ await recordNotice(input);
253
+ };
244
254
  constructor() {
245
255
  setRoutesChangeListener((mind) => {
246
256
  this.releaseGated(mind).catch(
@@ -256,6 +266,10 @@ var DeliveryManager = class {
256
266
  setNotifier(fn) {
257
267
  this.notify = fn;
258
268
  }
269
+ /** Test seam: capture/override dead-letter failure notices. */
270
+ setFailureNotifier(fn) {
271
+ this.notifyFailure = fn;
272
+ }
259
273
  // --- Public API ---
260
274
  /**
261
275
  * Route and deliver a message to a mind. This is the main entry point.
@@ -268,7 +282,7 @@ var DeliveryManager = class {
268
282
  if (payload.session) {
269
283
  let sessionName2 = payload.session;
270
284
  if (sessionName2 === "$new") {
271
- sessionName2 = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
285
+ sessionName2 = newEphemeralSession();
272
286
  }
273
287
  const sessionConfig2 = resolveDeliveryMode(config, sessionName2);
274
288
  if (sessionConfig2.delivery.mode === "batch") {
@@ -312,7 +326,7 @@ var DeliveryManager = class {
312
326
  }
313
327
  let sessionName = route.session;
314
328
  if (sessionName === "$new") {
315
- sessionName = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
329
+ sessionName = newEphemeralSession();
316
330
  }
317
331
  const sessionConfig = resolveDeliveryMode(config, sessionName, route.rule);
318
332
  if (sessionConfig.delivery.mode === "batch") {
@@ -462,7 +476,7 @@ var DeliveryManager = class {
462
476
  }
463
477
  let session = route.session;
464
478
  if (session === "$new") {
465
- session = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
479
+ session = newEphemeralSession();
466
480
  }
467
481
  const channel = row.channel ?? payload.channel ?? "unknown";
468
482
  const list = byChannel.get(channel) ?? [];
@@ -764,22 +778,102 @@ var DeliveryManager = class {
764
778
  dlog.warn("failed to delete delivered delivery queue rows", logger_default.errorData(err));
765
779
  }
766
780
  }
767
- /** Bump attempt counters and set a backoff window so a down mind isn't hot-looped. */
768
- async scheduleRetry(ids) {
781
+ /**
782
+ * Record the outcome of a failed delivery attempt: set a backoff window so the target
783
+ * isn't hot-looped, and — for a LIVE rejection — advance the dead-letter counter.
784
+ *
785
+ * Only a live rejection (`liveRejection: true`: the target was reachable and answered with
786
+ * a non-OK HTTP status) counts toward {@link MAX_DELIVERY_ATTEMPTS}. A transport failure
787
+ * (`liveRejection: false`: connection refused, reset, or timeout — the mind or a detached
788
+ * variant is simply down/unreachable) backs off WITHOUT advancing the counter, so a merely
789
+ * offline target is never dead-lettered and its message is preserved until it returns. The
790
+ * redrive guard only checks the base mind's up-ness, so a stopped variant reaches here on
791
+ * every sweep — this is what stops that from silently dropping its messages. #356
792
+ *
793
+ * A row that reaches the ceiling moves to the terminal `dead` status (excluded from
794
+ * redrive, which only reads `pending`) and the batch is surfaced as one failure notice.
795
+ *
796
+ * Rows here are still `inFlight` (the caller clears ownership in its own `finally`, after
797
+ * this resolves), so the concurrent redrive sweep skips them and can't race this update.
798
+ */
799
+ async scheduleRetry(ids, opts) {
769
800
  const valid = [...new Set(ids.filter((id) => typeof id === "number"))];
770
801
  if (valid.length === 0) return;
771
802
  try {
772
803
  const db = await getDb();
773
- const rows = await db.select({ id: deliveryQueue.id, attempts: deliveryQueue.attempts }).from(deliveryQueue).where(inArray(deliveryQueue.id, valid));
804
+ const rows = await db.select({
805
+ id: deliveryQueue.id,
806
+ attempts: deliveryQueue.attempts,
807
+ mind: deliveryQueue.mind,
808
+ target_mind: deliveryQueue.target_mind,
809
+ thread: deliveryQueue.thread,
810
+ channel: deliveryQueue.channel,
811
+ sender: deliveryQueue.sender,
812
+ created_at: deliveryQueue.created_at
813
+ }).from(deliveryQueue).where(inArray(deliveryQueue.id, valid));
814
+ const dead = [];
774
815
  for (const row of rows) {
816
+ if (!opts.liveRejection) {
817
+ await db.update(deliveryQueue).set({ next_attempt_at: this.backoffExpr(row.attempts) }).where(eq2(deliveryQueue.id, row.id));
818
+ continue;
819
+ }
775
820
  const attempts = row.attempts + 1;
776
- const backoffSec = Math.round(
777
- Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
778
- );
779
- await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq2(deliveryQueue.id, row.id));
821
+ if (attempts >= MAX_DELIVERY_ATTEMPTS) {
822
+ dlog.error(
823
+ `dead-lettering delivery queue row ${row.id} for ${row.mind} after ${attempts} live rejections (channel=${row.channel ?? "?"}, sender=${row.sender ?? "?"})`
824
+ );
825
+ const flipped = await db.update(deliveryQueue).set({ attempts, status: "dead", next_attempt_at: null }).where(and2(eq2(deliveryQueue.id, row.id), eq2(deliveryQueue.status, "pending"))).returning({ id: deliveryQueue.id });
826
+ if (flipped.length > 0) dead.push(row);
827
+ continue;
828
+ }
829
+ await db.update(deliveryQueue).set({ attempts, next_attempt_at: this.backoffExpr(attempts) }).where(eq2(deliveryQueue.id, row.id));
780
830
  }
831
+ if (dead.length > 0) await this.notifyDeadLettered(dead);
832
+ } catch (err) {
833
+ dlog.error("failed to record delivery retry / dead-letter", logger_default.errorData(err));
834
+ }
835
+ }
836
+ /** Exponential backoff window (capped at {@link RETRY_MAX_MS}) as a SQL datetime expr. */
837
+ backoffExpr(attempts) {
838
+ const backoffSec = Math.round(
839
+ Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
840
+ );
841
+ return sql`datetime('now', ${`+${backoffSec} seconds`})`;
842
+ }
843
+ /**
844
+ * Surface a batch of dead-lettered rows as ONE next-turn failure notice, so a whole failing
845
+ * batch can't emit dozens of notices and evict unrelated ones via the per-thread overflow
846
+ * cap. The notice names the channel(s) and send times and points the mind at the surviving
847
+ * channel history, and never throws back into the delivery path — the rows are terminal. #356
848
+ */
849
+ async notifyDeadLettered(rows) {
850
+ const mind = rows[0].mind;
851
+ const threads = new Set(rows.map((r) => r.thread));
852
+ const single = threads.size === 1 ? [...threads][0] : MIND_LEVEL_THREAD;
853
+ const thread = single.startsWith("new-") ? MIND_LEVEL_THREAD : single;
854
+ const lines = rows.map((r) => {
855
+ const from = r.sender ? ` from ${r.sender}` : "";
856
+ const on = r.channel ? ` on ${r.channel}` : "";
857
+ const to = r.target_mind && r.target_mind !== r.mind ? ` to your variant ${r.target_mind}` : "";
858
+ return `- a message${from}${on}${to} (sent ${r.created_at})`;
859
+ });
860
+ const channels = [...new Set(rows.map((r) => r.channel).filter((c) => !!c))];
861
+ const recovery = channels.length > 0 ? `The original message(s) remain in the channel history \u2014 read them with ${channels.map((c) => `\`volute chat read ${c}\``).join(" or ")}.` : `The original message(s) remain in the channel history.`;
862
+ const detail = [
863
+ `${rows.length} message(s) sent to you could not be delivered after ${MAX_DELIVERY_ATTEMPTS} attempts and were dropped:`,
864
+ ...lines,
865
+ recovery
866
+ ].join("\n");
867
+ try {
868
+ await this.notifyFailure({
869
+ mind,
870
+ thread,
871
+ kind: "delivery_failed",
872
+ reason: "delivery_failed",
873
+ detail
874
+ });
781
875
  } catch (err) {
782
- dlog.warn("failed to schedule delivery retry", logger_default.errorData(err));
876
+ dlog.warn(`failed to send dead-letter notice for ${mind}`, logger_default.errorData(err));
783
877
  }
784
878
  }
785
879
  async deliverToMind(mindName, session, payload, sessionConfig, queueId) {
@@ -821,7 +915,7 @@ var DeliveryManager = class {
821
915
  if (!ok) {
822
916
  this.decrementActive(baseName, session);
823
917
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
824
- await this.scheduleRetry([queueId]);
918
+ await this.scheduleRetry([queueId], { liveRejection: true });
825
919
  } else {
826
920
  await this.deleteQueueRows([queueId]);
827
921
  }
@@ -829,7 +923,7 @@ var DeliveryManager = class {
829
923
  dlog.warn(`failed to deliver to ${mindName}`, logger_default.errorData(err));
830
924
  this.decrementActive(baseName, session);
831
925
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
832
- await this.scheduleRetry([queueId]);
926
+ await this.scheduleRetry([queueId], { liveRejection: false });
833
927
  } finally {
834
928
  if (queueId != null) this.inFlight.delete(queueId);
835
929
  }
@@ -908,7 +1002,7 @@ var DeliveryManager = class {
908
1002
  if (!ok) {
909
1003
  this.decrementActive(baseName, session);
910
1004
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
911
- await this.scheduleRetry(queueIds);
1005
+ await this.scheduleRetry(queueIds, { liveRejection: true });
912
1006
  } else {
913
1007
  await this.deleteQueueRows(queueIds);
914
1008
  }
@@ -916,7 +1010,7 @@ var DeliveryManager = class {
916
1010
  dlog.warn(`failed to deliver batch to ${mindName}`, logger_default.errorData(err));
917
1011
  this.decrementActive(baseName, session);
918
1012
  publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
919
- await this.scheduleRetry(queueIds);
1013
+ await this.scheduleRetry(queueIds, { liveRejection: false });
920
1014
  } finally {
921
1015
  for (const id of queueIds) this.inFlight.delete(id);
922
1016
  }
@@ -1036,7 +1130,7 @@ var DeliveryManager = class {
1036
1130
  const details = detailLines.length > 0 ? `${detailLines.join("\n")}
1037
1131
 
1038
1132
  ` : "\n";
1039
- const { getPrompt } = await import("./prompts-QKFPX6DQ.js");
1133
+ const { getPrompt } = await import("./prompts-NBO3HRK3.js");
1040
1134
  const notification = await getPrompt("channel_invite", {
1041
1135
  channel,
1042
1136
  heldLine,
@@ -1215,6 +1309,7 @@ export {
1215
1309
  publishTypingForChannels,
1216
1310
  checkStaleSend,
1217
1311
  formatHoldNotice,
1312
+ MAX_DELIVERY_ATTEMPTS,
1218
1313
  DeliveryManager,
1219
1314
  initDeliveryManager,
1220
1315
  getDeliveryManager,
@@ -6,24 +6,24 @@ import {
6
6
  getMindToken,
7
7
  loadJsonMap,
8
8
  saveJsonMapAsync
9
- } from "./chunk-ALIVXDM4.js";
9
+ } from "./chunk-724DVYHU.js";
10
10
  import {
11
11
  deliverEvent
12
- } from "./chunk-7OZWKHA3.js";
12
+ } from "./chunk-AXFLY5GU.js";
13
13
  import {
14
14
  isSandboxEnabled,
15
15
  wrapForSandbox
16
- } from "./chunk-ZUG76D3P.js";
16
+ } from "./chunk-5Y54AE3L.js";
17
17
  import {
18
18
  readVoluteConfig,
19
19
  writeVoluteConfig
20
20
  } from "./chunk-WCFSBH33.js";
21
21
  import {
22
22
  exec
23
- } from "./chunk-AJWFDIEY.js";
23
+ } from "./chunk-M77B73VV.js";
24
24
  import {
25
25
  loadMergedEnv
26
- } from "./chunk-7UWXAJMK.js";
26
+ } from "./chunk-GLM7TVWP.js";
27
27
  import {
28
28
  logger_default
29
29
  } from "./chunk-QUSMYGNQ.js";
@@ -34,7 +34,7 @@ import {
34
34
  mindTmpEnv,
35
35
  stateDir,
36
36
  voluteSystemDir
37
- } from "./chunk-CAIYV33P.js";
37
+ } from "./chunk-H6X6CKDN.js";
38
38
 
39
39
  // packages/daemon/src/lib/daemon/scheduler.ts
40
40
  import { resolve } from "path";
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  bufferEvent,
4
4
  subscribe
5
- } from "./chunk-3BAFMGCJ.js";
5
+ } from "./chunk-U54PJPTB.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-QUSMYGNQ.js";
9
9
  import {
10
10
  getDb
11
- } from "./chunk-CAIYV33P.js";
11
+ } from "./chunk-H6X6CKDN.js";
12
12
  import {
13
13
  channels,
14
14
  conversationParticipants,
@@ -16,7 +16,7 @@ import {
16
16
  conversations,
17
17
  messages,
18
18
  users
19
- } from "./chunk-5IIVOWSP.js";
19
+ } from "./chunk-2RNMBXD2.js";
20
20
 
21
21
  // packages/daemon/src/lib/events/conversations.ts
22
22
  import { randomUUID } from "crypto";
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-QUSMYGNQ.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-CAIYV33P.js";
7
+ } from "./chunk-H6X6CKDN.js";
8
8
  import {
9
9
  activity
10
- } from "./chunk-5IIVOWSP.js";
10
+ } from "./chunk-2RNMBXD2.js";
11
11
 
12
12
  // packages/daemon/src/lib/events/event-sequencer.ts
13
13
  var BUFFER_SIZE = 1e3;
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-QUSMYGNQ.js";
5
5
  import {
6
6
  mindDir
7
- } from "./chunk-CAIYV33P.js";
7
+ } from "./chunk-H6X6CKDN.js";
8
8
 
9
9
  // packages/daemon/src/lib/delivery/delivery-router.ts
10
10
  import { readFileSync, statSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-3BAFMGCJ.js";
4
+ } from "./chunk-U54PJPTB.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-QUSMYGNQ.js";
@@ -8,12 +8,12 @@ import {
8
8
  modeLabel,
9
9
  pollHealth,
10
10
  startService
11
- } from "./chunk-5FVANDON.js";
11
+ } from "./chunk-6NLW72HP.js";
12
12
  import {
13
13
  readGlobalConfig,
14
14
  voluteHome,
15
15
  voluteSystemDir
16
- } from "./chunk-CAIYV33P.js";
16
+ } from "./chunk-H6X6CKDN.js";
17
17
 
18
18
  // src/commands/up.ts
19
19
  import { spawn } from "child_process";
@@ -80,10 +80,8 @@ function copyTemplateToDir(composedDir, destDir, mindName, manifest) {
80
80
  }
81
81
  function renderComposedPackageJson(composedDir, mindName) {
82
82
  const dest = resolve(composedDir, "package.json");
83
- if (existsSync(dest)) return dest;
84
- const tmpl = resolve(composedDir, "package.json.tmpl");
85
- if (!existsSync(tmpl)) return null;
86
- const content = readFileSync(tmpl, "utf-8").replaceAll("{{name}}", mindName);
83
+ if (!existsSync(dest)) return null;
84
+ const content = readFileSync(dest, "utf-8").replaceAll("{{name}}", mindName);
87
85
  writeFileSync(dest, content);
88
86
  return dest;
89
87
  }
@@ -128,11 +126,11 @@ function applyTemplateHomeFiles(homeDir, template) {
128
126
  } else {
129
127
  rmSync(settingsDest, { force: true });
130
128
  }
131
- const templateConfig = resolve(root, template, "home", ".config", "config.json.tmpl");
132
- const configTmpl = existsSync(templateConfig) ? templateConfig : resolve(root, "_base", "home", ".config", "config.json.tmpl");
129
+ const templateConfig = resolve(root, template, "home", ".config", "config.json");
130
+ const configSrc = existsSync(templateConfig) ? templateConfig : resolve(root, "_base", "home", ".config", "config.json");
133
131
  const configDest = resolve(homeDir, ".config", "config.json");
134
132
  mkdirSync(dirname(configDest), { recursive: true });
135
- writeFileSync(configDest, readFileSync(configTmpl, "utf-8"));
133
+ writeFileSync(configDest, readFileSync(configSrc, "utf-8"));
136
134
  }
137
135
  function listFiles(dir) {
138
136
  const results = [];
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  findBridgeForChannel,
4
4
  getBridgeConfig
5
- } from "./chunk-7XDOTN4R.js";
5
+ } from "./chunk-L7BKTZ5U.js";
6
6
  import {
7
7
  recordOutbound
8
- } from "./chunk-EQKSRVDE.js";
8
+ } from "./chunk-ZEZQNP2G.js";
9
9
  import {
10
10
  getOrCreateMindUser,
11
11
  getUserByUsername
12
- } from "./chunk-UC6BNMGT.js";
12
+ } from "./chunk-7LT3YZUR.js";
13
13
  import {
14
14
  publish
15
15
  } from "./chunk-LNNI3MS4.js";
@@ -20,18 +20,18 @@ import {
20
20
  getChannelName,
21
21
  getConversation,
22
22
  getParticipants
23
- } from "./chunk-574M6I7F.js";
23
+ } from "./chunk-TPX6E6PL.js";
24
24
  import {
25
25
  readVoluteConfig
26
26
  } from "./chunk-WCFSBH33.js";
27
27
  import {
28
28
  daemonConfigPath,
29
29
  readDaemonToken
30
- } from "./chunk-5FVANDON.js";
30
+ } from "./chunk-6NLW72HP.js";
31
31
  import {
32
32
  readEnv,
33
33
  sharedEnvPath
34
- } from "./chunk-7UWXAJMK.js";
34
+ } from "./chunk-GLM7TVWP.js";
35
35
  import {
36
36
  logger_default
37
37
  } from "./chunk-QUSMYGNQ.js";
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-LGNUFVMR.js";
41
41
  import {
42
42
  mindDir
43
- } from "./chunk-CAIYV33P.js";
43
+ } from "./chunk-H6X6CKDN.js";
44
44
  import {
45
45
  __export
46
46
  } from "./chunk-K3NQKI34.js";
@@ -581,6 +581,7 @@ async function read4(env, channelSlug, limit) {
581
581
  }
582
582
  return data.items.slice(-limit).map((m) => {
583
583
  const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
584
+ if (m.role === "event") return `\xB7 ${text}`;
584
585
  return `${m.sender_name ?? m.role}: ${text}`;
585
586
  }).join("\n");
586
587
  }
@@ -4,22 +4,22 @@ import {
4
4
  } from "./chunk-LNNI3MS4.js";
5
5
  import {
6
6
  getPrompt
7
- } from "./chunk-5TPC4KCV.js";
7
+ } from "./chunk-FXWBFDQR.js";
8
8
  import {
9
9
  aiCompleteUtility
10
- } from "./chunk-632FJ23O.js";
10
+ } from "./chunk-EGV6R6UF.js";
11
11
  import {
12
12
  logger_default
13
13
  } from "./chunk-QUSMYGNQ.js";
14
14
  import {
15
15
  getDb
16
- } from "./chunk-CAIYV33P.js";
16
+ } from "./chunk-H6X6CKDN.js";
17
17
  import {
18
18
  messages,
19
19
  mindHistory,
20
20
  summaries,
21
21
  turns
22
- } from "./chunk-5IIVOWSP.js";
22
+ } from "./chunk-2RNMBXD2.js";
23
23
 
24
24
  // packages/daemon/src/lib/daemon/turn-tracker.ts
25
25
  import { randomUUID } from "crypto";
@@ -1060,11 +1060,11 @@ var Summarizer = class {
1060
1060
  }
1061
1061
  };
1062
1062
  async function reconcileWedgedTurns(idleMs) {
1063
- const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-6EI2EL7G.js");
1063
+ const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-C5PVIC3B.js");
1064
1064
  const wedged = await sweepWedgedTurns2(idleMs);
1065
1065
  if (wedged.length === 0) return;
1066
1066
  summarizeOrphanedTurns2(wedged);
1067
- const { tryGetDeliveryManager } = await import("./delivery-manager-B4OKWD2P.js");
1067
+ const { tryGetDeliveryManager } = await import("./delivery-manager-VYXVKUMP.js");
1068
1068
  const dm = tryGetDeliveryManager();
1069
1069
  if (!dm) return;
1070
1070
  for (const t of wedged) {