volute 0.46.0 → 0.47.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 (136) hide show
  1. package/README.md +1 -1
  2. package/dist/{activity-events-NF6VWTJQ.js → activity-events-MIOK3IOU.js} +3 -3
  3. package/dist/{ai-service-UODCCE4O.js → ai-service-CVZEYKTK.js} +4 -4
  4. package/dist/{archive-WS2L2VIQ.js → archive-6Z4GFQPH.js} +3 -3
  5. package/dist/{auth-CCFAU4GQ.js → auth-A4VGHADI.js} +4 -4
  6. package/dist/avatar-image-7S3WA77A.js +22 -0
  7. package/dist/{backup-TST5C2YN.js → backup-IIAFOW2L.js} +13 -13
  8. package/dist/channels-ITE4YC57.js +89 -0
  9. package/dist/{chat-ZIQQS25U.js → chat-AJLJCQ3M.js} +12 -8
  10. package/dist/{chunk-ZV7MFISX.js → chunk-2MJ7EOMT.js} +20 -2
  11. package/dist/{chunk-R77O2WMK.js → chunk-2OIYNHWO.js} +1 -1
  12. package/dist/{chunk-QCN24XEN.js → chunk-3YUBFTDN.js} +1 -1
  13. package/dist/{chunk-E3DA4HYV.js → chunk-4I72V3E5.js} +10 -10
  14. package/dist/{chunk-K6T43YBD.js → chunk-5IERLDUS.js} +3 -3
  15. package/dist/{chunk-FXORA5JG.js → chunk-5NN2VHJV.js} +3 -3
  16. package/dist/chunk-7KRIHYLR.js +124 -0
  17. package/dist/{chunk-RRNXJOKB.js → chunk-BGYHLR54.js} +2 -2
  18. package/dist/{chunk-QMJCNZ6C.js → chunk-C3KLHJDB.js} +1 -1
  19. package/dist/{chunk-TQJ6ZYVY.js → chunk-CBOIHNMR.js} +2 -2
  20. package/dist/{chunk-H2X54EHC.js → chunk-DGGZVZTM.js} +1 -1
  21. package/dist/{chunk-4JOQZPCE.js → chunk-EIQDZC43.js} +1 -1
  22. package/dist/{chunk-JO5IO4FH.js → chunk-F55GRIEK.js} +243 -121
  23. package/dist/{chunk-5ZQZDDIK.js → chunk-HPGN6XRT.js} +3 -3
  24. package/dist/{chunk-KGIXX4NM.js → chunk-IWJSNMSY.js} +5 -5
  25. package/dist/{chunk-ZDZISKCL.js → chunk-IYG4FBMP.js} +1 -1
  26. package/dist/{chunk-4YU7HF4L.js → chunk-JCMGDCGX.js} +4 -4
  27. package/dist/{chunk-PJ45FQPT.js → chunk-JSVHH32K.js} +6 -6
  28. package/dist/{chunk-YXSUMLTC.js → chunk-K23P5Q5N.js} +301 -77
  29. package/dist/{chunk-ZNMCCHCP.js → chunk-LTLFOSA4.js} +2 -2
  30. package/dist/{chunk-IHKGWTGT.js → chunk-LWDTP6DH.js} +1 -1
  31. package/dist/{chunk-JZ62SYWR.js → chunk-M3XDVS5Q.js} +1 -1
  32. package/dist/{chunk-BJZ4PV7R.js → chunk-M6TTZRGH.js} +9 -9
  33. package/dist/{chunk-KQSFTPIM.js → chunk-MTRMQFCQ.js} +1 -1
  34. package/dist/{chunk-OV7ZQ2GH.js → chunk-ODDGGLM6.js} +3 -3
  35. package/dist/{chunk-QLWG3C2Z.js → chunk-RRZ4RQQU.js} +2 -2
  36. package/dist/{chunk-CDPBTTZO.js → chunk-RUFLGTOT.js} +2 -2
  37. package/dist/{chunk-F7H6ATM4.js → chunk-SKNXMUIU.js} +1 -1
  38. package/dist/{chunk-RXEEYDIB.js → chunk-SZJCJVXV.js} +1 -1
  39. package/dist/{chunk-DRJ24GXK.js → chunk-VMCZBXF2.js} +52 -8
  40. package/dist/{chunk-ALKYQULU.js → chunk-WW6C3PLV.js} +1 -1
  41. package/dist/{chunk-A7WQZKOU.js → chunk-XHN6XBWV.js} +2 -2
  42. package/dist/{chunk-6EMWJZBK.js → chunk-YKA7DCVW.js} +1 -1
  43. package/dist/cli.js +21 -21
  44. package/dist/{clock-RHS2JEDL.js → clock-E6N5Y3YW.js} +3 -3
  45. package/dist/{cloud-sync-46RS4SDA.js → cloud-sync-LQTVDTQ3.js} +19 -18
  46. package/dist/{conversations-CJOOCGNW.js → conversations-C7IKYSHD.js} +4 -4
  47. package/dist/{create-IOV4GCRX.js → create-PQQXKTJK.js} +1 -1
  48. package/dist/{daemon-restart-VFEI57UO.js → daemon-restart-5F3YTLZK.js} +7 -7
  49. package/dist/daemon.js +217 -151
  50. package/dist/{db-MF2JNRUW.js → db-FO6LXYCW.js} +2 -2
  51. package/dist/delivery-manager-TN5KK2AP.js +34 -0
  52. package/dist/{delivery-router-N4A7ZC5T.js → delivery-router-3QFG2ZEX.js} +3 -3
  53. package/dist/{down-U2CZ23M4.js → down-ZI4S6265.js} +5 -5
  54. package/dist/echo-text-SEQCLGAT.js +34 -0
  55. package/dist/{exec-G3ZRST4Z.js → exec-QIFYZG4C.js} +3 -3
  56. package/dist/{export-LY723I3Z.js → export-4GVGNW54.js} +3 -3
  57. package/dist/{extensions-ZELQLTSR.js → extensions-PIMIQ7J3.js} +21 -18
  58. package/dist/{files-4LN3EN7Q.js → files-FEFZKMNM.js} +6 -6
  59. package/dist/{import-42YNQBF4.js → import-7MO3YTRE.js} +6 -6
  60. package/dist/{isolation-YMGZZ4E5.js → isolation-MCT4ZYLT.js} +3 -3
  61. package/dist/{login-M6OJUPQ2.js → login-OF4WXDAN.js} +5 -5
  62. package/dist/{logout-WQBWO77G.js → logout-ERE2BGA3.js} +5 -5
  63. package/dist/message-delivery-ZE3YGJDD.js +38 -0
  64. package/dist/{mind-LHKS55DI.js → mind-POOHFZHP.js} +13 -13
  65. package/dist/{mind-activity-tracker-3QRNJVR4.js → mind-activity-tracker-YVZPC4A5.js} +4 -4
  66. package/dist/{mind-list-4D7J4CHD.js → mind-list-KEGRGQEQ.js} +5 -4
  67. package/dist/mind-service-JFY7UG5E.js +40 -0
  68. package/dist/{mind-sleep-74TGOQBC.js → mind-sleep-IN3WPA76.js} +5 -5
  69. package/dist/{mind-status-UO34ASXZ.js → mind-status-SMUWJKGV.js} +20 -3
  70. package/dist/{package-DOD6CPO6.js → package-WPEE2HVJ.js} +2 -2
  71. package/dist/{registry-OUZBZ3Q4.js → registry-UMMDRPRM.js} +2 -2
  72. package/dist/{sandbox-ZJ7QA6Z6.js → sandbox-G4X4K3UN.js} +4 -4
  73. package/dist/scheduler-HQSDYJFY.js +32 -0
  74. package/dist/{schema-PVNG3PQM.js → schema-VEVO7HG6.js} +3 -1
  75. package/dist/{seed-CNYIHPOH.js → seed-VANPU43L.js} +1 -1
  76. package/dist/{seed-cmd-AVNUNLZV.js → seed-cmd-SYVDZKM4.js} +2 -2
  77. package/dist/{seed-create-ANXGYSCC.js → seed-create-3FGZPS6C.js} +3 -3
  78. package/dist/{seed-sprout-NIUFTX32.js → seed-sprout-H7QLFPFM.js} +12 -12
  79. package/dist/{send-SPE6DVNJ.js → send-HYDFGC6G.js} +7 -7
  80. package/dist/{service-UOCLXDWR.js → service-SYJ5G6LP.js} +4 -4
  81. package/dist/{service-install-4HHFDDWY.js → service-install-FQ43Z4DF.js} +5 -5
  82. package/dist/{setup-Y2EJI3GG.js → setup-KFYVWTXG.js} +3 -3
  83. package/dist/{setup-QT2A25SY.js → setup-QJHGMGSN.js} +7 -7
  84. package/dist/{skills-AKCFA7BN.js → skills-L2CMTW7A.js} +5 -5
  85. package/dist/sleep-manager-3NA7ZJP3.js +36 -0
  86. package/dist/{spirit-RPBLLUQ5.js → spirit-WXN4IDBF.js} +8 -8
  87. package/dist/{sprout-SMX42ZBP.js → sprout-LTL5P36T.js} +1 -1
  88. package/dist/{status-SWRVGXBI.js → status-V4RJMHJ4.js} +19 -9
  89. package/dist/system-chat-N2F7HVY3.js +38 -0
  90. package/dist/{systems-VNB5WKSS.js → systems-B5YJKMW4.js} +6 -6
  91. package/dist/{tailscale-3XOKOW7T.js → tailscale-MCLY5GYE.js} +2 -2
  92. package/dist/{template-hash-446HHH5V.js → template-hash-GSOUARW4.js} +2 -2
  93. package/dist/template-staleness-EMQORSGG.js +15 -0
  94. package/dist/{turn-tracker-FOIBNUUA.js → turn-tracker-JZRZDFWV.js} +6 -6
  95. package/dist/{up-PS6PFS75.js → up-J2PBPOZL.js} +6 -6
  96. package/dist/{update-SWAYZRWJ.js → update-ACV4SAY3.js} +5 -5
  97. package/dist/{update-check-PQ2MZ47K.js → update-check-266R4ZSY.js} +3 -3
  98. package/dist/{version-notify-C5CH7CVP.js → version-notify-XRGTEEDK.js} +37 -23
  99. package/dist/web-assets/assets/index-DtctoDXY.css +1 -0
  100. package/dist/web-assets/assets/index-Wpyb2CML.js +86 -0
  101. package/dist/web-assets/index.html +2 -2
  102. package/drizzle/0011_drop_turn_summary_event_id.sql +3 -0
  103. package/drizzle/0012_channel_gates.sql +17 -0
  104. package/drizzle/meta/_journal.json +14 -0
  105. package/package.json +2 -2
  106. package/templates/_base/src/lib/startup.ts +43 -2
  107. package/templates/claude/src/agent.ts +28 -2
  108. package/templates/claude/src/lib/session-reaper.ts +32 -0
  109. package/templates/claude/src/server.ts +10 -1
  110. package/dist/avatar-image-6KY6UU4D.js +0 -16
  111. package/dist/delivery-manager-JINZRG2W.js +0 -33
  112. package/dist/echo-text-VQPB5L2X.js +0 -33
  113. package/dist/message-delivery-R7JSQGYX.js +0 -37
  114. package/dist/mind-service-NB2JMWQZ.js +0 -39
  115. package/dist/scheduler-WVKII3LA.js +0 -31
  116. package/dist/sleep-manager-G6TXSDP7.js +0 -35
  117. package/dist/system-chat-NDAW6YP7.js +0 -37
  118. package/dist/web-assets/assets/index-B1JBQv2m.css +0 -1
  119. package/dist/web-assets/assets/index-CU6XWICM.js +0 -86
  120. package/dist/{accept-N5XULCXK.js → accept-YAFP5JFJ.js} +3 -3
  121. package/dist/{bridge-2PO3AI5D.js → bridge-TIZGSXOD.js} +3 -3
  122. package/dist/{create-DF3KISTW.js → create-A7PDHIIL.js} +3 -3
  123. package/dist/{env-VZ4MLFQW.js → env-HRL2PWAT.js} +5 -5
  124. package/dist/{extension-SEID47SR.js → extension-QBY4C44L.js} +3 -3
  125. package/dist/{list-OXIAY3W7.js → list-LQO2DCIK.js} +3 -3
  126. package/dist/{login-MY6ETFYB.js → login-6TLMI2TQ.js} +3 -3
  127. package/dist/{logout-NCG3TMDL.js → logout-BK53HXNY.js} +3 -3
  128. package/dist/{mind-history-VRO5A76U.js → mind-history-47DN6ILW.js} +4 -4
  129. package/dist/{mind-wake-LCLGTXA2.js → mind-wake-R54BPESY.js} +3 -3
  130. package/dist/{read-QVAXMTPV.js → read-PWAJOQIQ.js} +3 -3
  131. package/dist/{register-Y2H4PY5R.js → register-7SZJFHU6.js} +3 -3
  132. package/dist/{reject-EV35X64Y.js → reject-6VH7N2A4.js} +3 -3
  133. package/dist/{restart-FJQ662XJ.js → restart-H5WJDMA7.js} +4 -4
  134. package/dist/{skill-FKFHIJRR.js → skill-GUKG2YVH.js} +5 -5
  135. package/dist/{start-5JE3TV66.js → start-X46I3GXB.js} +4 -4
  136. package/dist/{stop-AAMTG3TU.js → stop-HJQV4YSM.js} +4 -4
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getPrompt
4
- } from "./chunk-4YU7HF4L.js";
4
+ } from "./chunk-JCMGDCGX.js";
5
5
  import {
6
6
  aiCompleteUtility
7
- } from "./chunk-IHKGWTGT.js";
7
+ } from "./chunk-LWDTP6DH.js";
8
8
  import {
9
9
  logger_default
10
10
  } from "./chunk-T2TP6ZC6.js";
