volute 0.43.1 → 0.45.1

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 (132) hide show
  1. package/dist/{activity-events-UISXY4Y5.js → activity-events-OMCSM7XW.js} +3 -3
  2. package/dist/{ai-service-FBCCEYF5.js → ai-service-534WUSDL.js} +4 -4
  3. package/dist/{archive-I32WHCJB.js → archive-6R4TZMYI.js} +3 -3
  4. package/dist/{auth-3RIG6GKN.js → auth-FRZJVQCS.js} +4 -4
  5. package/dist/avatar-image-BNWEXSA2.js +16 -0
  6. package/dist/backup-OZXHL3JS.js +381 -0
  7. package/dist/{chat-SDSX5TVV.js → chat-I4EDDIQC.js} +2 -2
  8. package/dist/{chunk-AD46AYZQ.js → chunk-2BONPXAF.js} +1 -1
  9. package/dist/{chunk-ONM2AZFL.js → chunk-322KE457.js} +14 -2
  10. package/dist/{chunk-PGPLCJPK.js → chunk-3ERIL673.js} +1 -1
  11. package/dist/{chunk-QQ2MVQU3.js → chunk-5AUKCNPE.js} +10 -10
  12. package/dist/{chunk-XDTKJNW5.js → chunk-5GT7URXJ.js} +1 -1
  13. package/dist/{chunk-DMUPZQWP.js → chunk-77BVGCTZ.js} +8 -8
  14. package/dist/{chunk-EFL5UOY4.js → chunk-CYI534DJ.js} +3 -3
  15. package/dist/{chunk-57LBQ7JX.js → chunk-FHWUQZXE.js} +1 -1
  16. package/dist/{chunk-YAMO2U3N.js → chunk-H7IYFRAL.js} +1 -1
  17. package/dist/{chunk-M3PGGSBH.js → chunk-HVU45COX.js} +1 -1
  18. package/dist/{chunk-O3DXAR44.js → chunk-IEWZ7VD7.js} +2 -2
  19. package/dist/{chunk-DE4EKQFH.js → chunk-IUXTYXFI.js} +3 -3
  20. package/dist/{chunk-K4EHBHPE.js → chunk-JSRMEY52.js} +7 -5
  21. package/dist/{chunk-QHG4OMZL.js → chunk-KDLYIECM.js} +37 -32
  22. package/dist/{chunk-EE7SZJON.js → chunk-KP3KWNG6.js} +2 -2
  23. package/dist/chunk-KVKP6VQ5.js +318 -0
  24. package/dist/{chunk-JVK6QQIC.js → chunk-LAXUUY7O.js} +2 -2
  25. package/dist/{chunk-C6ESRQZ7.js → chunk-OXXTPILE.js} +1 -1
  26. package/dist/{chunk-ATEX3ZPG.js → chunk-Q2XBKQ2D.js} +2 -2
  27. package/dist/{chunk-GZJFC76M.js → chunk-QMJJWTNL.js} +1 -1
  28. package/dist/chunk-SQL7M4HJ.js +120 -0
  29. package/dist/{chunk-YSV3VJQK.js → chunk-TSK5PI4L.js} +1 -1
  30. package/dist/{chunk-4SVONUTC.js → chunk-U4FNXDEJ.js} +350 -207
  31. package/dist/{chunk-2HIVYZDB.js → chunk-UMI4UIJG.js} +1 -1
  32. package/dist/{chunk-Y6O35D7K.js → chunk-V4GYPD5L.js} +41 -16
  33. package/dist/{chunk-KINGVQSR.js → chunk-Y6OPHHGM.js} +5 -5
  34. package/dist/{chunk-SPLPKBES.js → chunk-ZIR7HSAX.js} +6 -9
  35. package/dist/{chunk-7G2EEYXX.js → chunk-ZMIMSIIT.js} +2 -2
  36. package/dist/{chunk-L3IV23ZG.js → chunk-ZV7MFISX.js} +2 -0
  37. package/dist/cli.js +53 -22
  38. package/dist/{cloud-sync-KOEBUFW5.js → cloud-sync-Y7LQPRE3.js} +16 -16
  39. package/dist/{conversations-67WWW25C.js → conversations-G4LYMHAA.js} +4 -4
  40. package/dist/{create-7W3LV3J7.js → create-2YY7IPMS.js} +1 -1
  41. package/dist/{daemon-restart-EZXB73W7.js → daemon-restart-ABP54YN6.js} +7 -7
  42. package/dist/daemon.js +679 -478
  43. package/dist/{db-CZV62BOQ.js → db-JFLCJCUG.js} +2 -2
  44. package/dist/{db-IJL6B26S.js → db-LZTLA7XC.js} +1 -1
  45. package/dist/delivery-manager-R5STGLXB.js +32 -0
  46. package/dist/{delivery-router-HXBHYHJM.js → delivery-router-6DE3BPHO.js} +3 -3
  47. package/dist/{down-RRNYIGCU.js → down-XCAZXWLB.js} +5 -5
  48. package/dist/echo-text-NQXYYYCU.js +32 -0
  49. package/dist/{exec-SJK7VUTC.js → exec-4GWHAHS3.js} +3 -3
  50. package/dist/{export-7LPCIB5J.js → export-ID7YUSLZ.js} +3 -3
  51. package/dist/{extensions-PQPZT6LS.js → extensions-AYSPKE33.js} +16 -16
  52. package/dist/{files-LV7VAKPC.js → files-BQ2WB5KC.js} +3 -3
  53. package/dist/{import-XPECCRQ7.js → import-SKID37QX.js} +6 -6
  54. package/dist/{isolation-5AYEY55P.js → isolation-5MB3J73F.js} +3 -3
  55. package/dist/{login-KJSN4JDA.js → login-LM4NF4ZB.js} +2 -2
  56. package/dist/{logout-TMJGUQV2.js → logout-CTJVIZGA.js} +2 -2
  57. package/dist/message-delivery-IUAXRPFE.js +34 -0
  58. package/dist/{mind-G3WEWDM4.js → mind-KLHC7XYR.js} +6 -6
  59. package/dist/{mind-activity-tracker-JHAT6456.js → mind-activity-tracker-BGHJE7XA.js} +4 -4
  60. package/dist/mind-service-BF2LUQMH.js +36 -0
  61. package/dist/{package-24TL6DTF.js → package-BMHV7UHN.js} +1 -1
  62. package/dist/{registry-7LQHTRUX.js → registry-NUJAGLQJ.js} +2 -2
  63. package/dist/{sandbox-SJH3XOPZ.js → sandbox-72MWV4JL.js} +4 -4
  64. package/dist/scheduler-YHJRRV6E.js +30 -0
  65. package/dist/{schema-LRUTFOER.js → schema-PVNG3PQM.js} +1 -1
  66. package/dist/{seed-HHSJTM6O.js → seed-4MKQ5QKG.js} +1 -1
  67. package/dist/{seed-cmd-UPQY35RG.js → seed-cmd-LCXMR7VV.js} +2 -2
  68. package/dist/{seed-create-MDRMXMSB.js → seed-create-N2BF5LQX.js} +3 -3
  69. package/dist/{seed-sprout-4M7IQWMH.js → seed-sprout-6ZDP23YI.js} +7 -7
  70. package/dist/{send-ZBZWIH5V.js → send-O6W53VBM.js} +37 -42
  71. package/dist/{service-4EVZKGSA.js → service-AQVPUANO.js} +4 -4
  72. package/dist/{service-install-SHRM6NHK.js → service-install-4QNUDKLM.js} +5 -5
  73. package/dist/{setup-ZD2LMBR3.js → setup-3O6O2JZ7.js} +7 -7
  74. package/dist/{setup-25MMJ3J4.js → setup-73YBJZQL.js} +3 -3
  75. package/dist/skills/memory/SKILL.md +2 -1
  76. package/dist/skills/orientation/SKILL.md +1 -1
  77. package/dist/skills/plan-coordinator/SKILL.md +2 -2
  78. package/dist/skills/volute-admin/SKILL.md +9 -6
  79. package/dist/skills/volute-mind/SKILL.md +15 -16
  80. package/dist/skills/volute-mind/references/extensions.md +5 -3
  81. package/dist/skills/volute-mind/references/integrations.md +2 -2
  82. package/dist/skills/volute-mind/references/routing.md +19 -23
  83. package/dist/skills/volute-mind/references/variants.md +10 -8
  84. package/dist/{skills-4KQ377GN.js → skills-ND4ZFYUW.js} +5 -5
  85. package/dist/sleep-manager-YAHDOIU5.js +34 -0
  86. package/dist/{spirit-53DQPRNR.js → spirit-II3HOERM.js} +7 -7
  87. package/dist/{sprout-YKM2M6D6.js → sprout-4KUG76AQ.js} +1 -1
  88. package/dist/{src-O4PRLMKM.js → src-JAOVXO5W.js} +1 -0
  89. package/dist/{src-3EGHHKZK.js → src-XFY3XDD6.js} +16 -11
  90. package/dist/{src-L2Z7X5UT.js → src-YH6PQ7LJ.js} +264 -52
  91. package/dist/{status-WTU2WHXY.js → status-E5DQNCAO.js} +5 -5
  92. package/dist/system-chat-E65T2HSW.js +34 -0
  93. package/dist/{tailscale-Q7ZJL7S7.js → tailscale-QAWVKQKU.js} +2 -2
  94. package/dist/{turn-tracker-JZYERJ76.js → turn-tracker-QQIVVXBI.js} +7 -5
  95. package/dist/{up-LFZA4GI5.js → up-LG56MPBI.js} +6 -6
  96. package/dist/{update-EJ7Q77ZX.js → update-7TYVH5FM.js} +5 -5
  97. package/dist/{update-check-ACYYNKTW.js → update-check-VSCSJ7KX.js} +3 -3
  98. package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
  99. package/dist/{version-notify-SRQJCQEK.js → version-notify-CODJOUW2.js} +17 -17
  100. package/dist/web-assets/assets/index-B1JBQv2m.css +1 -0
  101. package/dist/web-assets/assets/index-Dac11sKB.js +86 -0
  102. package/dist/web-assets/ext-theme.css +5 -0
  103. package/dist/web-assets/index.html +2 -2
  104. package/package.json +1 -1
  105. package/packages/extensions/notes/dist/ui/assets/{index-BbgANhFm.js → index-D0-DgYyj.js} +1 -1
  106. package/packages/extensions/notes/dist/ui/index.html +1 -1
  107. package/packages/extensions/notes/skills/notes/SKILL.md +31 -3
  108. package/packages/extensions/pages/dist/ui/assets/{index-Dxk-DYrf.js → index-CQjEQg1r.js} +1 -1
  109. package/packages/extensions/pages/dist/ui/index.html +1 -1
  110. package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
  111. package/templates/_base/.init/.config/prompts.json +1 -1
  112. package/templates/_base/.init/.local/hooks/startup-context.ts +21 -4
  113. package/templates/_base/.init/MEMORY.md +2 -1
  114. package/templates/_base/home/VOLUTE.md +6 -9
  115. package/templates/_base/src/lib/startup.ts +1 -1
  116. package/templates/claude/.init/CLAUDE.md +1 -1
  117. package/templates/claude/src/agent.ts +30 -3
  118. package/templates/claude/src/lib/stream-consumer.ts +7 -4
  119. package/templates/codex/.init/AGENTS.md +4 -2
  120. package/templates/codex/src/agent.ts +13 -7
  121. package/templates/pi/.init/MINDS.md +4 -2
  122. package/templates/pi/src/lib/event-handler.ts +6 -2
  123. package/dist/delivery-manager-OMCCJEVP.js +0 -32
  124. package/dist/echo-text-KFSUCZXL.js +0 -32
  125. package/dist/message-delivery-HRGFNWO7.js +0 -34
  126. package/dist/mind-service-2C7AINBO.js +0 -36
  127. package/dist/scheduler-VON2TB52.js +0 -30
  128. package/dist/sleep-manager-AG5SQDDI.js +0 -34
  129. package/dist/system-chat-54RN3K67.js +0 -34
  130. package/dist/web-assets/assets/index-D7udpBCx.css +0 -1
  131. package/dist/web-assets/assets/index-DTDmEfPU.js +0 -79
  132. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadMergedEnv
4
- } from "./chunk-XDTKJNW5.js";
4
+ } from "./chunk-5GT7URXJ.js";
5
5
  import {
6
6
  spiritDir
7
- } from "./chunk-QQ2MVQU3.js";
7
+ } from "./chunk-5AUKCNPE.js";
8
8
  import {
9
9
  hashSkillDir,
10
10
  importSkillFromDir,
11
11
  removeSharedSkill,
12
12
  sharedSkillsDir
13
- } from "./chunk-KINGVQSR.js";
13
+ } from "./chunk-Y6OPHHGM.js";
14
14
  import {
15
15
  addMessage,
16
16
  createChannel,
@@ -20,7 +20,7 @@ import {
20
20
  getParticipants,
21
21
  joinChannel,
22
22
  publish as publish2
23
- } from "./chunk-K4EHBHPE.js";
23
+ } from "./chunk-JSRMEY52.js";
24
24
  import {
25
25
  readVoluteConfig,
26
26
  writeVoluteConfig
@@ -28,52 +28,52 @@ import {
28
28
  import {
29
29
  isSandboxEnabled,
30
30
  wrapForSandbox
31
- } from "./chunk-C6ESRQZ7.js";
31
+ } from "./chunk-OXXTPILE.js";
32
32
  import {
33
33
  markIdle,
34
34
  onMindEvent
35
- } from "./chunk-M3PGGSBH.js";
35
+ } from "./chunk-HVU45COX.js";
36
36
  import {
37
37
  extractTextContent,
38
38
  getRoutingConfig,
39
39
  resolveDeliveryMode,
40
40
  resolveRoute,
41
41
  setRoutesChangeListener
42
- } from "./chunk-SPLPKBES.js";
42
+ } from "./chunk-ZIR7HSAX.js";
43
43
  import {
44
44
  clearMind,
45
45
  getPrompt,
46
46
  linkInboundToActiveTurn,
47
47
  publish as publish3,
48
48
  summarizeOrphanedTurns
49
- } from "./chunk-Y6O35D7K.js";
49
+ } from "./chunk-V4GYPD5L.js";
50
50
  import {
51
51
  chownMindDir,
52
52
  exec,
53
53
  isIsolationEnabled,
54
54
  mindUserName,
55
55
  wrapForIsolation
56
- } from "./chunk-PGPLCJPK.js";
56
+ } from "./chunk-3ERIL673.js";
57
57
  import {
58
58
  aiCompleteUtility,
59
59
  getAiConfig,
60
60
  resolveApiKey
61
- } from "./chunk-YSV3VJQK.js";
61
+ } from "./chunk-TSK5PI4L.js";
62
62
  import {
63
63
  readGlobalConfig,
64
64
  writeGlobalConfig
65
- } from "./chunk-ONM2AZFL.js";
65
+ } from "./chunk-322KE457.js";
66
66
  import {
67
67
  getOrCreateMindUser,
68
68
  getOrCreateSystemUser,
69
69
  getUser,
70
70
  getUserByUsername,
71
71
  syncMindProfile
72
- } from "./chunk-EFL5UOY4.js";
72
+ } from "./chunk-CYI534DJ.js";
73
73
  import {
74
74
  publish,
75
75
  subscribe
76
- } from "./chunk-7G2EEYXX.js";
76
+ } from "./chunk-ZMIMSIIT.js";
77
77
  import {
78
78
  logger_default
79
79
  } from "./chunk-T2TP6ZC6.js";
@@ -87,7 +87,7 @@ import {
87
87
  stateDir,
88
88
  voluteHome,
89
89
  voluteSystemDir
90
- } from "./chunk-YAMO2U3N.js";
90
+ } from "./chunk-H7IYFRAL.js";
91
91
  import {
92
92
  activity,
93
93
  deliveryQueue,
@@ -95,10 +95,10 @@ import {
95
95
  mindHistory,
96
96
  mindNotices,
97
97
  sessions
98
- } from "./chunk-L3IV23ZG.js";
98
+ } from "./chunk-ZV7MFISX.js";
99
99
 
100
100
  // packages/daemon/src/lib/delivery/message-delivery.ts
101
- import { and as and4, eq as eq5, inArray as inArray3 } from "drizzle-orm";
101
+ import { and as and5, eq as eq6, inArray as inArray3 } from "drizzle-orm";
102
102
 
103
103
  // packages/daemon/src/lib/daemon/sleep-manager.ts
104
104
  import { execFile as execFile2, spawn as spawnChild } from "child_process";
@@ -337,8 +337,9 @@ function resolveMindToken(token) {
337
337
  }
338
338
 
339
339
  // packages/daemon/src/lib/daemon/notices.ts
340
- import { and, asc, eq, lte, sql } from "drizzle-orm";
340
+ import { and, asc, eq, lte, or, sql } from "drizzle-orm";
341
341
  var nlog = logger_default.child("notices");
342
+ var MIND_LEVEL_SESSION = "";
342
343
  var MAX_NOTICES_PER_SESSION = 100;
343
344
  async function recordNotice(input) {
344
345
  try {
@@ -360,7 +361,12 @@ async function recordNotice(input) {
360
361
  }
361
362
  async function drainNotices(mind, session, limit = MAX_NOTICES_PER_SESSION) {
362
363
  const db = await getDb();
363
- return db.select().from(mindNotices).where(and(eq(mindNotices.mind, mind), eq(mindNotices.session, session))).orderBy(asc(mindNotices.id)).limit(limit);
364
+ return db.select().from(mindNotices).where(
365
+ and(
366
+ eq(mindNotices.mind, mind),
367
+ or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION))
368
+ )
369
+ ).orderBy(asc(mindNotices.id)).limit(limit);
364
370
  }