11
11
  import {
12
12
  getDb
13
- } from "./chunk-ALKYQULU.js";
13
+ } from "./chunk-WW6C3PLV.js";
14
14
  import {
15
15
  messages,
16
16
  mindHistory,
17
17
  summaries,
18
18
  turns
19
- } from "./chunk-ZV7MFISX.js";
19
+ } from "./chunk-2MJ7EOMT.js";
20
20
 
21
21
  // packages/daemon/src/lib/daemon/turn-tracker.ts
22
22
  import { randomUUID } from "crypto";
@@ -82,10 +82,8 @@ function summarizeTool(name, input) {
82
82
  return `[${name}]`;
83
83
  }
84
84
 
85
- // packages/daemon/src/lib/daemon/summarizer.ts
86
- var sLog = logger_default.child("summarizer");
87
- var SYSTEM_MIND = "_system";
88
- var WEDGED_TURN_IDLE_MS = 15 * 6e4;
85
+ // packages/daemon/src/lib/util/period-keys.ts
86
+ var DATE_STR_RE = /^\d{4}-\d{2}-\d{2}$/;
89
87
  function getPeriodKey(date, period) {
90
88
  switch (period) {
91
89
  case "hour": {
@@ -117,12 +115,27 @@ function getISOWeekKey(date) {
117
115
  const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
118
116
  return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
119
117
  }
118
+ function isoWeekToDate(weekKey) {
119
+ const [yearStr, weekStr] = weekKey.split("-W");
120
+ const year = parseInt(yearStr, 10);
121
+ const week = parseInt(weekStr, 10);
122
+ const jan4 = new Date(year, 0, 4);
123
+ const dayOfWeek = jan4.getDay() || 7;
124
+ const monday = new Date(jan4);
125
+ monday.setDate(jan4.getDate() - dayOfWeek + 1 + (week - 1) * 7);
126
+ return monday;
127
+ }
128
+ function isoWeekKeyForDateStr(dateStr) {
129
+ if (!DATE_STR_RE.test(dateStr)) return dateStr;
130
+ const [y, m, d] = dateStr.split("-").map(Number);
131
+ return getISOWeekKey(new Date(y, m - 1, d));
132
+ }
120
133
  function getPreviousPeriodKey(key2, period) {
121
134
  switch (period) {
122
135
  case "hour": {
123
136
  const d = /* @__PURE__ */ new Date(`${key2.slice(0, 10)}T${key2.slice(11)}:00:00`);
124
- d.setHours(d.getHours() - 1);
125
- return getPeriodKey(d, "hour");
137
+ const prev = new Date(d.getTime() - 36e5);
138
+ return getPeriodKey(prev, "hour");
126
139
  }
127
140
  case "day": {
128
141
  const d = /* @__PURE__ */ new Date(`${key2}T00:00:00`);
@@ -141,16 +154,6 @@ function getPreviousPeriodKey(key2, period) {
141
154
  }
142
155
  }
143
156
  }
144
- function isoWeekToDate(weekKey) {
145
- const [yearStr, weekStr] = weekKey.split("-W");
146
- const year = parseInt(yearStr, 10);
147
- const week = parseInt(weekStr, 10);
148
- const jan4 = new Date(year, 0, 4);
149
- const dayOfWeek = jan4.getDay() || 7;
150
- const monday = new Date(jan4);
151
- monday.setDate(jan4.getDate() - dayOfWeek + 1 + (week - 1) * 7);
152
- return monday;
153
- }
154
157
  function localDateStr(d) {
155
158
  const y = d.getFullYear();
156
159
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -160,6 +163,9 @@ function localDateStr(d) {
160
163
  function utcDateTimeStr(d) {
161
164
  return d.toISOString().replace("T", " ").slice(0, 19);
162
165
  }
166
+ function parseUtcDateTime(s) {
167
+ return /* @__PURE__ */ new Date(`${s.replace(" ", "T")}Z`);
168
+ }
163
169
  function getTimeRange(periodKey, period) {
164
170
  switch (period) {
165
171
  case "hour": {
@@ -188,6 +194,11 @@ function getTimeRange(periodKey, period) {
188
194
  }
189
195
  }
190
196
  }
197
+
198
+ // packages/daemon/src/lib/daemon/summarizer.ts
199
+ var sLog = logger_default.child("summarizer");
200
+ var SYSTEM_MIND = "_system";
201
+ var WEDGED_TURN_IDLE_MS = 15 * 6e4;
191
202
  async function gatherTurnEvents(mind, session, doneId) {
192
203
  const db = await getDb();
193
204
  const subConditions = [
@@ -210,6 +221,7 @@ async function gatherTurnEvents(mind, session, doneId) {
210
221
  session: mindHistory.session,
211
222
  content: mindHistory.content,
212
223
  metadata: mindHistory.metadata,
224
+ turn_id: mindHistory.turn_id,
213
225
  created_at: mindHistory.created_at
214
226
  }).from(mindHistory).where(and(...turnConditions)).orderBy(mindHistory.id);
215
227
  return {
@@ -227,6 +239,7 @@ async function gatherTurnEventsByTurnId(turnId) {
227
239
  session: mindHistory.session,
228
240
  content: mindHistory.content,
229
241
  metadata: mindHistory.metadata,
242
+ turn_id: mindHistory.turn_id,
230
243
  created_at: mindHistory.created_at
231
244
  }).from(mindHistory).where(eq(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
232
245
  return {
@@ -321,19 +334,22 @@ function buildTranscript(events, parsedMeta) {
321
334
  async function summarizeTurn(mind, session, channel, doneId, turnId) {
322
335
  const { events, fromId, toId } = turnId ? await gatherTurnEventsByTurnId(turnId) : await gatherTurnEvents(mind, session, doneId);
323
336
  if (events.length === 0) return;
337
+ const effectiveTurnId = turnId ?? events.find((ev) => ev.turn_id)?.turn_id ?? void 0;
338
+ if (effectiveTurnId && await summaryExists(mind, "turn", effectiveTurnId)) return;
324
339
  const substantiveTypes = /* @__PURE__ */ new Set(["text", "outbound", "tool_use", "tool_result", "thinking"]);
325
340
  const hasSubstantiveOutput = events.some((ev) => substantiveTypes.has(ev.type));
326
341
  if (!hasSubstantiveOutput) {
327
342
  sLog.info(
328
- `skipping summary for interrupted turn ${turnId ?? "(no turn)"} (no substantive output)`
343
+ `skipping summary for interrupted turn ${effectiveTurnId ?? "(no turn)"} (no substantive output)`
329
344
  );
330
- if (turnId) {
345
+ if (effectiveTurnId) {
331
346
  try {
332
347
  const db2 = await getDb();
333
- await db2.update(mindHistory).set({ turn_id: null }).where(and(eq(mindHistory.turn_id, turnId), eq(mindHistory.type, "inbound")));
334
- await db2.update(messages).set({ turn_id: null }).where(eq(messages.turn_id, turnId));
348
+ await db2.update(mindHistory).set({ turn_id: null }).where(and(eq(mindHistory.turn_id, effectiveTurnId), eq(mindHistory.type, "inbound")));
349
+ await db2.update(messages).set({ turn_id: null }).where(eq(messages.turn_id, effectiveTurnId));
350
+ await db2.delete(turns).where(eq(turns.id, effectiveTurnId));
335
351
  } catch (err) {
336
- sLog.error(`failed to un-tag events for interrupted turn ${turnId}`, logger_default.errorData(err));
352
+ sLog.error(`failed to clean up interrupted turn ${effectiveTurnId}`, logger_default.errorData(err));
337
353
  }
338
354
  }
339
355
  return;
@@ -374,7 +390,7 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
374
390
  from_time: fromTime,
375
391
  to_time: toTime
376
392
  };
377
- const periodKey = turnId ?? `${mind}-${doneId}`;
393
+ const periodKey = effectiveTurnId ?? `${mind}-${doneId}`;
378
394
  const db = await getDb();
379
395
  let summaryId;
380
396
  try {
@@ -403,9 +419,9 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
403
419
  );
404
420
  return;
405
421
  }
406
- if (turnId && summaryId != null) {
407
- setSummaryId(turnId, summaryId).catch((err) => {
408
- sLog.error(`failed to link summary to turn ${turnId}`, logger_default.errorData(err));
422
+ if (effectiveTurnId && summaryId != null) {
423
+ setSummaryId(effectiveTurnId, summaryId).catch((err) => {
424
+ sLog.error(`failed to link summary to turn ${effectiveTurnId}`, logger_default.errorData(err));
409
425
  });
410
426
  }
411
427
  publish(mind, {
@@ -415,7 +431,7 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
415
431
  channel,
416
432
  content: summaryText,
417
433
  metadata,
418
- turnId
434
+ turnId: effectiveTurnId
419
435
  });
420
436
  }
421
437
  async function setSummaryId(turnId, summaryId) {
@@ -439,31 +455,86 @@ function getScopeInstruction(mind) {
439
455
  }
440
456
  return 'Write in first person as the mind who performed the actions (e.g. "I explored...", "I worked on...").';
441
457
  }
442
- function buildPeriodicDeterministicSummary(sources, period, periodKey) {
443
- if (sources.length === 0) return "";
458
+ var DIGEST_CHILD_CHARS = 200;
459
+ var DIGEST_MAX_CHARS = 4e3;
460
+ var DIGEST_NOTICE = "(auto-generated digest \u2014 AI summary pending)";
461
+ var ROLLUP_CHILD_CHARS = 2e3;
462
+ var PROVISIONAL_MAX_ATTEMPTS = 5;
463
+ var PROVISIONAL_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
464
+ var PROVISIONAL_RETRY_BACKOFF_MS = PROVISIONAL_WINDOW_MS / PROVISIONAL_MAX_ATTEMPTS;
465
+ function truncateChars(text, max) {
466
+ const t = text.trim();
467
+ return t.length > max ? `${t.slice(0, max).trimEnd()}\u2026` : t;
468
+ }
469
+ function digestLine(text) {
470
+ const flat = text.trim().replace(/\s+/g, " ");
471
+ const match = flat.match(/^.*?[.!?](?=\s|$)/);
472
+ const line = match ? match[0] : flat;
473
+ return line.length > DIGEST_CHILD_CHARS ? `${line.slice(0, DIGEST_CHILD_CHARS).trimEnd()}\u2026` : line;
474
+ }
475
+ function buildBoundedDigest(entries, period, periodKey) {
476
+ const label = period === "week" ? `Week ${periodKey}` : periodKey;
477
+ const header = `${label} ${DIGEST_NOTICE}`;
478
+ const lines = [];
479
+ let total = header.length + 2;
480
+ for (let i = 0; i < entries.length; i++) {
481
+ const line = `${entries[i].key}: ${digestLine(entries[i].text)}`;
482
+ if (lines.length > 0 && total + line.length + 1 > DIGEST_MAX_CHARS) {
483
+ lines.push(`\u2026and ${entries.length - i} more`);
484
+ break;
485
+ }
486
+ lines.push(line);
487
+ total += line.length + 1;
488
+ }
489
+ return `${header}
490
+
491
+ ${lines.join("\n")}`;
492
+ }
493
+ function buildPeriodicDeterministicSummary(entries, period, periodKey) {
494
+ if (entries.length === 0) return "";
444
495
  switch (period) {
445
496
  case "hour":
446
- return `Activity during ${periodKey.slice(11)}:00: ${sources.join(" ")}`;
497
+ return `Activity during ${periodKey.slice(11)}:00: ${entries.map((e) => e.text).join(" ")}`;
447
498
  case "day":
448
499
  return `Activity on ${periodKey}:
449
500
 
450
- ${sources.join("\n\n")}`;
501
+ ${entries.map((e) => e.text).join("\n\n")}`;
451
502
  case "week":
452
- return `Week ${periodKey} summary:
453
-
454
- ${sources.join("\n\n")}`;
455
503
  case "month":
456
- return `${periodKey} summary:
457
-
458
- ${sources.join("\n\n")}`;
504
+ return buildBoundedDigest(entries, period, periodKey);
459
505
  }
460
506
  }
507
+ function parseMeta(raw) {
508
+ if (!raw) return {};
509
+ try {
510
+ return JSON.parse(raw);
511
+ } catch {
512
+ return {};
513
+ }
514
+ }
515
+ function shouldRetry(period, meta) {
516
+ if (period !== "week" && period !== "month") return false;
517
+ if (meta.deterministic !== true) return false;
518
+ const attempts = typeof meta.attempts === "number" ? meta.attempts : 0;
519
+ if (attempts >= PROVISIONAL_MAX_ATTEMPTS) return false;
520
+ const first = typeof meta.first_attempt_at === "string" ? Date.parse(meta.first_attempt_at) : NaN;
521
+ if (!Number.isNaN(first) && Date.now() - first > PROVISIONAL_WINDOW_MS) return false;
522
+ const last = typeof meta.last_attempt_at === "string" ? Date.parse(meta.last_attempt_at) : NaN;
523
+ if (!Number.isNaN(last) && Date.now() - last < PROVISIONAL_RETRY_BACKOFF_MS) return false;
524
+ return true;
525
+ }
526
+ function trackProvisionalAttempt(metadata, prev) {
527
+ const prevAttempts = prev && typeof prev.attempts === "number" ? prev.attempts : 0;
528
+ metadata.attempts = prevAttempts + 1;
529
+ metadata.first_attempt_at = prev && typeof prev.first_attempt_at === "string" ? prev.first_attempt_at : (/* @__PURE__ */ new Date()).toISOString();
530
+ metadata.last_attempt_at = (/* @__PURE__ */ new Date()).toISOString();
531
+ }
461
532
  async function gatherChildSummaries(mind, period, periodKey) {
462
533
  const db = await getDb();
463
534
  const childPeriod = getChildPeriod(period);
464
535
  if (period === "hour") {
465
536
  const { start: start2, end: end2 } = getTimeRange(periodKey, "hour");
466
- const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
537
+ const rows2 = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
467
538
  and(
468
539
  eq(summaries.mind, mind),
469
540
  eq(summaries.period, childPeriod),
@@ -473,11 +544,12 @@ async function gatherChildSummaries(mind, period, periodKey) {
473
544
  ).orderBy(summaries.created_at);
474
545
  return {
475
546
  texts: rows2.map((r) => r.content),
476
- sourceIds: rows2.map((r) => r.id)
547
+ sourceIds: rows2.map((r) => r.id),
548
+ keys: rows2.map((r) => r.key)
477
549
  };
478
550
  }
479
551
  if (period === "day") {
480
- const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
552
+ const rows2 = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
481
553
  and(
482
554
  eq(summaries.mind, mind),
483
555
  eq(summaries.period, childPeriod),
@@ -486,13 +558,14 @@ async function gatherChildSummaries(mind, period, periodKey) {
486
558
  ).orderBy(summaries.period_key);
487
559
  return {
488
560
  texts: rows2.map((r) => r.content),
489
- sourceIds: rows2.map((r) => r.id)
561
+ sourceIds: rows2.map((r) => r.id),
562
+ keys: rows2.map((r) => r.key)
490
563
  };
491
564
  }
492
565
  const { start, end } = getTimeRange(periodKey, period);
493
566
  const startKey = start.slice(0, 10);
494
567
  const endKey = end.slice(0, 10);
495
- const rows = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
568
+ const rows = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
496
569
  and(
497
570
  eq(summaries.mind, mind),
498
571
  eq(summaries.period, childPeriod),
@@ -502,15 +575,24 @@ async function gatherChildSummaries(mind, period, periodKey) {
502
575
  ).orderBy(summaries.period_key);
503
576
  return {
504
577
  texts: rows.map((r) => r.content),
505
- sourceIds: rows.map((r) => r.id)
578
+ sourceIds: rows.map((r) => r.id),
579
+ keys: rows.map((r) => r.key)
506
580
  };
507
581
  }
508
- async function summarizePeriod(mind, period, periodKey) {
509
- if (await summaryExists(mind, period, periodKey)) return false;
582
+ async function summarizePeriod(mind, period, periodKey, complete = aiCompleteUtility) {
510
583
  const db = await getDb();
584
+ const existing = await db.select({ id: summaries.id, metadata: summaries.metadata }).from(summaries).where(
585
+ and(
586
+ eq(summaries.mind, mind),
587
+ eq(summaries.period, period),
588
+ eq(summaries.period_key, periodKey)
589
+ )
590
+ ).get();
591
+ const existingMeta = existing ? parseMeta(existing.metadata) : null;
592
+ if (existing && !shouldRetry(period, existingMeta)) return false;
511
593
  const sources = await gatherChildSummaries(mind, period, periodKey);
512
594
  if (sources.texts.length === 0) return false;
513
- if (sources.texts.length === 1) {
595
+ if (!existing && sources.texts.length === 1) {
514
596
  try {
515
597
  await db.insert(summaries).values({
516
598
  mind,
@@ -534,33 +616,39 @@ async function summarizePeriod(mind, period, periodKey) {
534
616
  sLog.info(`promoted single-child ${period} summary for ${mind} (${periodKey})`);
535
617
  return true;
536
618
  }
619
+ const entries = sources.texts.map((text, i) => ({ key: sources.keys[i], text }));
537
620
  const promptKey = `meta_summary_${period}`;
538
621
  const scopeInstruction = getScopeInstruction(mind);
539
622
  const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
540
623
  const userMessage = sources.texts.join("\n\n---\n\n");
541
624
  let content;
542
625
  let deterministic;
543
- const aiResult = await aiCompleteUtility(systemPrompt, userMessage);
626
+ const metadata = {
627
+ source_count: sources.texts.length,
628
+ source_ids: sources.sourceIds
629
+ };
630
+ const aiResult = await complete(systemPrompt, userMessage);
544
631
  if (aiResult) {
545
632
  content = aiResult;
546
633
  deterministic = false;
547
634
  } else {
548
- content = buildPeriodicDeterministicSummary(sources.texts, period, periodKey);
635
+ content = buildPeriodicDeterministicSummary(entries, period, periodKey);
549
636
  deterministic = true;
637
+ if (period === "week" || period === "month") trackProvisionalAttempt(metadata, existingMeta);
550
638
  }
551
- const metadata = {
552
- deterministic,
553
- source_count: sources.texts.length,
554
- source_ids: sources.sourceIds
555
- };
639
+ metadata.deterministic = deterministic;
556
640
  try {
557
- await db.insert(summaries).values({
558
- mind,
559
- period,
560
- period_key: periodKey,
561
- content,
562
- metadata: JSON.stringify(metadata)
563
- }).onConflictDoNothing();
641
+ if (existing) {
642
+ await db.update(summaries).set({ content, metadata: JSON.stringify(metadata) }).where(eq(summaries.id, existing.id));
643
+ } else {
644
+ await db.insert(summaries).values({
645
+ mind,
646
+ period,
647
+ period_key: periodKey,
648
+ content,
649
+ metadata: JSON.stringify(metadata)
650
+ }).onConflictDoNothing();
651
+ }
564
652
  } catch (err) {
565
653
  sLog.error(
566
654
  `failed to persist ${period} summary for ${mind} (${periodKey})`,
@@ -573,9 +661,17 @@ async function summarizePeriod(mind, period, periodKey) {
573
661
  );
574
662
  return true;
575
663
  }
576
- async function summarizeSystem(period, periodKey) {
577
- if (await summaryExists(SYSTEM_MIND, period, periodKey)) return;
664
+ async function summarizeSystem(period, periodKey, complete = aiCompleteUtility) {
578
665
  const db = await getDb();
666
+ const existing = await db.select({ id: summaries.id, metadata: summaries.metadata }).from(summaries).where(
667
+ and(
668
+ eq(summaries.mind, SYSTEM_MIND),
669
+ eq(summaries.period, period),
670
+ eq(summaries.period_key, periodKey)
671
+ )
672
+ ).get();
673
+ const existingMeta = existing ? parseMeta(existing.metadata) : null;
674
+ if (existing && !shouldRetry(period, existingMeta)) return;
579
675
  const rows = await db.select({ mind: summaries.mind, content: summaries.content }).from(summaries).where(
580
676
  and(
581
677
  eq(summaries.period, period),
@@ -585,33 +681,69 @@ async function summarizeSystem(period, periodKey) {
585
681
  ).orderBy(summaries.mind);
586
682
  if (rows.length === 0) return;
587
683
  const minds = [...new Set(rows.map((r) => r.mind))];
588
- const texts = rows.map((r) => `[${r.mind}] ${r.content}`);
684
+ const entries = rows.map((r) => ({
685
+ key: r.mind,
686
+ text: truncateChars(r.content, ROLLUP_CHILD_CHARS)
687
+ }));
688
+ const texts = entries.map((e) => `[${e.key}] ${e.text}`);
589
689
  const promptKey = `meta_summary_${period}`;
590
690
  const scopeInstruction = getScopeInstruction(SYSTEM_MIND);
591
691
  const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
592
692
  const userMessage = texts.join("\n\n---\n\n");
593
693
  let content;
594
694
  let deterministic;
595
- const aiResult = await aiCompleteUtility(systemPrompt, userMessage);
695
+ const metadata = { minds, source_count: rows.length };
696
+ const aiResult = await complete(systemPrompt, userMessage);
596
697
  if (aiResult) {
597
698
  content = aiResult;
598
699
  deterministic = false;
599
700
  } else {
600
- content = buildPeriodicDeterministicSummary(texts, period, periodKey);
701
+ content = buildPeriodicDeterministicSummary(entries, period, periodKey);
601
702
  deterministic = true;
703
+ if (period === "week" || period === "month") trackProvisionalAttempt(metadata, existingMeta);
602
704
  }
705
+ metadata.deterministic = deterministic;
603
706
  try {
604
- await db.insert(summaries).values({
605
- mind: SYSTEM_MIND,
606
- period,
607
- period_key: periodKey,
608
- content,
609
- metadata: JSON.stringify({ deterministic, minds, source_count: rows.length })
610
- }).onConflictDoNothing();
707
+ if (existing) {
708
+ await db.update(summaries).set({ content, metadata: JSON.stringify(metadata) }).where(eq(summaries.id, existing.id));
709
+ } else {
710
+ await db.insert(summaries).values({
711
+ mind: SYSTEM_MIND,
712
+ period,
713
+ period_key: periodKey,
714
+ content,
715
+ metadata: JSON.stringify(metadata)
716
+ }).onConflictDoNothing();
717
+ }
611
718
  } catch (err) {
612
719
  sLog.error(`failed to persist system ${period} summary (${periodKey})`, logger_default.errorData(err));
613
720
  }
614
721
  }
722
+ async function repairProvisionalSummaries(complete = aiCompleteUtility) {
723
+ const db = await getDb();
724
+ const rows = await db.select({
725
+ mind: summaries.mind,
726
+ period: summaries.period,
727
+ period_key: summaries.period_key,
728
+ metadata: summaries.metadata
729
+ }).from(summaries).where(inArray(summaries.period, ["week", "month"]));
730
+ const due = rows.filter((r) => shouldRetry(r.period, parseMeta(r.metadata)));
731
+ due.sort((a, b) => (a.mind === SYSTEM_MIND ? 1 : 0) - (b.mind === SYSTEM_MIND ? 1 : 0));
732
+ for (const r of due) {
733
+ try {
734
+ if (r.mind === SYSTEM_MIND) {
735
+ await summarizeSystem(r.period, r.period_key, complete);
736
+ } else {
737
+ await summarizePeriod(r.mind, r.period, r.period_key, complete);
738
+ }
739
+ } catch (err) {
740
+ sLog.error(
741
+ `failed to repair provisional ${r.period} summary for ${r.mind} (${r.period_key})`,
742
+ logger_default.errorData(err)
743
+ );
744
+ }
745
+ }
746
+ }
615
747
  async function mindsWithTurnSummaries(start, end) {
616
748
  const db = await getDb();
617
749
  const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
@@ -756,6 +888,95 @@ async function backfill() {
756
888
  }
757
889
  }
758
890
  }
891
+ async function healMissing(period, needed, process) {
892
+ if (needed.size === 0) return;
893
+ const db = await getDb();
894
+ const keys = [...needed.keys()];
895
+ const existing = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(and(eq(summaries.period, period), inArray(summaries.period_key, keys)));
896
+ const have = new Set(existing.map((r) => `${r.mind}|${r.period_key}`));
897
+ for (const [key2, minds] of needed) {
898
+ let gap = !have.has(`${SYSTEM_MIND}|${key2}`);
899
+ if (!gap) {
900
+ for (const mind of minds) {
901
+ if (!have.has(`${mind}|${key2}`)) {
902
+ gap = true;
903
+ break;
904
+ }
905
+ }
906
+ }
907
+ if (!gap) continue;
908
+ try {
909
+ await process(key2);
910
+ } catch (err) {
911
+ sLog.error(`reconcile ${period} ${key2} failed`, logger_default.errorData(err));
912
+ }
913
+ }
914
+ }
915
+ function addNeeded(map, key2, mind) {
916
+ let set = map.get(key2);
917
+ if (!set) {
918
+ set = /* @__PURE__ */ new Set();
919
+ map.set(key2, set);
920
+ }
921
+ set.add(mind);
922
+ }
923
+ async function reconcileMissingSummaries(lookbackDays = 7) {
924
+ const db = await getDb();
925
+ const now = /* @__PURE__ */ new Date();
926
+ const windowStart = new Date(now);
927
+ windowStart.setDate(windowStart.getDate() - lookbackDays);
928
+ const currentHourKey = getPeriodKey(now, "hour");
929
+ const currentDayKey = getPeriodKey(now, "day");
930
+ const currentWeekKey = getPeriodKey(now, "week");
931
+ const currentMonthKey = getPeriodKey(now, "month");
932
+ const cutoffDayKey = getPeriodKey(windowStart, "day");
933
+ const turnRows = await db.select({ mind: summaries.mind, created_at: summaries.created_at }).from(summaries).where(
934
+ and(
935
+ eq(summaries.period, "turn"),
936
+ gte(summaries.created_at, utcDateTimeStr(windowStart)),
937
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
938
+ )
939
+ );
940
+ const neededHours = /* @__PURE__ */ new Map();
941
+ for (const r of turnRows) {
942
+ const key2 = getPeriodKey(parseUtcDateTime(r.created_at), "hour");
943
+ if (key2 === currentHourKey) continue;
944
+ addNeeded(neededHours, key2, r.mind);
945
+ }
946
+ await healMissing("hour", neededHours, processHour);
947
+ const hourRows = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(
948
+ and(
949
+ eq(summaries.period, "hour"),
950
+ gte(summaries.period_key, cutoffDayKey),
951
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
952
+ )
953
+ );
954
+ const neededDays = /* @__PURE__ */ new Map();
955
+ for (const r of hourRows) {
956
+ const key2 = r.period_key.slice(0, 10);
957
+ if (key2 === currentDayKey) continue;
958
+ addNeeded(neededDays, key2, r.mind);
959
+ }
960
+ await healMissing("day", neededDays, processDay);
961
+ const dayRows = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(
962
+ and(
963
+ eq(summaries.period, "day"),
964
+ gte(summaries.period_key, cutoffDayKey),
965
+ sql`${summaries.mind} != ${SYSTEM_MIND}`
966
+ )
967
+ );
968
+ const neededWeeks = /* @__PURE__ */ new Map();
969
+ const neededMonths = /* @__PURE__ */ new Map();
970
+ for (const r of dayRows) {
971
+ const d = /* @__PURE__ */ new Date(`${r.period_key}T00:00:00`);
972
+ const weekKey = getPeriodKey(d, "week");
973
+ if (weekKey !== currentWeekKey) addNeeded(neededWeeks, weekKey, r.mind);
974
+ const monthKey = r.period_key.slice(0, 7);
975
+ if (monthKey !== currentMonthKey) addNeeded(neededMonths, monthKey, r.mind);
976
+ }
977
+ await healMissing("week", neededWeeks, processWeek);
978
+ await healMissing("month", neededMonths, processMonth);
979
+ }
759
980
  var Summarizer = class {
760
981
  interval = null;
761
982
  lastHourKey = null;
@@ -777,6 +998,7 @@ var Summarizer = class {
777
998
  this.hasBackfilled = true;
778
999
  }
779
1000
  await reconcileWedgedTurns(WEDGED_TURN_IDLE_MS);
1001
+ await reconcileMissingSummaries();
780
1002
  const now = /* @__PURE__ */ new Date();
781
1003
  const currentHourKey = getPeriodKey(now, "hour");
782
1004
  if (this.lastHourKey && this.lastHourKey !== currentHourKey) {
@@ -799,17 +1021,18 @@ var Summarizer = class {
799
1021
  if (!await summaryExists(SYSTEM_MIND, "month", prevMonthKey)) {
800
1022
  await processMonth(prevMonthKey);
801
1023
  }
1024
+ await repairProvisionalSummaries();
802
1025
  } catch (err) {
803
1026
  sLog.error("tick failed", logger_default.errorData(err));
804
1027
  }
805
1028
  }
806
1029
  };
807
1030
  async function reconcileWedgedTurns(idleMs) {
808
- const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-FOIBNUUA.js");
1031
+ const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-JZRZDFWV.js");
809
1032
  const wedged = await sweepWedgedTurns2(idleMs);
810
1033
  if (wedged.length === 0) return;
811
1034
  summarizeOrphanedTurns2(wedged);
812
- const { tryGetDeliveryManager } = await import("./delivery-manager-JINZRG2W.js");
1035
+ const { tryGetDeliveryManager } = await import("./delivery-manager-TN5KK2AP.js");
813
1036
  const dm = tryGetDeliveryManager();
814
1037
  if (!dm) return;
815
1038
  for (const t of wedged) {
@@ -1010,6 +1233,7 @@ export {
1010
1233
  subscribe,
1011
1234
  subscribeAll,
1012
1235
  publish,
1236
+ isoWeekKeyForDateStr,
1013
1237
  summarizeTurn,
1014
1238
  initSummarizer,
1015
1239
  markErrored,
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-ALKYQULU.js";
7
+ } from "./chunk-WW6C3PLV.js";
8
8
  import {
9
9
  activity
10
- } from "./chunk-ZV7MFISX.js";
10
+ } from "./chunk-2MJ7EOMT.js";
11
11
 
12
12
  // packages/daemon/src/lib/events/event-sequencer.ts
13
13
  var BUFFER_SIZE = 1e3;
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readGlobalConfig,
4
4
  writeGlobalConfig
5
- } from "./chunk-F7H6ATM4.js";
5
+ } from "./chunk-SKNXMUIU.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-T2TP6ZC6.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readGlobalConfig
4
- } from "./chunk-F7H6ATM4.js";
4
+ } from "./chunk-SKNXMUIU.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";