365
371
  async function clearDeliveredNotices(mind, session, uptoId) {
366
372
  try {
@@ -368,7 +374,7 @@ async function clearDeliveredNotices(mind, session, uptoId) {
368
374
  await db.delete(mindNotices).where(
369
375
  and(
370
376
  eq(mindNotices.mind, mind),
371
- eq(mindNotices.session, session),
377
+ or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION)),
372
378
  lte(mindNotices.id, uptoId)
373
379
  )
374
380
  );
@@ -379,25 +385,51 @@ async function clearDeliveredNotices(mind, session, uptoId) {
379
385
  var NOTICE_HEADER = "[Notices] While you were unavailable, one or more turns failed. You're back now:";
380
386
  function formatNotices(notices) {
381
387
  if (notices.length === 0) return null;
382
- const groups = /* @__PURE__ */ new Map();
383
- for (const n of notices) {
384
- const time = localHM(n.created_at);
385
- const g = groups.get(n.reason);
386
- if (g) {
387
- g.count += 1;
388
- g.detail = n.detail;
389
- g.last = time;
390
- } else {
391
- groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
388
+ const failures = notices.filter((n) => n.kind !== "extension" && n.kind !== "budget");
389
+ const budgets = notices.filter((n) => n.kind === "budget");
390
+ const extensions = notices.filter((n) => n.kind === "extension");
391
+ const blocks = [];
392
+ if (failures.length > 0) {
393
+ const groups = /* @__PURE__ */ new Map();
394
+ for (const n of failures) {
395
+ const time = localHM(n.created_at);
396
+ const g = groups.get(n.reason);
397
+ if (g) {
398
+ g.count += 1;
399
+ g.detail = n.detail;
400
+ g.last = time;
401
+ } else {
402
+ groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
403
+ }
392
404
  }
405
+ const lines = [...groups.values()].map((g) => {
406
+ const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
407
+ const plural = g.count === 1 ? "turn" : "turns";
408
+ return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
409
+ });
410
+ blocks.push(`${NOTICE_HEADER}
411
+ ${lines.join("\n")}`);
393
412
  }
394
- const lines = [...groups.values()].map((g) => {
395
- const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
396
- const plural = g.count === 1 ? "turn" : "turns";
397
- return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
398
- });
399
- return `${NOTICE_HEADER}
400
- ${lines.join("\n")}`;
413
+ if (budgets.length > 0) {
414
+ const lines = budgets.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
415
+ blocks.push(`[Budget]
416
+ ${lines.join("\n")}`);
417
+ }
418
+ if (extensions.length > 0) {
419
+ const byExt = /* @__PURE__ */ new Map();
420
+ for (const n of extensions) {
421
+ const arr = byExt.get(n.reason);
422
+ if (arr) arr.push(n);
423
+ else byExt.set(n.reason, [n]);
424
+ }
425
+ for (const [ext, items] of byExt) {
426
+ const header = `[${ext.charAt(0).toUpperCase()}${ext.slice(1)}]`;
427
+ const lines = items.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
428
+ blocks.push(`${header}
429
+ ${lines.join("\n")}`);
430
+ }
431
+ }
432
+ return blocks.join("\n\n");
401
433
  }
402
434
  function localHM(createdAt) {
403
435
  const iso = createdAt.endsWith("Z") ? createdAt : `${createdAt.replace(" ", "T")}Z`;
@@ -861,6 +893,8 @@ var MindManager = class {
861
893
  const parts = [];
862
894
  if (context.type === "merge" || context.type === "merged") {
863
895
  parts.push(await getPrompt("merge_message", { name: String(context.name ?? "") }));
896
+ } else if (context.type === "split") {
897
+ parts.push(await getPrompt("split_message", { name, parent: String(context.parent ?? "") }));
864
898
  } else if (context.type === "sprouted") {
865
899
  parts.push(await getPrompt("sprout_message"));
866
900
  } else if (context.type === "upgraded") {
@@ -905,7 +939,7 @@ var MindManager = class {
905
939
  if (this.shuttingDown || this.stopping.has(name)) return;
906
940
  mlog.error(`mind ${name} exited with code ${code}`);
907
941
  try {
908
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-AG5SQDDI.js");
942
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-YAHDOIU5.js");
909
943
  const sleepMgr = getSleepManagerIfReady2();
910
944
  const sleepState = sleepMgr?.getState(name);
911
945
  if (sleepState?.sleeping) {
@@ -935,15 +969,15 @@ var MindManager = class {
935
969
  (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
936
970
  );
937
971
  try {
938
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-OMCCJEVP.js");
972
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-R5STGLXB.js");
939
973
  getDeliveryManager2().clearMindSessions(name);
940
974
  } catch (err) {
941
975
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
942
976
  mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
943
977
  }
944
978
  }
945
- import("./mind-activity-tracker-JHAT6456.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
946
- import("./activity-events-UISXY4Y5.js").then(
979
+ import("./mind-activity-tracker-BGHJE7XA.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
980
+ import("./activity-events-OMCSM7XW.js").then(
947
981
  ({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
948
982
  ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
949
983
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
@@ -1001,7 +1035,7 @@ var MindManager = class {
1001
1035
  mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
1002
1036
  }
1003
1037
  try {
1004
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-OMCCJEVP.js");
1038
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-R5STGLXB.js");
1005
1039
  getDeliveryManager2().clearMindSessions(name);
1006
1040
  } catch (err) {
1007
1041
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
@@ -1009,7 +1043,7 @@ var MindManager = class {
1009
1043
  }
1010
1044
  }
1011
1045
  try {
1012
- const { clearEchoTextCache } = await import("./echo-text-KFSUCZXL.js");
1046
+ const { clearEchoTextCache } = await import("./echo-text-NQXYYYCU.js");
1013
1047
  clearEchoTextCache(name);
1014
1048
  } catch (err) {
1015
1049
  mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
@@ -1462,6 +1496,24 @@ async function buildContext(manifest, dataDir, authMw) {
1462
1496
  },
1463
1497
  getSystemsConfig: () => readSystemsConfig(),
1464
1498
  announceToSystem: (text) => announceToSystem(text),
1499
+ recordNotice: async (mindName, text) => {
1500
+ try {
1501
+ const user = await getUserByUsername(mindName);
1502
+ if (user?.user_type !== "mind") return;
1503
+ await recordNotice({
1504
+ mind: mindName,
1505
+ session: MIND_LEVEL_SESSION,
1506
+ kind: "extension",
1507
+ reason: manifest.id,
1508
+ detail: text.slice(0, 500)
1509
+ });
1510
+ } catch (err) {
1511
+ logger_default.warn(
1512
+ `extension ${manifest.id}: failed to record notice for ${mindName}`,
1513
+ logger_default.errorData(err)
1514
+ );
1515
+ }
1516
+ },
1465
1517
  isIsolationEnabled,
1466
1518
  getMindUser: mindUserName,
1467
1519
  dataDir
@@ -1646,9 +1698,9 @@ function resolveSkillsDir(manifest) {
1646
1698
  }
1647
1699
  async function discoverBuiltinExtensions(disabledIds) {
1648
1700
  const builtins = [
1649
- { id: "notes", load: async () => (await import("./src-L2Z7X5UT.js")).default },
1650
- { id: "pages", load: async () => (await import("./src-3EGHHKZK.js")).default },
1651
- { id: "plan", load: async () => (await import("./src-O4PRLMKM.js")).default }
1701
+ { id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
1702
+ { id: "pages", load: async () => (await import("./src-XFY3XDD6.js")).default },
1703
+ { id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
1652
1704
  ];
1653
1705
  const results = [];
1654
1706
  for (const { id, load } of builtins) {
@@ -1781,6 +1833,15 @@ async function loadAllExtensions(app, authMw) {
1781
1833
  }
1782
1834
  return c.json(result);
1783
1835
  });
1836
+ app.get("/api/extensions/mind-docs", (c) => {
1837
+ const result = loaded.filter(({ manifest }) => manifest.mindDoc || manifest.commands).map(({ manifest }) => ({
1838
+ id: manifest.id,
1839
+ name: manifest.name,
1840
+ mindDoc: manifest.mindDoc ?? manifest.description ?? "",
1841
+ commands: Object.keys(manifest.commands ?? {})
1842
+ }));
1843
+ return c.json(result);
1844
+ });
1784
1845
  }
1785
1846
  function getLoadedExtensions() {
1786
1847
  return loaded.map(({ manifest }) => {
@@ -1888,7 +1949,7 @@ async function installNpmExtension(pkg) {
1888
1949
  throw new Error(`Extension "${pkg}" is already installed`);
1889
1950
  }
1890
1951
  const dir = ensureExtensionsNpmDir();
1891
- const { exec: exec2 } = await import("./exec-SJK7VUTC.js");
1952
+ const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1892
1953
  try {
1893
1954
  await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
1894
1955
  } catch (err) {
@@ -1909,7 +1970,7 @@ async function uninstallNpmExtension(pkg) {
1909
1970
  packages.splice(idx, 1);
1910
1971
  writeExtensionsConfig(packages);
1911
1972
  try {
1912
- const { exec: exec2 } = await import("./exec-SJK7VUTC.js");
1973
+ const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1913
1974
  await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
1914
1975
  } catch (err) {
1915
1976
  logger_default.warn(
@@ -2466,24 +2527,24 @@ var TokenBudget = class {
2466
2527
  this.budgets.delete(mind);
2467
2528
  }
2468
2529
  recordUsage(mind, inputTokens, outputTokens) {
2469
- const state = this.budgets.get(mind);
2470
- if (!state) return;
2471
- state.tokensUsed += inputTokens + outputTokens;
2530
+ const state2 = this.budgets.get(mind);
2531
+ if (!state2) return;
2532
+ state2.tokensUsed += inputTokens + outputTokens;
2472
2533
  this.dirty.add(mind);
2473
2534
  }
2474
2535
  /** Returns current budget status. Does not mutate state — call acknowledgeWarning() after delivering a warning. */
2475
2536
  checkBudget(mind) {
2476
- const state = this.budgets.get(mind);
2477
- if (!state) return "ok";
2478
- const pct = state.tokensUsed / state.tokenLimit;
2537
+ const state2 = this.budgets.get(mind);
2538
+ if (!state2) return "ok";
2539
+ const pct = state2.tokensUsed / state2.tokenLimit;
2479
2540
  if (pct >= 1) return "exceeded";
2480
- if (pct >= 0.8 && !state.warningInjected) return "warning";
2541
+ if (pct >= 0.8 && !state2.warningInjected) return "warning";
2481
2542
  return "ok";
2482
2543
  }
2483
2544
  /** Mark warning as delivered for this period. Call after successfully injecting the warning. */
2484
2545
  acknowledgeWarning(mind) {
2485
- const state = this.budgets.get(mind);
2486
- if (state) state.warningInjected = true;
2546
+ const state2 = this.budgets.get(mind);
2547
+ if (state2) state2.warningInjected = true;
2487
2548
  }
2488
2549
  /**
2489
2550
  * Returns true exactly once per budget period when the mind first crosses its limit,
@@ -2491,50 +2552,50 @@ var TokenBudget = class {
2491
2552
  * every subsequent usage event. The flag resets when the period rolls over.
2492
2553
  */
2493
2554
  noteExceeded(mind) {
2494
- const state = this.budgets.get(mind);
2495
- if (!state) return false;
2496
- if (state.tokensUsed < state.tokenLimit) return false;
2497
- if (state.exceededNotified) return false;
2498
- state.exceededNotified = true;
2555
+ const state2 = this.budgets.get(mind);
2556
+ if (!state2) return false;
2557
+ if (state2.tokensUsed < state2.tokenLimit) return false;
2558
+ if (state2.exceededNotified) return false;
2559
+ state2.exceededNotified = true;
2499
2560
  this.dirty.add(mind);
2500
2561
  return true;
2501
2562
  }
2502
2563
  enqueue(mind, message) {
2503
- const state = this.budgets.get(mind);
2504
- if (!state) return;
2505
- if (state.queue.length >= MAX_QUEUE_SIZE) {
2506
- state.queue.shift();
2564
+ const state2 = this.budgets.get(mind);
2565
+ if (!state2) return;
2566
+ if (state2.queue.length >= MAX_QUEUE_SIZE) {
2567
+ state2.queue.shift();
2507
2568
  }
2508
- state.queue.push(message);
2569
+ state2.queue.push(message);
2509
2570
  }
2510
2571
  drain(mind) {
2511
- const state = this.budgets.get(mind);
2512
- if (!state) return [];
2513
- const messages2 = state.queue;
2514
- state.queue = [];
2572
+ const state2 = this.budgets.get(mind);
2573
+ if (!state2) return [];
2574
+ const messages2 = state2.queue;
2575
+ state2.queue = [];
2515
2576
  return messages2;
2516
2577
  }
2517
2578
  getUsage(mind) {
2518
- const state = this.budgets.get(mind);
2519
- if (!state) return null;
2579
+ const state2 = this.budgets.get(mind);
2580
+ if (!state2) return null;
2520
2581
  return {
2521
- tokensUsed: state.tokensUsed,
2522
- tokenLimit: state.tokenLimit,
2523
- periodMinutes: state.periodMinutes,
2524
- periodStart: state.periodStart,
2525
- queueLength: state.queue.length,
2526
- percentUsed: Math.round(state.tokensUsed / state.tokenLimit * 100)
2582
+ tokensUsed: state2.tokensUsed,
2583
+ tokenLimit: state2.tokenLimit,
2584
+ periodMinutes: state2.periodMinutes,
2585
+ periodStart: state2.periodStart,
2586
+ queueLength: state2.queue.length,
2587
+ percentUsed: Math.round(state2.tokensUsed / state2.tokenLimit * 100)
2527
2588
  };
2528
2589
  }
2529
2590
  async tick() {
2530
2591
  const now = Date.now();
2531
- for (const [mind, state] of this.budgets) {
2532
- const elapsed = now - state.periodStart;
2533
- if (elapsed >= state.periodMinutes * 6e4) {
2534
- state.tokensUsed = 0;
2535
- state.periodStart = now;
2536
- state.warningInjected = false;
2537
- state.exceededNotified = false;
2592
+ for (const [mind, state2] of this.budgets) {
2593
+ const elapsed = now - state2.periodStart;
2594
+ if (elapsed >= state2.periodMinutes * 6e4) {
2595
+ state2.tokensUsed = 0;
2596
+ state2.periodStart = now;
2597
+ state2.warningInjected = false;
2598
+ state2.exceededNotified = false;
2538
2599
  this.dirty.add(mind);
2539
2600
  const queued = this.drain(mind);
2540
2601
  if (queued.length > 0) {
@@ -2552,24 +2613,24 @@ var TokenBudget = class {
2552
2613
  this.dirty.clear();
2553
2614
  const writes = [];
2554
2615
  for (const mind of flushing) {
2555
- const state = this.budgets.get(mind);
2556
- if (state) writes.push(this.saveBudgetState(mind, state).catch(() => this.dirty.add(mind)));
2616
+ const state2 = this.budgets.get(mind);
2617
+ if (state2) writes.push(this.saveBudgetState(mind, state2).catch(() => this.dirty.add(mind)));
2557
2618
  }
2558
2619
  await Promise.all(writes);
2559
2620
  }
2560
2621
  budgetStatePath(mind) {
2561
2622
  return resolve6(stateDir(mind), "budget.json");
2562
2623
  }
2563
- async saveBudgetState(mind, state) {
2624
+ async saveBudgetState(mind, state2) {
2564
2625
  try {
2565
2626
  const dir = stateDir(mind);
2566
2627
  await mkdir(dir, { recursive: true });
2567
2628
  const data = {
2568
- periodStart: state.periodStart,
2569
- tokensUsed: state.tokensUsed,
2570
- warningInjected: state.warningInjected,
2571
- exceededNotified: state.exceededNotified,
2572
- queue: state.queue
2629
+ periodStart: state2.periodStart,
2630
+ tokensUsed: state2.tokensUsed,
2631
+ warningInjected: state2.warningInjected,
2632
+ exceededNotified: state2.exceededNotified,
2633
+ queue: state2.queue
2573
2634
  };
2574
2635
  await writeFile2(this.budgetStatePath(mind), `${JSON.stringify(data)}
2575
2636
  `);
@@ -2615,8 +2676,8 @@ ${summary}`
2615
2676
  tlog.info(`replayed ${messages2.length} queued message(s) for ${mindName}`);
2616
2677
  } catch (err) {
2617
2678
  tlog.warn(`failed to replay for ${mindName}`, logger_default.errorData(err));
2618
- const state = this.budgets.get(mindName);
2619
- if (state) state.queue.push(...messages2);
2679
+ const state2 = this.budgets.get(mindName);
2680
+ if (state2) state2.queue.push(...messages2);
2620
2681
  }
2621
2682
  }
2622
2683
  };
@@ -2710,7 +2771,7 @@ async function wakeMind(name) {
2710
2771
  async function startSpiritFull(name) {
2711
2772
  const entry = await findMind(name);
2712
2773
  if (entry?.dir) {
2713
- const { registerMindDir } = await import("./delivery-router-HXBHYHJM.js");
2774
+ const { registerMindDir } = await import("./delivery-router-6DE3BPHO.js");
2714
2775
  registerMindDir(name, entry.dir);
2715
2776
  }
2716
2777
  await getMindManager().startMind(name);
@@ -2732,8 +2793,8 @@ async function stopSpiritFull(name) {
2732
2793
  }).catch((err) => logger_default.error("failed to publish spirit_stopped activity", logger_default.errorData(err)));
2733
2794
  }
2734
2795
  async function ensureCreatorDM(mindName, creatorUsername) {
2735
- const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-3RIG6GKN.js");
2736
- const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-67WWW25C.js");
2796
+ const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FRZJVQCS.js");
2797
+ const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-G4LYMHAA.js");
2737
2798
  const mindUser = await getOrCreateMindUser2(mindName);
2738
2799
  const creatorUser = await getUserByUsername2(creatorUsername);
2739
2800
  if (!creatorUser) {
@@ -2785,6 +2846,22 @@ function formatCurrentDate() {
2785
2846
  day: "numeric"
2786
2847
  });
2787
2848
  }
2849
+ function formatWakeTime(wakeAt) {
2850
+ if (wakeAt) {
2851
+ const date = new Date(wakeAt);
2852
+ if (!Number.isNaN(date.getTime())) {
2853
+ const time = date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
2854
+ const day = date.toLocaleDateString("en-US", {
2855
+ weekday: "long",
2856
+ month: "long",
2857
+ day: "numeric"
2858
+ });
2859
+ return `at ${time} on ${day}`;
2860
+ }
2861
+ return `at ${wakeAt}`;
2862
+ }
2863
+ return "when something needs you \u2014 a message, a mention, or someone waking you";
2864
+ }
2788
2865
  function formatDuration(from, to) {
2789
2866
  const ms = to.getTime() - from.getTime();
2790
2867
  const hours = Math.floor(ms / 36e5);
@@ -2826,9 +2903,9 @@ var SleepManager = class {
2826
2903
  try {
2827
2904
  if (existsSync8(this.statePath)) {
2828
2905
  const data = JSON.parse(readFileSync7(this.statePath, "utf-8"));
2829
- for (const [name, state] of Object.entries(data)) {
2830
- state.triggerWakeHistory ??= [];
2831
- this.states.set(name, state);
2906
+ for (const [name, state2] of Object.entries(data)) {
2907
+ state2.triggerWakeHistory ??= [];
2908
+ this.states.set(name, state2);
2832
2909
  }
2833
2910
  }
2834
2911
  } catch (err) {
@@ -2837,8 +2914,8 @@ var SleepManager = class {
2837
2914
  }
2838
2915
  saveState() {
2839
2916
  const data = {};
2840
- for (const [name, state] of this.states) {
2841
- if (state.sleeping) data[name] = state;
2917
+ for (const [name, state2] of this.states) {
2918
+ if (state2.sleeping) data[name] = state2;
2842
2919
  }
2843
2920
  try {
2844
2921
  writeFileSync6(this.statePath, `${JSON.stringify(data, null, 2)}
@@ -2849,9 +2926,9 @@ var SleepManager = class {
2849
2926
  }
2850
2927
  // --- Public API ---
2851
2928
  isSleeping(name) {
2852
- const state = this.states.get(name);
2853
- if (!state?.sleeping) return false;
2854
- if (state.wokenByTrigger) return false;
2929
+ const state2 = this.states.get(name);
2930
+ if (!state2?.sleeping) return false;
2931
+ if (state2.wokenByTrigger) return false;
2855
2932
  return true;
2856
2933
  }
2857
2934
  getState(name) {
@@ -2862,8 +2939,8 @@ var SleepManager = class {
2862
2939
  * this just clears the sleep state so onActivityEvent won't return it to sleep.
2863
2940
  */
2864
2941
  convertTriggerToFullWake(name) {
2865
- const state = this.states.get(name);
2866
- if (!state?.sleeping || !state.wokenByTrigger) return;
2942
+ const state2 = this.states.get(name);
2943
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
2867
2944
  this.markAwake(name);
2868
2945
  slog3.info(`${name} trigger-wake converted to full wake`);
2869
2946
  }
@@ -2875,11 +2952,11 @@ var SleepManager = class {
2875
2952
  * return-to-sleep in onActivityEvent, minus stopping an already-dead process.
2876
2953
  */
2877
2954
  async returnToSleepAfterCrash(name) {
2878
- const state = this.states.get(name);
2879
- if (!state?.sleeping || !state.wokenByTrigger) return;
2955
+ const state2 = this.states.get(name);
2956
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
2880
2957
  if (this.transitioning.has(name)) return;
2881
2958
  this.transitioning.add(name);
2882
- state.wokenByTrigger = false;
2959
+ state2.wokenByTrigger = false;
2883
2960
  try {
2884
2961
  try {
2885
2962
  await sleepMind(name);
@@ -2887,9 +2964,9 @@ var SleepManager = class {
2887
2964
  slog3.warn(`sleepMind during crash-return failed for ${name}`, logger_default.errorData(err));
2888
2965
  }
2889
2966
  await this.archiveSessions(name);
2890
- state.sleeping = true;
2891
- state.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
2892
- state.scheduledWakeAt = this.getNextWakeTime(this.getSleepConfig(name));
2967
+ state2.sleeping = true;
2968
+ state2.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
2969
+ state2.scheduledWakeAt = this.getNextWakeTime(this.getSleepConfig(name));
2893
2970
  this.saveState();
2894
2971
  slog3.info(`${name} returned to sleep after crashing during trigger wake`);
2895
2972
  } catch (err) {
@@ -2932,7 +3009,8 @@ var SleepManager = class {
2932
3009
  const entry = await findMind(name);
2933
3010
  if (!entry) return;
2934
3011
  const sleepConfig = this.getSleepConfig(name);
2935
- const wakeTime = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig) ?? "scheduled time";
3012
+ const wakeAt = opts?.voluntaryWakeAt ?? this.getNextWakeTime(sleepConfig);
3013
+ const wakeTime = formatWakeTime(wakeAt);
2936
3014
  const preSleepMsg = await getPrompt("pre_sleep", { wakeTime });
2937
3015
  let conversationId;
2938
3016
  try {
@@ -2973,8 +3051,8 @@ var SleepManager = class {
2973
3051
  * Wake a sleeping mind. Starts the process, delivers wake summary.
2974
3052
  */
2975
3053
  async initiateWake(name, opts) {
2976
- const state = this.states.get(name);
2977
- if (!state?.sleeping) return;
3054
+ const state2 = this.states.get(name);
3055
+ if (!state2?.sleeping) return;
2978
3056
  if (this.transitioning.has(name)) return;
2979
3057
  this.transitioning.add(name);
2980
3058
  try {
@@ -2990,14 +3068,14 @@ var SleepManager = class {
2990
3068
  const entry = await findMind(name);
2991
3069
  if (!entry) return;
2992
3070
  if (opts?.trigger) {
2993
- state.wokenByTrigger = true;
2994
- state.triggerWakeHistory.push({
3071
+ state2.wokenByTrigger = true;
3072
+ state2.triggerWakeHistory.push({
2995
3073
  channel: opts.trigger.channel,
2996
3074
  at: (/* @__PURE__ */ new Date()).toISOString()
2997
3075
  });
2998
3076
  this.saveState();
2999
3077
  } else {
3000
- const sleepingSince = state.sleepingSince ? new Date(state.sleepingSince) : /* @__PURE__ */ new Date();
3078
+ const sleepingSince = state2.sleepingSince ? new Date(state2.sleepingSince) : /* @__PURE__ */ new Date();
3001
3079
  const now = /* @__PURE__ */ new Date();
3002
3080
  const duration = formatDuration(sleepingSince, now);
3003
3081
  const currentDate = formatCurrentDate();
@@ -3005,10 +3083,10 @@ var SleepManager = class {
3005
3083
  hour: "numeric",
3006
3084
  minute: "2-digit"
3007
3085
  });
3008
- const triggerWakeSummary = this.buildTriggerWakeSummary(state);
3086
+ const triggerWakeSummary = this.buildTriggerWakeSummary(state2);
3009
3087
  const wakeContext = await this.runWakeContextScript(
3010
3088
  name,
3011
- state.sleepingSince ?? sleepingSince.toISOString(),
3089
+ state2.sleepingSince ?? sleepingSince.toISOString(),
3012
3090
  duration
3013
3091
  );
3014
3092
  const queuedSummary = await this.buildQueuedSummary(name);
@@ -3094,9 +3172,9 @@ var SleepManager = class {
3094
3172
  status: "sleep-queued",
3095
3173
  payload: JSON.stringify(payload)
3096
3174
  });
3097
- const state = this.states.get(name);
3098
- if (state) {
3099
- state.queuedMessageCount++;
3175
+ const state2 = this.states.get(name);
3176
+ if (state2) {
3177
+ state2.queuedMessageCount++;
3100
3178
  this.saveState();
3101
3179
  }
3102
3180
  }
@@ -3108,7 +3186,7 @@ var SleepManager = class {
3108
3186
  const db = await getDb();
3109
3187
  const rows = await db.select().from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3110
3188
  if (rows.length === 0) return 0;
3111
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-HRGFNWO7.js");
3189
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-IUAXRPFE.js");
3112
3190
  const delivered = [];
3113
3191
  for (const row of rows) {
3114
3192
  try {
@@ -3121,9 +3199,9 @@ var SleepManager = class {
3121
3199
  if (delivered.length > 0) {
3122
3200
  await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, delivered));
3123
3201
  }
3124
- const state = this.states.get(name);
3125
- if (state) {
3126
- state.queuedMessageCount = Math.max(0, state.queuedMessageCount - delivered.length);
3202
+ const state2 = this.states.get(name);
3203
+ if (state2) {
3204
+ state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - delivered.length);
3127
3205
  }
3128
3206
  return delivered.length;
3129
3207
  } catch (err) {
@@ -3134,7 +3212,7 @@ var SleepManager = class {
3134
3212
  // --- Internal methods ---
3135
3213
  markSleeping(name, opts) {
3136
3214
  const sleepConfig = this.getSleepConfig(name);
3137
- const state = {
3215
+ const state2 = {
3138
3216
  sleeping: true,
3139
3217
  sleepingSince: (/* @__PURE__ */ new Date()).toISOString(),
3140
3218
  scheduledWakeAt: this.getNextWakeTime(sleepConfig),
@@ -3143,7 +3221,7 @@ var SleepManager = class {
3143
3221
  queuedMessageCount: this.states.get(name)?.queuedMessageCount ?? 0,
3144
3222
  triggerWakeHistory: []
3145
3223
  };
3146
- this.states.set(name, state);
3224
+ this.states.set(name, state2);
3147
3225
  this.saveState();
3148
3226
  }
3149
3227
  markAwake(name) {
@@ -3171,9 +3249,9 @@ var SleepManager = class {
3171
3249
  }
3172
3250
  const config = this.getSleepConfig(name);
3173
3251
  if (!config?.enabled || !config.schedule) continue;
3174
- const state = this.states.get(name);
3175
- if (state?.sleeping && state.voluntaryWakeAt) {
3176
- const wakeAt = new Date(state.voluntaryWakeAt);
3252
+ const state2 = this.states.get(name);
3253
+ if (state2?.sleeping && state2.voluntaryWakeAt) {
3254
+ const wakeAt = new Date(state2.voluntaryWakeAt);
3177
3255
  if (now >= wakeAt) {
3178
3256
  this.initiateWake(name).catch(
3179
3257
  (err) => slog3.error(`failed voluntary wake for ${name}`, logger_default.errorData(err))
@@ -3181,8 +3259,8 @@ var SleepManager = class {
3181
3259
  continue;
3182
3260
  }
3183
3261
  }
3184
- if (state?.sleeping && state.scheduledWakeAt) {
3185
- const wakeAt = new Date(state.scheduledWakeAt);
3262
+ if (state2?.sleeping && state2.scheduledWakeAt) {
3263
+ const wakeAt = new Date(state2.scheduledWakeAt);
3186
3264
  if (now >= wakeAt) {
3187
3265
  this.initiateWake(name).catch(
3188
3266
  (err) => slog3.error(`failed scheduled wake for ${name}`, logger_default.errorData(err))
@@ -3190,7 +3268,7 @@ var SleepManager = class {
3190
3268
  continue;
3191
3269
  }
3192
3270
  }
3193
- if (!state?.sleeping) {
3271
+ if (!state2?.sleeping) {
3194
3272
  if (this.shouldSleep(config.schedule.sleep, epochMinute)) {
3195
3273
  this.initiateSleep(name).catch(
3196
3274
  (err) => slog3.error(`failed to initiate sleep for ${name}`, logger_default.errorData(err))
@@ -3303,8 +3381,8 @@ var SleepManager = class {
3303
3381
  return "";
3304
3382
  }
3305
3383
  }
3306
- buildTriggerWakeSummary(state) {
3307
- const history = state.triggerWakeHistory;
3384
+ buildTriggerWakeSummary(state2) {
3385
+ const history = state2.triggerWakeHistory;
3308
3386
  if (!history || history.length === 0) return "";
3309
3387
  const channels = [...new Set(history.map((h) => h.channel))];
3310
3388
  const times = history.length === 1 ? "once" : `${history.length} times`;
@@ -3390,18 +3468,18 @@ var SleepManager = class {
3390
3468
  await new Promise((r) => setTimeout(r, 1e3));
3391
3469
  }
3392
3470
  onActivityEvent(event) {
3393
- const state = this.states.get(event.mind);
3394
- if (!state?.sleeping || !state.wokenByTrigger) return;
3471
+ const state2 = this.states.get(event.mind);
3472
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
3395
3473
  if (this.transitioning.has(event.mind)) return;
3396
3474
  if (event.type === "mind_idle") {
3397
3475
  slog3.info(`${event.mind} going back to sleep after trigger wake`);
3398
- state.wokenByTrigger = false;
3476
+ state2.wokenByTrigger = false;
3399
3477
  this.transitioning.add(event.mind);
3400
3478
  sleepMind(event.mind).then(() => this.archiveSessions(event.mind)).then(() => {
3401
- state.sleeping = true;
3402
- state.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
3479
+ state2.sleeping = true;
3480
+ state2.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
3403
3481
  const sleepConfig = this.getSleepConfig(event.mind);
3404
- state.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
3482
+ state2.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
3405
3483
  this.saveState();
3406
3484
  slog3.info(`${event.mind} returned to sleep`);
3407
3485
  }).catch((err) => {
@@ -3429,7 +3507,7 @@ function getSleepManagerIfReady() {
3429
3507
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3430
3508
  import { readFile, realpath } from "fs/promises";
3431
3509
  import { extname, resolve as resolve8 } from "path";
3432
- import { and as and3, eq as eq4, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3510
+ import { and as and4, eq as eq5, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3433
3511
 
3434
3512
  // packages/daemon/src/lib/chat/typing.ts
3435
3513
  var DEFAULT_TTL_MS = 1e4;
@@ -3522,6 +3600,73 @@ function publishTypingForChannels(channels, map) {
3522
3600
  }
3523
3601
  }
3524
3602
 
3603
+ // packages/daemon/src/lib/delivery/send-gate.ts
3604
+ import { and as and3, asc as asc2, desc, eq as eq4, gt } from "drizzle-orm";
3605
+ var state = /* @__PURE__ */ new Map();
3606
+ function extractText(raw) {
3607
+ if (!raw) return "";
3608
+ try {
3609
+ const blocks = JSON.parse(raw);
3610
+ if (Array.isArray(blocks)) {
3611
+ return blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ").trim();
3612
+ }
3613
+ } catch {
3614
+ }
3615
+ return String(raw);
3616
+ }
3617
+ async function maxMessageId(conversationId) {
3618
+ const db = await getDb();
3619
+ const rows = await db.select({ id: messages.id }).from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(1);
3620
+ return rows[0]?.id ?? 0;
3621
+ }
3622
+ async function onDeliveredToMind(mindBase, conversationId) {
3623
+ if (!conversationId) return;
3624
+ let conv = state.get(mindBase);
3625
+ if (!conv) {
3626
+ conv = /* @__PURE__ */ new Map();
3627
+ state.set(mindBase, conv);
3628
+ }
3629
+ const existing = conv.get(conversationId);
3630
+ if (existing?.turnOpen) return;
3631
+ const baselineId = await maxMessageId(conversationId);
3632
+ conv.set(conversationId, { baselineId, heldThisTurn: false, turnOpen: true });
3633
+ }
3634
+ async function checkStaleSend(mindBase, conversationId, excludeSenders) {
3635
+ const entry = state.get(mindBase)?.get(conversationId);
3636
+ if (!entry?.turnOpen || entry.heldThisTurn) return { held: false };
3637
+ const db = await getDb();
3638
+ const rows = await db.select({ id: messages.id, sender: messages.sender_name, content: messages.content }).from(messages).where(and3(eq4(messages.conversation_id, conversationId), gt(messages.id, entry.baselineId))).orderBy(asc2(messages.id));
3639
+ const excluded = new Set(excludeSenders.filter(Boolean));
3640
+ const unseen = rows.filter((r) => r.sender && !excluded.has(r.sender));
3641
+ if (unseen.length === 0) return { held: false };
3642
+ entry.heldThisTurn = true;
3643
+ entry.baselineId = rows[rows.length - 1].id;
3644
+ return {
3645
+ held: true,
3646
+ unseen: unseen.map((r) => ({ sender: r.sender, text: extractText(r.content) }))
3647
+ };
3648
+ }
3649
+ function resetTurn(mindBase) {
3650
+ const conv = state.get(mindBase);
3651
+ if (!conv) return;
3652
+ for (const entry of conv.values()) {
3653
+ entry.turnOpen = false;
3654
+ entry.heldThisTurn = false;
3655
+ }
3656
+ }
3657
+ function formatHoldNotice(channelLabel, unseen) {
3658
+ const lines = unseen.map((m) => {
3659
+ const t = m.text.length > 120 ? `${m.text.slice(0, 119)}\u2026` : m.text;
3660
+ return ` ${m.sender}: ${JSON.stringify(t)}`;
3661
+ });
3662
+ const n = unseen.length;
3663
+ return [
3664
+ `\u26A0 Not sent \u2014 ${n} newer message${n === 1 ? "" : "s"} arrived on ${channelLabel} while you were composing:`,
3665
+ ...lines,
3666
+ "Your message was NOT posted. Revise it in light of these, or run the same send again to post as-is."
3667
+ ].join("\n");
3668
+ }
3669
+
3525
3670
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3526
3671
  var dlog = logger_default.child("delivery-manager");
3527
3672
  var MAX_BATCH_SIZE = 50;
@@ -3638,6 +3783,7 @@ var DeliveryManager = class {
3638
3783
  * causing isSessionBusy to return true even when no deliveries are pending.
3639
3784
  */
3640
3785
  sessionDone(baseName, session) {
3786
+ resetTurn(baseName);
3641
3787
  if (session) {
3642
3788
  this.decrementActive(baseName, session);
3643
3789
  } else {
@@ -3675,8 +3821,8 @@ var DeliveryManager = class {
3675
3821
  try {
3676
3822
  const db = await getDb();
3677
3823
  rows = await db.select().from(deliveryQueue).where(
3678
- and3(
3679
- eq4(deliveryQueue.status, "pending"),
3824
+ and4(
3825
+ eq5(deliveryQueue.status, "pending"),
3680
3826
  sql2`(${deliveryQueue.next_attempt_at} IS NULL OR ${deliveryQueue.next_attempt_at} <= datetime('now'))`
3681
3827
  )
3682
3828
  ).orderBy(deliveryQueue.id).limit(REDRIVE_BATCH_LIMIT);
@@ -3731,7 +3877,7 @@ var DeliveryManager = class {
3731
3877
  let rows;
3732
3878
  try {
3733
3879
  const db = await getDb();
3734
- rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, baseName), eq4(deliveryQueue.status, "gated")));
3880
+ rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, baseName), eq5(deliveryQueue.status, "gated")));
3735
3881
  } catch (err) {
3736
3882
  dlog.warn(`failed to read gated rows for ${baseName}`, logger_default.errorData(err));
3737
3883
  return;
@@ -3771,7 +3917,7 @@ var DeliveryManager = class {
3771
3917
  */
3772
3918
  async getPending(mindName) {
3773
3919
  const db = await getDb();
3774
- const rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, mindName), eq4(deliveryQueue.status, "gated")));
3920
+ const rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, mindName), eq5(deliveryQueue.status, "gated")));
3775
3921
  const byChannel = /* @__PURE__ */ new Map();
3776
3922
  for (const row of rows) {
3777
3923
  const ch = row.channel ?? "unknown";
@@ -3796,8 +3942,8 @@ var DeliveryManager = class {
3796
3942
  * Check if a session is currently busy (has active deliveries).
3797
3943
  */
3798
3944
  isSessionBusy(mindName, session) {
3799
- const state = this.sessionStates.get(mindName)?.get(session);
3800
- return (state?.activeCount ?? 0) > 0;
3945
+ const state2 = this.sessionStates.get(mindName)?.get(session);
3946
+ return (state2?.activeCount ?? 0) > 0;
3801
3947
  }
3802
3948
  /**
3803
3949
  * Check if any session for a mind is currently busy.
@@ -3805,8 +3951,8 @@ var DeliveryManager = class {
3805
3951
  isMindBusy(mindName) {
3806
3952
  const mindSessions = this.sessionStates.get(mindName);
3807
3953
  if (!mindSessions) return false;
3808
- for (const [, state] of mindSessions) {
3809
- if (state.activeCount > 0) return true;
3954
+ for (const [, state2] of mindSessions) {
3955
+ if (state2.activeCount > 0) return true;
3810
3956
  }
3811
3957
  return false;
3812
3958
  }
@@ -3844,10 +3990,10 @@ var DeliveryManager = class {
3844
3990
  * count was reset.
3845
3991
  */
3846
3992
  clearSessionActive(mindName, session, minIdleMs) {
3847
- const state = this.sessionStates.get(mindName)?.get(session);
3848
- if (!state) return false;
3849
- if (Date.now() - state.lastDeliveredAt < minIdleMs) return false;
3850
- state.activeCount = 0;
3993
+ const state2 = this.sessionStates.get(mindName)?.get(session);
3994
+ if (!state2) return false;
3995
+ if (Date.now() - state2.lastDeliveredAt < minIdleMs) return false;
3996
+ state2.activeCount = 0;
3851
3997
  return true;
3852
3998
  }
3853
3999
  /**
@@ -3940,7 +4086,7 @@ var DeliveryManager = class {
3940
4086
  const backoffSec = Math.round(
3941
4087
  Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
3942
4088
  );
3943
- await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql2`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq4(deliveryQueue.id, row.id));
4089
+ await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql2`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq5(deliveryQueue.id, row.id));
3944
4090
  }
3945
4091
  } catch (err) {
3946
4092
  dlog.warn("failed to schedule delivery retry", logger_default.errorData(err));
@@ -3961,6 +4107,7 @@ var DeliveryManager = class {
3961
4107
  const channels = /* @__PURE__ */ new Set();
3962
4108
  if (payload.channel) channels.add(payload.channel);
3963
4109
  this.incrementActive(baseName, session, senders, channels);
4110
+ await onDeliveredToMind(baseName, payload.conversationId);
3964
4111
  linkInboundToActiveTurn(baseName, session, payload.channel).catch(
3965
4112
  (err) => dlog.warn(`failed to link mid-turn inbound for ${baseName}`, logger_default.errorData(err))
3966
4113
  );
@@ -3976,7 +4123,6 @@ var DeliveryManager = class {
3976
4123
  const body = JSON.stringify({
3977
4124
  ...enrichedPayload,
3978
4125
  session,
3979
- interrupt: sessionConfig.interrupt,
3980
4126
  instructions: sessionConfig.instructions
3981
4127
  });
3982
4128
  try {
@@ -3998,7 +4144,7 @@ var DeliveryManager = class {
3998
4144
  }
3999
4145
  });
4000
4146
  }
4001
- async deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride) {
4147
+ async deliverBatchToMind(mindName, session, messages2, sessionConfig) {
4002
4148
  const queueIds = messages2.map((m) => m.queueId).filter((id) => typeof id === "number");
4003
4149
  await this.runSequential(`${mindName}:${session}`, async () => {
4004
4150
  const resolved = await this.resolvePort(mindName);
@@ -4035,6 +4181,13 @@ var DeliveryManager = class {
4035
4181
  if (msg.channel) channelSet.add(msg.channel);
4036
4182
  }
4037
4183
  this.incrementActive(baseName, session, senders, channelSet);
4184
+ const convIds = /* @__PURE__ */ new Set();
4185
+ for (const msg of messages2) {
4186
+ if (msg.payload.conversationId) convIds.add(msg.payload.conversationId);
4187
+ }
4188
+ for (const convId of convIds) {
4189
+ await onDeliveredToMind(baseName, convId);
4190
+ }
4038
4191
  for (const ch of channelSet) {
4039
4192
  linkInboundToActiveTurn(baseName, session, ch).catch(
4040
4193
  (err) => dlog.warn(`failed to link mid-turn inbound for ${baseName}`, logger_default.errorData(err))
@@ -4054,7 +4207,6 @@ var DeliveryManager = class {
4054
4207
  const body = JSON.stringify({
4055
4208
  session,
4056
4209
  batch: { channels },
4057
- interrupt: interruptOverride ?? sessionConfig.interrupt,
4058
4210
  instructions: sessionConfig.instructions
4059
4211
  });
4060
4212
  try {
@@ -4078,7 +4230,6 @@ var DeliveryManager = class {
4078
4230
  }
4079
4231
  async enqueueBatch(mindName, session, payload, sessionConfig) {
4080
4232
  const delivery = sessionConfig.delivery;
4081
- const baseName = await getBaseName(mindName);
4082
4233
  const queueId = await this.persistToQueue(mindName, session, payload);
4083
4234
  if (queueId != null) this.inFlight.add(queueId);
4084
4235
  const msg = {
@@ -4096,12 +4247,6 @@ var DeliveryManager = class {
4096
4247
  return;
4097
4248
  }
4098
4249
  }
4099
- const state = this.sessionStates.get(baseName)?.get(session);
4100
- if (state && state.activeCount > 0 && payload.sender && !state.lastDeliverySenders.has(payload.sender) && payload.channel && state.lastDeliveryChannels.has(payload.channel) && Date.now() - state.lastDeliveredAt < delivery.maxWait * 1e3 && Date.now() - state.lastInterruptAt > delivery.debounce * 1e3) {
4101
- state.lastInterruptAt = Date.now();
4102
- await this.flushBatch(mindName, session, [msg], true);
4103
- return;
4104
- }
4105
4250
  this.addToBatchBuffer(mindName, session, sessionConfig, msg);
4106
4251
  }
4107
4252
  addToBatchBuffer(mindName, session, sessionConfig, msg) {
@@ -4141,7 +4286,7 @@ var DeliveryManager = class {
4141
4286
  buffer.maxWaitTimer.unref();
4142
4287
  }
4143
4288
  }
4144
- async flushBatch(mindName, session, extra, interruptOverride) {
4289
+ async flushBatch(mindName, session, extra) {
4145
4290
  const bufferKey = `${mindName}:${session}`;
4146
4291
  const buffer = this.batchBuffers.get(bufferKey);
4147
4292
  const messages2 = [];
@@ -4158,14 +4303,10 @@ var DeliveryManager = class {
4158
4303
  const baseName = await getBaseName(mindName);
4159
4304
  const config = getRoutingConfig(baseName);
4160
4305
  const sessionConfig = resolveDeliveryMode(config, session);
4161
- dlog.info(
4162
- `flushing batch for ${mindName}/${session}: ${messages2.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
4163
- );
4164
- this.deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride).catch(
4165
- (err) => {
4166
- dlog.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
4167
- }
4168
- );
4306
+ dlog.info(`flushing batch for ${mindName}/${session}: ${messages2.length} messages`);
4307
+ this.deliverBatchToMind(mindName, session, messages2, sessionConfig).catch((err) => {
4308
+ dlog.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
4309
+ });
4169
4310
  }
4170
4311
  async gateMessage(mindName, session, payload) {
4171
4312
  const baseName = await getBaseName(mindName);
@@ -4173,10 +4314,10 @@ var DeliveryManager = class {
4173
4314
  try {
4174
4315
  const db = await getDb();
4175
4316
  const count = await db.select({ count: sql2`count(*)` }).from(deliveryQueue).where(
4176
- and3(
4177
- eq4(deliveryQueue.mind, baseName),
4178
- eq4(deliveryQueue.channel, payload.channel),
4179
- eq4(deliveryQueue.status, "gated")
4317
+ and4(
4318
+ eq5(deliveryQueue.mind, baseName),
4319
+ eq5(deliveryQueue.channel, payload.channel),
4320
+ eq5(deliveryQueue.status, "gated")
4180
4321
  )
4181
4322
  );
4182
4323
  if ((count[0]?.count ?? 0) <= 1) {
@@ -4192,16 +4333,17 @@ var DeliveryManager = class {
4192
4333
  const channel = payload.channel ?? "unknown";
4193
4334
  const notification = [
4194
4335
  `[New channel: ${channel}]`,
4336
+ `Someone new is reaching out \u2014 you don't have a route for this channel yet.`,
4195
4337
  `Sender: ${payload.sender ?? "unknown"}`,
4196
4338
  payload.platform ? `Platform: ${payload.platform}` : null,
4197
4339
  payload.participantCount ? `Participants: ${payload.participantCount}` : null,
4198
4340
  "",
4199
4341
  `Preview: ${preview}`,
4200
4342
  "",
4201
- `To accept this channel, add a routing rule for "${channel}" to your routes.json.`,
4202
- `Messages are being held until a route is configured.`
4343
+ `To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json.`,
4344
+ `Messages are held safely until you do \u2014 nothing is lost. If you'd rather not engage, just leave it unrouted.`
4203
4345
  ].filter((line) => line !== null).join("\n");
4204
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-54RN3K67.js");
4346
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-E65T2HSW.js");
4205
4347
  await sendSystemMessage2(mindName, notification);
4206
4348
  }
4207
4349
  /**
@@ -4237,10 +4379,10 @@ var DeliveryManager = class {
4237
4379
  async enrichWithProfiles(mindName, session, payload) {
4238
4380
  if (!payload.conversationId || !payload.channel) return payload;
4239
4381
  const mindSessions = this.sessionStates.get(mindName);
4240
- const state = mindSessions?.get(session);
4241
- if (!state) return payload;
4382
+ const state2 = mindSessions?.get(session);
4383
+ if (!state2) return payload;
4242
4384
  const channelKey = payload.channel;
4243
- if (state.seenChannelProfiles.has(channelKey)) return payload;
4385
+ if (state2.seenChannelProfiles.has(channelKey)) return payload;
4244
4386
  try {
4245
4387
  const participants = await getParticipants(payload.conversationId);
4246
4388
  const profiles = participants.map((p) => ({
@@ -4250,7 +4392,7 @@ var DeliveryManager = class {
4250
4392
  description: p.description
4251
4393
  }));
4252
4394
  const avatarBlocks = await this.loadAvatarBlocks(participants);
4253
- state.seenChannelProfiles.add(channelKey);
4395
+ state2.seenChannelProfiles.add(channelKey);
4254
4396
  const enriched = { ...payload, participantProfiles: profiles };
4255
4397
  if (avatarBlocks.length > 0) {
4256
4398
  const existing = Array.isArray(payload.content) ? payload.content : typeof payload.content === "string" ? [{ type: "text", text: payload.content }] : [];
@@ -4351,27 +4493,26 @@ var DeliveryManager = class {
4351
4493
  mindSessions = /* @__PURE__ */ new Map();
4352
4494
  this.sessionStates.set(mind, mindSessions);
4353
4495
  }
4354
- const state = mindSessions.get(session) ?? {
4496
+ const state2 = mindSessions.get(session) ?? {
4355
4497
  activeCount: 0,
4356
4498
  lastDeliveredAt: 0,
4357
4499
  lastDeliverySenders: /* @__PURE__ */ new Set(),
4358
4500
  lastDeliveryChannels: /* @__PURE__ */ new Set(),
4359
- lastInterruptAt: 0,
4360
4501
  seenChannelProfiles: /* @__PURE__ */ new Set()
4361
4502
  };
4362
- state.activeCount++;
4363
- state.lastDeliveredAt = Date.now();
4364
- if (senders) state.lastDeliverySenders = senders;
4365
- if (channels) state.lastDeliveryChannels = channels;
4366
- mindSessions.set(session, state);
4503
+ state2.activeCount++;
4504
+ state2.lastDeliveredAt = Date.now();
4505
+ if (senders) state2.lastDeliverySenders = senders;
4506
+ if (channels) state2.lastDeliveryChannels = channels;
4507
+ mindSessions.set(session, state2);
4367
4508
  }
4368
4509
  decrementActive(mind, session) {
4369
4510
  const mindSessions = this.sessionStates.get(mind);
4370
4511
  if (!mindSessions) return;
4371
- const state = mindSessions.get(session);
4372
- if (!state) return;
4373
- state.activeCount = Math.max(0, state.activeCount - 1);
4374
- if (state.activeCount === 0) {
4512
+ const state2 = mindSessions.get(session);
4513
+ if (!state2) return;
4514
+ state2.activeCount = Math.max(0, state2.activeCount - 1);
4515
+ if (state2.activeCount === 0) {
4375
4516
  const bufferKey = `${mind}:${session}`;
4376
4517
  const buffer = this.batchBuffers.get(bufferKey);
4377
4518
  if (buffer && buffer.messages.length > 0) {
@@ -4453,7 +4594,7 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4453
4594
  content: mindHistory.content,
4454
4595
  message_id: mindHistory.message_id,
4455
4596
  turn_id: mindHistory.turn_id
4456
- }).from(mindHistory).where(and4(eq5(mindHistory.id, outboundId), eq5(mindHistory.mind, mind))).limit(1);
4597
+ }).from(mindHistory).where(and5(eq6(mindHistory.id, outboundId), eq6(mindHistory.mind, mind))).limit(1);
4457
4598
  const row = rows[0];
4458
4599
  if (!row) {
4459
4600
  dlog2.warn(`outbound marker references missing record: mind=${mind} id=${outboundId}`);
@@ -4461,13 +4602,13 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4461
4602
  }
4462
4603
  const alreadyTagged = row.turn_id != null;
4463
4604
  if (!alreadyTagged) {
4464
- await db.update(mindHistory).set({ turn_id: turnId }).where(eq5(mindHistory.id, outboundId));
4605
+ await db.update(mindHistory).set({ turn_id: turnId }).where(eq6(mindHistory.id, outboundId));
4465
4606
  }
4466
4607
  if (row.message_id) {
4467
4608
  await db.update(messages).set({
4468
4609
  turn_id: turnId,
4469
4610
  ...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
4470
- }).where(eq5(messages.id, Number(row.message_id)));
4611
+ }).where(eq6(messages.id, Number(row.message_id)));
4471
4612
  }
4472
4613
  if (!alreadyTagged) {
4473
4614
  publish3(mind, {
@@ -4643,12 +4784,12 @@ async function generateSystemReply(conversationId, mindName, message) {
4643
4784
  if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
4644
4785
  }
4645
4786
  try {
4646
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-AG5SQDDI.js");
4787
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-YAHDOIU5.js");
4647
4788
  const sm = getSleepManagerIfReady2();
4648
4789
  if (sm) {
4649
- const state = sm.getState(mindName);
4650
- if (state.sleeping) {
4651
- contextParts.push(`Sleep state: sleeping since ${state.sleepingSince}`);
4790
+ const state2 = sm.getState(mindName);
4791
+ if (state2.sleeping) {
4792
+ contextParts.push(`Sleep state: sleeping since ${state2.sleepingSince}`);
4652
4793
  }
4653
4794
  }
4654
4795
  } catch (err) {
@@ -4704,6 +4845,8 @@ export {
4704
4845
  getTypingMap,
4705
4846
  isConversationId,
4706
4847
  publishTypingForChannels,
4848
+ checkStaleSend,
4849
+ formatHoldNotice,
4707
4850
  DeliveryManager,
4708
4851
  initDeliveryManager,
4709
4852
  getDeliveryManager,