volute 0.43.0 → 0.44.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 (108) hide show
  1. package/dist/{activity-events-UISXY4Y5.js → activity-events-OMCSM7XW.js} +3 -3
  2. package/dist/{ai-service-FBCCEYF5.js → ai-service-7PHSRDMO.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/{chat-SDSX5TVV.js → chat-I4EDDIQC.js} +2 -2
  7. package/dist/{chunk-AD46AYZQ.js → chunk-2BONPXAF.js} +1 -1
  8. package/dist/{chunk-PGPLCJPK.js → chunk-3ERIL673.js} +1 -1
  9. package/dist/{chunk-C6ESRQZ7.js → chunk-3GYVW2X3.js} +1 -1
  10. package/dist/{chunk-SPLPKBES.js → chunk-5AHYQHNT.js} +4 -8
  11. package/dist/{chunk-XDTKJNW5.js → chunk-5GT7URXJ.js} +1 -1
  12. package/dist/{chunk-65OLHBCL.js → chunk-7FT24JBQ.js} +8 -8
  13. package/dist/{chunk-ZO7IQJKO.js → chunk-7KWANSLX.js} +3 -3
  14. package/dist/{chunk-EFL5UOY4.js → chunk-CYI534DJ.js} +3 -3
  15. package/dist/{chunk-FZ5TH5PF.js → chunk-EQZD4YU3.js} +8 -8
  16. package/dist/{chunk-57LBQ7JX.js → chunk-FHWUQZXE.js} +1 -1
  17. package/dist/{chunk-K4EHBHPE.js → chunk-FZMEVCWC.js} +3 -3
  18. package/dist/{chunk-YAMO2U3N.js → chunk-H7IYFRAL.js} +1 -1
  19. package/dist/{chunk-M3PGGSBH.js → chunk-HVU45COX.js} +1 -1
  20. package/dist/{chunk-FSSUANZJ.js → chunk-I2Y2YHIO.js} +5 -5
  21. package/dist/{chunk-O3DXAR44.js → chunk-IEWZ7VD7.js} +2 -2
  22. package/dist/{chunk-YSV3VJQK.js → chunk-J47TIYHA.js} +1 -1
  23. package/dist/{chunk-QHG4OMZL.js → chunk-KDLYIECM.js} +37 -32
  24. package/dist/{chunk-EE7SZJON.js → chunk-KP3KWNG6.js} +2 -2
  25. package/dist/{chunk-JVK6QQIC.js → chunk-LAXUUY7O.js} +2 -2
  26. package/dist/{chunk-3WLWKOGM.js → chunk-NF75HPNR.js} +5 -5
  27. package/dist/{chunk-PF2AB7VJ.js → chunk-OMETFX5H.js} +321 -204
  28. package/dist/{chunk-ONM2AZFL.js → chunk-PNV24SEP.js} +1 -1
  29. package/dist/{chunk-ATEX3ZPG.js → chunk-Q2XBKQ2D.js} +2 -2
  30. package/dist/{chunk-GZJFC76M.js → chunk-QMJJWTNL.js} +1 -1
  31. package/dist/chunk-SQL7M4HJ.js +120 -0
  32. package/dist/{chunk-2HIVYZDB.js → chunk-UMI4UIJG.js} +1 -1
  33. package/dist/{chunk-7G2EEYXX.js → chunk-ZMIMSIIT.js} +2 -2
  34. package/dist/{chunk-L3IV23ZG.js → chunk-ZV7MFISX.js} +2 -0
  35. package/dist/cli.js +47 -22
  36. package/dist/{cloud-sync-FRYNWKCR.js → cloud-sync-6WJ7WFYD.js} +16 -16
  37. package/dist/{conversations-67WWW25C.js → conversations-WAJYZKOC.js} +4 -4
  38. package/dist/{create-7W3LV3J7.js → create-YZIV5OJC.js} +1 -1
  39. package/dist/{daemon-restart-5GVZ5V3X.js → daemon-restart-OUJVINXV.js} +7 -7
  40. package/dist/daemon.js +297 -294
  41. package/dist/{db-CZV62BOQ.js → db-JFLCJCUG.js} +2 -2
  42. package/dist/{db-IJL6B26S.js → db-LZTLA7XC.js} +1 -1
  43. package/dist/delivery-manager-TEVOHX5W.js +32 -0
  44. package/dist/{delivery-router-HXBHYHJM.js → delivery-router-GODSIS62.js} +3 -3
  45. package/dist/{down-RRNYIGCU.js → down-XCAZXWLB.js} +5 -5
  46. package/dist/echo-text-T7ZA5EYD.js +32 -0
  47. package/dist/{exec-SJK7VUTC.js → exec-4GWHAHS3.js} +3 -3
  48. package/dist/{export-7LPCIB5J.js → export-ID7YUSLZ.js} +3 -3
  49. package/dist/{extensions-76EGTMFY.js → extensions-3QNDGW5E.js} +16 -16
  50. package/dist/{files-LV7VAKPC.js → files-BQ2WB5KC.js} +3 -3
  51. package/dist/{import-XPECCRQ7.js → import-SKID37QX.js} +6 -6
  52. package/dist/{isolation-5AYEY55P.js → isolation-5MB3J73F.js} +3 -3
  53. package/dist/{login-KJSN4JDA.js → login-LM4NF4ZB.js} +2 -2
  54. package/dist/{logout-TMJGUQV2.js → logout-CTJVIZGA.js} +2 -2
  55. package/dist/message-delivery-2EA5LLMD.js +34 -0
  56. package/dist/{mind-QEWCR7FA.js → mind-MI7Z6KM2.js} +5 -5
  57. package/dist/{mind-activity-tracker-JHAT6456.js → mind-activity-tracker-BGHJE7XA.js} +4 -4
  58. package/dist/mind-service-S3SY7HAM.js +36 -0
  59. package/dist/{package-ZOI2KAYC.js → package-5B4XDTJH.js} +1 -1
  60. package/dist/{registry-7LQHTRUX.js → registry-NUJAGLQJ.js} +2 -2
  61. package/dist/{sandbox-SJH3XOPZ.js → sandbox-IOPWWILM.js} +4 -4
  62. package/dist/scheduler-CXWQ6Z4E.js +30 -0
  63. package/dist/{schema-LRUTFOER.js → schema-PVNG3PQM.js} +1 -1
  64. package/dist/{seed-KOG6KJAO.js → seed-UUPVQWFB.js} +1 -1
  65. package/dist/{seed-cmd-GJFENEJH.js → seed-cmd-7XPKDQUC.js} +2 -2
  66. package/dist/{seed-create-YXO6R4KE.js → seed-create-IZ4M5ESO.js} +3 -3
  67. package/dist/{seed-sprout-BBU64QXP.js → seed-sprout-POJXHJWV.js} +7 -7
  68. package/dist/{send-ZBZWIH5V.js → send-O6W53VBM.js} +37 -42
  69. package/dist/{service-4EVZKGSA.js → service-AQVPUANO.js} +4 -4
  70. package/dist/{service-install-SHRM6NHK.js → service-install-4QNUDKLM.js} +5 -5
  71. package/dist/{setup-354PDHUF.js → setup-5GCO3CUB.js} +7 -7
  72. package/dist/{setup-25MMJ3J4.js → setup-MLRKNP7Y.js} +3 -3
  73. package/dist/{skills-DPI3WZPQ.js → skills-775C7QFF.js} +5 -5
  74. package/dist/sleep-manager-HINI6Y46.js +34 -0
  75. package/dist/{spirit-JQQHUVWR.js → spirit-IK5OYWMV.js} +7 -7
  76. package/dist/{sprout-4WAYBUVV.js → sprout-ZY5QGOEL.js} +1 -1
  77. package/dist/{src-O4PRLMKM.js → src-JAOVXO5W.js} +1 -0
  78. package/dist/{src-TJ7RBU5H.js → src-XFY3XDD6.js} +24 -19
  79. package/dist/{src-L2Z7X5UT.js → src-YH6PQ7LJ.js} +264 -52
  80. package/dist/{status-WTU2WHXY.js → status-E5DQNCAO.js} +5 -5
  81. package/dist/system-chat-4DQZOEZI.js +34 -0
  82. package/dist/{tailscale-Q7ZJL7S7.js → tailscale-QAWVKQKU.js} +2 -2
  83. package/dist/{turn-tracker-2GMKJQTN.js → turn-tracker-RWZMY7M6.js} +5 -5
  84. package/dist/{up-7RSBEKW6.js → up-FLCDR2UB.js} +6 -6
  85. package/dist/{update-EJ7Q77ZX.js → update-7TYVH5FM.js} +5 -5
  86. package/dist/{update-check-ACYYNKTW.js → update-check-VSCSJ7KX.js} +3 -3
  87. package/dist/{version-notify-DCAIXXZB.js → version-notify-B67N35A3.js} +17 -17
  88. package/dist/web-assets/assets/{index-DTDmEfPU.js → index-Domoz5Cg.js} +8 -8
  89. package/dist/web-assets/assets/index-dJdNzAPn.css +1 -0
  90. package/dist/web-assets/ext-theme.css +5 -0
  91. package/dist/web-assets/index.html +2 -2
  92. package/package.json +1 -1
  93. package/packages/extensions/notes/dist/ui/assets/{index-BbgANhFm.js → index-D0-DgYyj.js} +1 -1
  94. package/packages/extensions/notes/dist/ui/index.html +1 -1
  95. package/packages/extensions/notes/skills/notes/SKILL.md +31 -3
  96. package/packages/extensions/pages/dist/ui/assets/{index-Dxk-DYrf.js → index-CQjEQg1r.js} +1 -1
  97. package/packages/extensions/pages/dist/ui/index.html +1 -1
  98. package/templates/_base/.init/.local/hooks/startup-context.ts +25 -0
  99. package/templates/claude/src/agent.ts +16 -2
  100. package/templates/claude/src/lib/message-channel.ts +28 -6
  101. package/dist/delivery-manager-5J2BE22P.js +0 -32
  102. package/dist/echo-text-XKBJM6RV.js +0 -32
  103. package/dist/message-delivery-WJBZFALY.js +0 -34
  104. package/dist/mind-service-ZBVUMJ5H.js +0 -36
  105. package/dist/scheduler-BR6C6B3E.js +0 -30
  106. package/dist/sleep-manager-BVU5D2AE.js +0 -34
  107. package/dist/system-chat-KMQIP7VH.js +0 -34
  108. package/dist/web-assets/assets/index-D7udpBCx.css +0 -1
@@ -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-65OLHBCL.js";
7
+ } from "./chunk-7FT24JBQ.js";
8
8
  import {
9
9
  hashSkillDir,
10
10
  importSkillFromDir,
11
11
  removeSharedSkill,
12
12
  sharedSkillsDir
13
- } from "./chunk-FSSUANZJ.js";
13
+ } from "./chunk-I2Y2YHIO.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-FZMEVCWC.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-3GYVW2X3.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-5AHYQHNT.js";
43
43
  import {
44
44
  clearMind,
45
45
  getPrompt,
46
46
  linkInboundToActiveTurn,
47
47
  publish as publish3,
48
48
  summarizeOrphanedTurns
49
- } from "./chunk-3WLWKOGM.js";
49
+ } from "./chunk-NF75HPNR.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-J47TIYHA.js";
62
62
  import {
63
63
  readGlobalConfig,
64
64
  writeGlobalConfig
65
- } from "./chunk-ONM2AZFL.js";
65
+ } from "./chunk-PNV24SEP.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,45 @@ 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");
389
+ const extensions = notices.filter((n) => n.kind === "extension");
390
+ const blocks = [];
391
+ if (failures.length > 0) {
392
+ const groups = /* @__PURE__ */ new Map();
393
+ for (const n of failures) {
394
+ const time = localHM(n.created_at);
395
+ const g = groups.get(n.reason);
396
+ if (g) {
397
+ g.count += 1;
398
+ g.detail = n.detail;
399
+ g.last = time;
400
+ } else {
401
+ groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
402
+ }
392
403
  }
404
+ const lines = [...groups.values()].map((g) => {
405
+ const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
406
+ const plural = g.count === 1 ? "turn" : "turns";
407
+ return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
408
+ });
409
+ blocks.push(`${NOTICE_HEADER}
410
+ ${lines.join("\n")}`);
393
411
  }
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")}`;
412
+ if (extensions.length > 0) {
413
+ const byExt = /* @__PURE__ */ new Map();
414
+ for (const n of extensions) {
415
+ const arr = byExt.get(n.reason);
416
+ if (arr) arr.push(n);
417
+ else byExt.set(n.reason, [n]);
418
+ }
419
+ for (const [ext, items] of byExt) {
420
+ const header = `[${ext.charAt(0).toUpperCase()}${ext.slice(1)}]`;
421
+ const lines = items.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
422
+ blocks.push(`${header}
423
+ ${lines.join("\n")}`);
424
+ }
425
+ }
426
+ return blocks.join("\n\n");
401
427
  }
402
428
  function localHM(createdAt) {
403
429
  const iso = createdAt.endsWith("Z") ? createdAt : `${createdAt.replace(" ", "T")}Z`;
@@ -905,7 +931,7 @@ var MindManager = class {
905
931
  if (this.shuttingDown || this.stopping.has(name)) return;
906
932
  mlog.error(`mind ${name} exited with code ${code}`);
907
933
  try {
908
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
934
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HINI6Y46.js");
909
935
  const sleepMgr = getSleepManagerIfReady2();
910
936
  const sleepState = sleepMgr?.getState(name);
911
937
  if (sleepState?.sleeping) {
@@ -935,15 +961,15 @@ var MindManager = class {
935
961
  (err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
936
962
  );
937
963
  try {
938
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-5J2BE22P.js");
964
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TEVOHX5W.js");
939
965
  getDeliveryManager2().clearMindSessions(name);
940
966
  } catch (err) {
941
967
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
942
968
  mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
943
969
  }
944
970
  }
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(
971
+ 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)));
972
+ import("./activity-events-OMCSM7XW.js").then(
947
973
  ({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
948
974
  ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
949
975
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
@@ -1001,7 +1027,7 @@ var MindManager = class {
1001
1027
  mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
1002
1028
  }
1003
1029
  try {
1004
- const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-5J2BE22P.js");
1030
+ const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-TEVOHX5W.js");
1005
1031
  getDeliveryManager2().clearMindSessions(name);
1006
1032
  } catch (err) {
1007
1033
  if (!(err instanceof Error && err.message.includes("not initialized"))) {
@@ -1009,7 +1035,7 @@ var MindManager = class {
1009
1035
  }
1010
1036
  }
1011
1037
  try {
1012
- const { clearEchoTextCache } = await import("./echo-text-XKBJM6RV.js");
1038
+ const { clearEchoTextCache } = await import("./echo-text-T7ZA5EYD.js");
1013
1039
  clearEchoTextCache(name);
1014
1040
  } catch (err) {
1015
1041
  mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
@@ -1462,6 +1488,24 @@ async function buildContext(manifest, dataDir, authMw) {
1462
1488
  },
1463
1489
  getSystemsConfig: () => readSystemsConfig(),
1464
1490
  announceToSystem: (text) => announceToSystem(text),
1491
+ recordNotice: async (mindName, text) => {
1492
+ try {
1493
+ const user = await getUserByUsername(mindName);
1494
+ if (user?.user_type !== "mind") return;
1495
+ await recordNotice({
1496
+ mind: mindName,
1497
+ session: MIND_LEVEL_SESSION,
1498
+ kind: "extension",
1499
+ reason: manifest.id,
1500
+ detail: text.slice(0, 500)
1501
+ });
1502
+ } catch (err) {
1503
+ logger_default.warn(
1504
+ `extension ${manifest.id}: failed to record notice for ${mindName}`,
1505
+ logger_default.errorData(err)
1506
+ );
1507
+ }
1508
+ },
1465
1509
  isIsolationEnabled,
1466
1510
  getMindUser: mindUserName,
1467
1511
  dataDir
@@ -1646,9 +1690,9 @@ function resolveSkillsDir(manifest) {
1646
1690
  }
1647
1691
  async function discoverBuiltinExtensions(disabledIds) {
1648
1692
  const builtins = [
1649
- { id: "notes", load: async () => (await import("./src-L2Z7X5UT.js")).default },
1650
- { id: "pages", load: async () => (await import("./src-TJ7RBU5H.js")).default },
1651
- { id: "plan", load: async () => (await import("./src-O4PRLMKM.js")).default }
1693
+ { id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
1694
+ { id: "pages", load: async () => (await import("./src-XFY3XDD6.js")).default },
1695
+ { id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
1652
1696
  ];
1653
1697
  const results = [];
1654
1698
  for (const { id, load } of builtins) {
@@ -1781,6 +1825,15 @@ async function loadAllExtensions(app, authMw) {
1781
1825
  }
1782
1826
  return c.json(result);
1783
1827
  });
1828
+ app.get("/api/extensions/mind-docs", (c) => {
1829
+ const result = loaded.filter(({ manifest }) => manifest.mindDoc || manifest.commands).map(({ manifest }) => ({
1830
+ id: manifest.id,
1831
+ name: manifest.name,
1832
+ mindDoc: manifest.mindDoc ?? manifest.description ?? "",
1833
+ commands: Object.keys(manifest.commands ?? {})
1834
+ }));
1835
+ return c.json(result);
1836
+ });
1784
1837
  }
1785
1838
  function getLoadedExtensions() {
1786
1839
  return loaded.map(({ manifest }) => {
@@ -1888,7 +1941,7 @@ async function installNpmExtension(pkg) {
1888
1941
  throw new Error(`Extension "${pkg}" is already installed`);
1889
1942
  }
1890
1943
  const dir = ensureExtensionsNpmDir();
1891
- const { exec: exec2 } = await import("./exec-SJK7VUTC.js");
1944
+ const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1892
1945
  try {
1893
1946
  await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
1894
1947
  } catch (err) {
@@ -1909,7 +1962,7 @@ async function uninstallNpmExtension(pkg) {
1909
1962
  packages.splice(idx, 1);
1910
1963
  writeExtensionsConfig(packages);
1911
1964
  try {
1912
- const { exec: exec2 } = await import("./exec-SJK7VUTC.js");
1965
+ const { exec: exec2 } = await import("./exec-4GWHAHS3.js");
1913
1966
  await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
1914
1967
  } catch (err) {
1915
1968
  logger_default.warn(
@@ -2466,24 +2519,24 @@ var TokenBudget = class {
2466
2519
  this.budgets.delete(mind);
2467
2520
  }
2468
2521
  recordUsage(mind, inputTokens, outputTokens) {
2469
- const state = this.budgets.get(mind);
2470
- if (!state) return;
2471
- state.tokensUsed += inputTokens + outputTokens;
2522
+ const state2 = this.budgets.get(mind);
2523
+ if (!state2) return;
2524
+ state2.tokensUsed += inputTokens + outputTokens;
2472
2525
  this.dirty.add(mind);
2473
2526
  }
2474
2527
  /** Returns current budget status. Does not mutate state — call acknowledgeWarning() after delivering a warning. */
2475
2528
  checkBudget(mind) {
2476
- const state = this.budgets.get(mind);
2477
- if (!state) return "ok";
2478
- const pct = state.tokensUsed / state.tokenLimit;
2529
+ const state2 = this.budgets.get(mind);
2530
+ if (!state2) return "ok";
2531
+ const pct = state2.tokensUsed / state2.tokenLimit;
2479
2532
  if (pct >= 1) return "exceeded";
2480
- if (pct >= 0.8 && !state.warningInjected) return "warning";
2533
+ if (pct >= 0.8 && !state2.warningInjected) return "warning";
2481
2534
  return "ok";
2482
2535
  }
2483
2536
  /** Mark warning as delivered for this period. Call after successfully injecting the warning. */
2484
2537
  acknowledgeWarning(mind) {
2485
- const state = this.budgets.get(mind);
2486
- if (state) state.warningInjected = true;
2538
+ const state2 = this.budgets.get(mind);
2539
+ if (state2) state2.warningInjected = true;
2487
2540
  }
2488
2541
  /**
2489
2542
  * Returns true exactly once per budget period when the mind first crosses its limit,
@@ -2491,50 +2544,50 @@ var TokenBudget = class {
2491
2544
  * every subsequent usage event. The flag resets when the period rolls over.
2492
2545
  */
2493
2546
  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;
2547
+ const state2 = this.budgets.get(mind);
2548
+ if (!state2) return false;
2549
+ if (state2.tokensUsed < state2.tokenLimit) return false;
2550
+ if (state2.exceededNotified) return false;
2551
+ state2.exceededNotified = true;
2499
2552
  this.dirty.add(mind);
2500
2553
  return true;
2501
2554
  }
2502
2555
  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();
2556
+ const state2 = this.budgets.get(mind);
2557
+ if (!state2) return;
2558
+ if (state2.queue.length >= MAX_QUEUE_SIZE) {
2559
+ state2.queue.shift();
2507
2560
  }
2508
- state.queue.push(message);
2561
+ state2.queue.push(message);
2509
2562
  }
2510
2563
  drain(mind) {
2511
- const state = this.budgets.get(mind);
2512
- if (!state) return [];
2513
- const messages2 = state.queue;
2514
- state.queue = [];
2564
+ const state2 = this.budgets.get(mind);
2565
+ if (!state2) return [];
2566
+ const messages2 = state2.queue;
2567
+ state2.queue = [];
2515
2568
  return messages2;
2516
2569
  }
2517
2570
  getUsage(mind) {
2518
- const state = this.budgets.get(mind);
2519
- if (!state) return null;
2571
+ const state2 = this.budgets.get(mind);
2572
+ if (!state2) return null;
2520
2573
  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)
2574
+ tokensUsed: state2.tokensUsed,
2575
+ tokenLimit: state2.tokenLimit,
2576
+ periodMinutes: state2.periodMinutes,
2577
+ periodStart: state2.periodStart,
2578
+ queueLength: state2.queue.length,
2579
+ percentUsed: Math.round(state2.tokensUsed / state2.tokenLimit * 100)
2527
2580
  };
2528
2581
  }
2529
2582
  async tick() {
2530
2583
  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;
2584
+ for (const [mind, state2] of this.budgets) {
2585
+ const elapsed = now - state2.periodStart;
2586
+ if (elapsed >= state2.periodMinutes * 6e4) {
2587
+ state2.tokensUsed = 0;
2588
+ state2.periodStart = now;
2589
+ state2.warningInjected = false;
2590
+ state2.exceededNotified = false;
2538
2591
  this.dirty.add(mind);
2539
2592
  const queued = this.drain(mind);
2540
2593
  if (queued.length > 0) {
@@ -2552,24 +2605,24 @@ var TokenBudget = class {
2552
2605
  this.dirty.clear();
2553
2606
  const writes = [];
2554
2607
  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)));
2608
+ const state2 = this.budgets.get(mind);
2609
+ if (state2) writes.push(this.saveBudgetState(mind, state2).catch(() => this.dirty.add(mind)));
2557
2610
  }
2558
2611
  await Promise.all(writes);
2559
2612
  }
2560
2613
  budgetStatePath(mind) {
2561
2614
  return resolve6(stateDir(mind), "budget.json");
2562
2615
  }
2563
- async saveBudgetState(mind, state) {
2616
+ async saveBudgetState(mind, state2) {
2564
2617
  try {
2565
2618
  const dir = stateDir(mind);
2566
2619
  await mkdir(dir, { recursive: true });
2567
2620
  const data = {
2568
- periodStart: state.periodStart,
2569
- tokensUsed: state.tokensUsed,
2570
- warningInjected: state.warningInjected,
2571
- exceededNotified: state.exceededNotified,
2572
- queue: state.queue
2621
+ periodStart: state2.periodStart,
2622
+ tokensUsed: state2.tokensUsed,
2623
+ warningInjected: state2.warningInjected,
2624
+ exceededNotified: state2.exceededNotified,
2625
+ queue: state2.queue
2573
2626
  };
2574
2627
  await writeFile2(this.budgetStatePath(mind), `${JSON.stringify(data)}
2575
2628
  `);
@@ -2615,8 +2668,8 @@ ${summary}`
2615
2668
  tlog.info(`replayed ${messages2.length} queued message(s) for ${mindName}`);
2616
2669
  } catch (err) {
2617
2670
  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);
2671
+ const state2 = this.budgets.get(mindName);
2672
+ if (state2) state2.queue.push(...messages2);
2620
2673
  }
2621
2674
  }
2622
2675
  };
@@ -2710,7 +2763,7 @@ async function wakeMind(name) {
2710
2763
  async function startSpiritFull(name) {
2711
2764
  const entry = await findMind(name);
2712
2765
  if (entry?.dir) {
2713
- const { registerMindDir } = await import("./delivery-router-HXBHYHJM.js");
2766
+ const { registerMindDir } = await import("./delivery-router-GODSIS62.js");
2714
2767
  registerMindDir(name, entry.dir);
2715
2768
  }
2716
2769
  await getMindManager().startMind(name);
@@ -2732,8 +2785,8 @@ async function stopSpiritFull(name) {
2732
2785
  }).catch((err) => logger_default.error("failed to publish spirit_stopped activity", logger_default.errorData(err)));
2733
2786
  }
2734
2787
  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");
2788
+ const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-FRZJVQCS.js");
2789
+ const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-WAJYZKOC.js");
2737
2790
  const mindUser = await getOrCreateMindUser2(mindName);
2738
2791
  const creatorUser = await getUserByUsername2(creatorUsername);
2739
2792
  if (!creatorUser) {
@@ -2826,9 +2879,9 @@ var SleepManager = class {
2826
2879
  try {
2827
2880
  if (existsSync8(this.statePath)) {
2828
2881
  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);
2882
+ for (const [name, state2] of Object.entries(data)) {
2883
+ state2.triggerWakeHistory ??= [];
2884
+ this.states.set(name, state2);
2832
2885
  }
2833
2886
  }
2834
2887
  } catch (err) {
@@ -2837,8 +2890,8 @@ var SleepManager = class {
2837
2890
  }
2838
2891
  saveState() {
2839
2892
  const data = {};
2840
- for (const [name, state] of this.states) {
2841
- if (state.sleeping) data[name] = state;
2893
+ for (const [name, state2] of this.states) {
2894
+ if (state2.sleeping) data[name] = state2;
2842
2895
  }
2843
2896
  try {
2844
2897
  writeFileSync6(this.statePath, `${JSON.stringify(data, null, 2)}
@@ -2849,9 +2902,9 @@ var SleepManager = class {
2849
2902
  }
2850
2903
  // --- Public API ---
2851
2904
  isSleeping(name) {
2852
- const state = this.states.get(name);
2853
- if (!state?.sleeping) return false;
2854
- if (state.wokenByTrigger) return false;
2905
+ const state2 = this.states.get(name);
2906
+ if (!state2?.sleeping) return false;
2907
+ if (state2.wokenByTrigger) return false;
2855
2908
  return true;
2856
2909
  }
2857
2910
  getState(name) {
@@ -2862,8 +2915,8 @@ var SleepManager = class {
2862
2915
  * this just clears the sleep state so onActivityEvent won't return it to sleep.
2863
2916
  */
2864
2917
  convertTriggerToFullWake(name) {
2865
- const state = this.states.get(name);
2866
- if (!state?.sleeping || !state.wokenByTrigger) return;
2918
+ const state2 = this.states.get(name);
2919
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
2867
2920
  this.markAwake(name);
2868
2921
  slog3.info(`${name} trigger-wake converted to full wake`);
2869
2922
  }
@@ -2875,11 +2928,11 @@ var SleepManager = class {
2875
2928
  * return-to-sleep in onActivityEvent, minus stopping an already-dead process.
2876
2929
  */
2877
2930
  async returnToSleepAfterCrash(name) {
2878
- const state = this.states.get(name);
2879
- if (!state?.sleeping || !state.wokenByTrigger) return;
2931
+ const state2 = this.states.get(name);
2932
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
2880
2933
  if (this.transitioning.has(name)) return;
2881
2934
  this.transitioning.add(name);
2882
- state.wokenByTrigger = false;
2935
+ state2.wokenByTrigger = false;
2883
2936
  try {
2884
2937
  try {
2885
2938
  await sleepMind(name);
@@ -2887,9 +2940,9 @@ var SleepManager = class {
2887
2940
  slog3.warn(`sleepMind during crash-return failed for ${name}`, logger_default.errorData(err));
2888
2941
  }
2889
2942
  await this.archiveSessions(name);
2890
- state.sleeping = true;
2891
- state.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
2892
- state.scheduledWakeAt = this.getNextWakeTime(this.getSleepConfig(name));
2943
+ state2.sleeping = true;
2944
+ state2.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
2945
+ state2.scheduledWakeAt = this.getNextWakeTime(this.getSleepConfig(name));
2893
2946
  this.saveState();
2894
2947
  slog3.info(`${name} returned to sleep after crashing during trigger wake`);
2895
2948
  } catch (err) {
@@ -2973,8 +3026,8 @@ var SleepManager = class {
2973
3026
  * Wake a sleeping mind. Starts the process, delivers wake summary.
2974
3027
  */
2975
3028
  async initiateWake(name, opts) {
2976
- const state = this.states.get(name);
2977
- if (!state?.sleeping) return;
3029
+ const state2 = this.states.get(name);
3030
+ if (!state2?.sleeping) return;
2978
3031
  if (this.transitioning.has(name)) return;
2979
3032
  this.transitioning.add(name);
2980
3033
  try {
@@ -2990,14 +3043,14 @@ var SleepManager = class {
2990
3043
  const entry = await findMind(name);
2991
3044
  if (!entry) return;
2992
3045
  if (opts?.trigger) {
2993
- state.wokenByTrigger = true;
2994
- state.triggerWakeHistory.push({
3046
+ state2.wokenByTrigger = true;
3047
+ state2.triggerWakeHistory.push({
2995
3048
  channel: opts.trigger.channel,
2996
3049
  at: (/* @__PURE__ */ new Date()).toISOString()
2997
3050
  });
2998
3051
  this.saveState();
2999
3052
  } else {
3000
- const sleepingSince = state.sleepingSince ? new Date(state.sleepingSince) : /* @__PURE__ */ new Date();
3053
+ const sleepingSince = state2.sleepingSince ? new Date(state2.sleepingSince) : /* @__PURE__ */ new Date();
3001
3054
  const now = /* @__PURE__ */ new Date();
3002
3055
  const duration = formatDuration(sleepingSince, now);
3003
3056
  const currentDate = formatCurrentDate();
@@ -3005,10 +3058,10 @@ var SleepManager = class {
3005
3058
  hour: "numeric",
3006
3059
  minute: "2-digit"
3007
3060
  });
3008
- const triggerWakeSummary = this.buildTriggerWakeSummary(state);
3061
+ const triggerWakeSummary = this.buildTriggerWakeSummary(state2);
3009
3062
  const wakeContext = await this.runWakeContextScript(
3010
3063
  name,
3011
- state.sleepingSince ?? sleepingSince.toISOString(),
3064
+ state2.sleepingSince ?? sleepingSince.toISOString(),
3012
3065
  duration
3013
3066
  );
3014
3067
  const queuedSummary = await this.buildQueuedSummary(name);
@@ -3094,9 +3147,9 @@ var SleepManager = class {
3094
3147
  status: "sleep-queued",
3095
3148
  payload: JSON.stringify(payload)
3096
3149
  });
3097
- const state = this.states.get(name);
3098
- if (state) {
3099
- state.queuedMessageCount++;
3150
+ const state2 = this.states.get(name);
3151
+ if (state2) {
3152
+ state2.queuedMessageCount++;
3100
3153
  this.saveState();
3101
3154
  }
3102
3155
  }
@@ -3108,7 +3161,7 @@ var SleepManager = class {
3108
3161
  const db = await getDb();
3109
3162
  const rows = await db.select().from(deliveryQueue).where(and2(eq3(deliveryQueue.mind, name), eq3(deliveryQueue.status, "sleep-queued"))).all();
3110
3163
  if (rows.length === 0) return 0;
3111
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-WJBZFALY.js");
3164
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-2EA5LLMD.js");
3112
3165
  const delivered = [];
3113
3166
  for (const row of rows) {
3114
3167
  try {
@@ -3121,9 +3174,9 @@ var SleepManager = class {
3121
3174
  if (delivered.length > 0) {
3122
3175
  await db.delete(deliveryQueue).where(inArray(deliveryQueue.id, delivered));
3123
3176
  }
3124
- const state = this.states.get(name);
3125
- if (state) {
3126
- state.queuedMessageCount = Math.max(0, state.queuedMessageCount - delivered.length);
3177
+ const state2 = this.states.get(name);
3178
+ if (state2) {
3179
+ state2.queuedMessageCount = Math.max(0, state2.queuedMessageCount - delivered.length);
3127
3180
  }
3128
3181
  return delivered.length;
3129
3182
  } catch (err) {
@@ -3134,7 +3187,7 @@ var SleepManager = class {
3134
3187
  // --- Internal methods ---
3135
3188
  markSleeping(name, opts) {
3136
3189
  const sleepConfig = this.getSleepConfig(name);
3137
- const state = {
3190
+ const state2 = {
3138
3191
  sleeping: true,
3139
3192
  sleepingSince: (/* @__PURE__ */ new Date()).toISOString(),
3140
3193
  scheduledWakeAt: this.getNextWakeTime(sleepConfig),
@@ -3143,7 +3196,7 @@ var SleepManager = class {
3143
3196
  queuedMessageCount: this.states.get(name)?.queuedMessageCount ?? 0,
3144
3197
  triggerWakeHistory: []
3145
3198
  };
3146
- this.states.set(name, state);
3199
+ this.states.set(name, state2);
3147
3200
  this.saveState();
3148
3201
  }
3149
3202
  markAwake(name) {
@@ -3171,9 +3224,9 @@ var SleepManager = class {
3171
3224
  }
3172
3225
  const config = this.getSleepConfig(name);
3173
3226
  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);
3227
+ const state2 = this.states.get(name);
3228
+ if (state2?.sleeping && state2.voluntaryWakeAt) {
3229
+ const wakeAt = new Date(state2.voluntaryWakeAt);
3177
3230
  if (now >= wakeAt) {
3178
3231
  this.initiateWake(name).catch(
3179
3232
  (err) => slog3.error(`failed voluntary wake for ${name}`, logger_default.errorData(err))
@@ -3181,8 +3234,8 @@ var SleepManager = class {
3181
3234
  continue;
3182
3235
  }
3183
3236
  }
3184
- if (state?.sleeping && state.scheduledWakeAt) {
3185
- const wakeAt = new Date(state.scheduledWakeAt);
3237
+ if (state2?.sleeping && state2.scheduledWakeAt) {
3238
+ const wakeAt = new Date(state2.scheduledWakeAt);
3186
3239
  if (now >= wakeAt) {
3187
3240
  this.initiateWake(name).catch(
3188
3241
  (err) => slog3.error(`failed scheduled wake for ${name}`, logger_default.errorData(err))
@@ -3190,7 +3243,7 @@ var SleepManager = class {
3190
3243
  continue;
3191
3244
  }
3192
3245
  }
3193
- if (!state?.sleeping) {
3246
+ if (!state2?.sleeping) {
3194
3247
  if (this.shouldSleep(config.schedule.sleep, epochMinute)) {
3195
3248
  this.initiateSleep(name).catch(
3196
3249
  (err) => slog3.error(`failed to initiate sleep for ${name}`, logger_default.errorData(err))
@@ -3303,8 +3356,8 @@ var SleepManager = class {
3303
3356
  return "";
3304
3357
  }
3305
3358
  }
3306
- buildTriggerWakeSummary(state) {
3307
- const history = state.triggerWakeHistory;
3359
+ buildTriggerWakeSummary(state2) {
3360
+ const history = state2.triggerWakeHistory;
3308
3361
  if (!history || history.length === 0) return "";
3309
3362
  const channels = [...new Set(history.map((h) => h.channel))];
3310
3363
  const times = history.length === 1 ? "once" : `${history.length} times`;
@@ -3390,18 +3443,18 @@ var SleepManager = class {
3390
3443
  await new Promise((r) => setTimeout(r, 1e3));
3391
3444
  }
3392
3445
  onActivityEvent(event) {
3393
- const state = this.states.get(event.mind);
3394
- if (!state?.sleeping || !state.wokenByTrigger) return;
3446
+ const state2 = this.states.get(event.mind);
3447
+ if (!state2?.sleeping || !state2.wokenByTrigger) return;
3395
3448
  if (this.transitioning.has(event.mind)) return;
3396
3449
  if (event.type === "mind_idle") {
3397
3450
  slog3.info(`${event.mind} going back to sleep after trigger wake`);
3398
- state.wokenByTrigger = false;
3451
+ state2.wokenByTrigger = false;
3399
3452
  this.transitioning.add(event.mind);
3400
3453
  sleepMind(event.mind).then(() => this.archiveSessions(event.mind)).then(() => {
3401
- state.sleeping = true;
3402
- state.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
3454
+ state2.sleeping = true;
3455
+ state2.sleepingSince = (/* @__PURE__ */ new Date()).toISOString();
3403
3456
  const sleepConfig = this.getSleepConfig(event.mind);
3404
- state.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
3457
+ state2.scheduledWakeAt = this.getNextWakeTime(sleepConfig);
3405
3458
  this.saveState();
3406
3459
  slog3.info(`${event.mind} returned to sleep`);
3407
3460
  }).catch((err) => {
@@ -3429,7 +3482,7 @@ function getSleepManagerIfReady() {
3429
3482
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3430
3483
  import { readFile, realpath } from "fs/promises";
3431
3484
  import { extname, resolve as resolve8 } from "path";
3432
- import { and as and3, eq as eq4, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3485
+ import { and as and4, eq as eq5, inArray as inArray2, sql as sql2 } from "drizzle-orm";
3433
3486
 
3434
3487
  // packages/daemon/src/lib/chat/typing.ts
3435
3488
  var DEFAULT_TTL_MS = 1e4;
@@ -3522,6 +3575,73 @@ function publishTypingForChannels(channels, map) {
3522
3575
  }
3523
3576
  }
3524
3577
 
3578
+ // packages/daemon/src/lib/delivery/send-gate.ts
3579
+ import { and as and3, asc as asc2, desc, eq as eq4, gt } from "drizzle-orm";
3580
+ var state = /* @__PURE__ */ new Map();
3581
+ function extractText(raw) {
3582
+ if (!raw) return "";
3583
+ try {
3584
+ const blocks = JSON.parse(raw);
3585
+ if (Array.isArray(blocks)) {
3586
+ return blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ").trim();
3587
+ }
3588
+ } catch {
3589
+ }
3590
+ return String(raw);
3591
+ }
3592
+ async function maxMessageId(conversationId) {
3593
+ const db = await getDb();
3594
+ const rows = await db.select({ id: messages.id }).from(messages).where(eq4(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(1);
3595
+ return rows[0]?.id ?? 0;
3596
+ }
3597
+ async function onDeliveredToMind(mindBase, conversationId) {
3598
+ if (!conversationId) return;
3599
+ let conv = state.get(mindBase);
3600
+ if (!conv) {
3601
+ conv = /* @__PURE__ */ new Map();
3602
+ state.set(mindBase, conv);
3603
+ }
3604
+ const existing = conv.get(conversationId);
3605
+ if (existing?.turnOpen) return;
3606
+ const baselineId = await maxMessageId(conversationId);
3607
+ conv.set(conversationId, { baselineId, heldThisTurn: false, turnOpen: true });
3608
+ }
3609
+ async function checkStaleSend(mindBase, conversationId, excludeSenders) {
3610
+ const entry = state.get(mindBase)?.get(conversationId);
3611
+ if (!entry?.turnOpen || entry.heldThisTurn) return { held: false };
3612
+ const db = await getDb();
3613
+ 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));
3614
+ const excluded = new Set(excludeSenders.filter(Boolean));
3615
+ const unseen = rows.filter((r) => r.sender && !excluded.has(r.sender));
3616
+ if (unseen.length === 0) return { held: false };
3617
+ entry.heldThisTurn = true;
3618
+ entry.baselineId = rows[rows.length - 1].id;
3619
+ return {
3620
+ held: true,
3621
+ unseen: unseen.map((r) => ({ sender: r.sender, text: extractText(r.content) }))
3622
+ };
3623
+ }
3624
+ function resetTurn(mindBase) {
3625
+ const conv = state.get(mindBase);
3626
+ if (!conv) return;
3627
+ for (const entry of conv.values()) {
3628
+ entry.turnOpen = false;
3629
+ entry.heldThisTurn = false;
3630
+ }
3631
+ }
3632
+ function formatHoldNotice(channelLabel, unseen) {
3633
+ const lines = unseen.map((m) => {
3634
+ const t = m.text.length > 120 ? `${m.text.slice(0, 119)}\u2026` : m.text;
3635
+ return ` ${m.sender}: ${JSON.stringify(t)}`;
3636
+ });
3637
+ const n = unseen.length;
3638
+ return [
3639
+ `\u26A0 Not sent \u2014 ${n} newer message${n === 1 ? "" : "s"} arrived on ${channelLabel} while you were composing:`,
3640
+ ...lines,
3641
+ "Your message was NOT posted. Revise it in light of these, or run the same send again to post as-is."
3642
+ ].join("\n");
3643
+ }
3644
+
3525
3645
  // packages/daemon/src/lib/delivery/delivery-manager.ts
3526
3646
  var dlog = logger_default.child("delivery-manager");
3527
3647
  var MAX_BATCH_SIZE = 50;
@@ -3638,6 +3758,7 @@ var DeliveryManager = class {
3638
3758
  * causing isSessionBusy to return true even when no deliveries are pending.
3639
3759
  */
3640
3760
  sessionDone(baseName, session) {
3761
+ resetTurn(baseName);
3641
3762
  if (session) {
3642
3763
  this.decrementActive(baseName, session);
3643
3764
  } else {
@@ -3675,8 +3796,8 @@ var DeliveryManager = class {
3675
3796
  try {
3676
3797
  const db = await getDb();
3677
3798
  rows = await db.select().from(deliveryQueue).where(
3678
- and3(
3679
- eq4(deliveryQueue.status, "pending"),
3799
+ and4(
3800
+ eq5(deliveryQueue.status, "pending"),
3680
3801
  sql2`(${deliveryQueue.next_attempt_at} IS NULL OR ${deliveryQueue.next_attempt_at} <= datetime('now'))`
3681
3802
  )
3682
3803
  ).orderBy(deliveryQueue.id).limit(REDRIVE_BATCH_LIMIT);
@@ -3731,7 +3852,7 @@ var DeliveryManager = class {
3731
3852
  let rows;
3732
3853
  try {
3733
3854
  const db = await getDb();
3734
- rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, baseName), eq4(deliveryQueue.status, "gated")));
3855
+ rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, baseName), eq5(deliveryQueue.status, "gated")));
3735
3856
  } catch (err) {
3736
3857
  dlog.warn(`failed to read gated rows for ${baseName}`, logger_default.errorData(err));
3737
3858
  return;
@@ -3771,7 +3892,7 @@ var DeliveryManager = class {
3771
3892
  */
3772
3893
  async getPending(mindName) {
3773
3894
  const db = await getDb();
3774
- const rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, mindName), eq4(deliveryQueue.status, "gated")));
3895
+ const rows = await db.select().from(deliveryQueue).where(and4(eq5(deliveryQueue.mind, mindName), eq5(deliveryQueue.status, "gated")));
3775
3896
  const byChannel = /* @__PURE__ */ new Map();
3776
3897
  for (const row of rows) {
3777
3898
  const ch = row.channel ?? "unknown";
@@ -3796,8 +3917,8 @@ var DeliveryManager = class {
3796
3917
  * Check if a session is currently busy (has active deliveries).
3797
3918
  */
3798
3919
  isSessionBusy(mindName, session) {
3799
- const state = this.sessionStates.get(mindName)?.get(session);
3800
- return (state?.activeCount ?? 0) > 0;
3920
+ const state2 = this.sessionStates.get(mindName)?.get(session);
3921
+ return (state2?.activeCount ?? 0) > 0;
3801
3922
  }
3802
3923
  /**
3803
3924
  * Check if any session for a mind is currently busy.
@@ -3805,8 +3926,8 @@ var DeliveryManager = class {
3805
3926
  isMindBusy(mindName) {
3806
3927
  const mindSessions = this.sessionStates.get(mindName);
3807
3928
  if (!mindSessions) return false;
3808
- for (const [, state] of mindSessions) {
3809
- if (state.activeCount > 0) return true;
3929
+ for (const [, state2] of mindSessions) {
3930
+ if (state2.activeCount > 0) return true;
3810
3931
  }
3811
3932
  return false;
3812
3933
  }
@@ -3844,10 +3965,10 @@ var DeliveryManager = class {
3844
3965
  * count was reset.
3845
3966
  */
3846
3967
  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;
3968
+ const state2 = this.sessionStates.get(mindName)?.get(session);
3969
+ if (!state2) return false;
3970
+ if (Date.now() - state2.lastDeliveredAt < minIdleMs) return false;
3971
+ state2.activeCount = 0;
3851
3972
  return true;
3852
3973
  }
3853
3974
  /**
@@ -3940,7 +4061,7 @@ var DeliveryManager = class {
3940
4061
  const backoffSec = Math.round(
3941
4062
  Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
3942
4063
  );
3943
- await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql2`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq4(deliveryQueue.id, row.id));
4064
+ await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql2`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq5(deliveryQueue.id, row.id));
3944
4065
  }
3945
4066
  } catch (err) {
3946
4067
  dlog.warn("failed to schedule delivery retry", logger_default.errorData(err));
@@ -3961,6 +4082,7 @@ var DeliveryManager = class {
3961
4082
  const channels = /* @__PURE__ */ new Set();
3962
4083
  if (payload.channel) channels.add(payload.channel);
3963
4084
  this.incrementActive(baseName, session, senders, channels);
4085
+ await onDeliveredToMind(baseName, payload.conversationId);
3964
4086
  linkInboundToActiveTurn(baseName, session, payload.channel).catch(
3965
4087
  (err) => dlog.warn(`failed to link mid-turn inbound for ${baseName}`, logger_default.errorData(err))
3966
4088
  );
@@ -3976,7 +4098,6 @@ var DeliveryManager = class {
3976
4098
  const body = JSON.stringify({
3977
4099
  ...enrichedPayload,
3978
4100
  session,
3979
- interrupt: sessionConfig.interrupt,
3980
4101
  instructions: sessionConfig.instructions
3981
4102
  });
3982
4103
  try {
@@ -3998,7 +4119,7 @@ var DeliveryManager = class {
3998
4119
  }
3999
4120
  });
4000
4121
  }
4001
- async deliverBatchToMind(mindName, session, messages2, sessionConfig, interruptOverride) {
4122
+ async deliverBatchToMind(mindName, session, messages2, sessionConfig) {
4002
4123
  const queueIds = messages2.map((m) => m.queueId).filter((id) => typeof id === "number");
4003
4124
  await this.runSequential(`${mindName}:${session}`, async () => {
4004
4125
  const resolved = await this.resolvePort(mindName);
@@ -4035,6 +4156,13 @@ var DeliveryManager = class {
4035
4156
  if (msg.channel) channelSet.add(msg.channel);
4036
4157
  }
4037
4158
  this.incrementActive(baseName, session, senders, channelSet);
4159
+ const convIds = /* @__PURE__ */ new Set();
4160
+ for (const msg of messages2) {
4161
+ if (msg.payload.conversationId) convIds.add(msg.payload.conversationId);
4162
+ }
4163
+ for (const convId of convIds) {
4164
+ await onDeliveredToMind(baseName, convId);
4165
+ }
4038
4166
  for (const ch of channelSet) {
4039
4167
  linkInboundToActiveTurn(baseName, session, ch).catch(
4040
4168
  (err) => dlog.warn(`failed to link mid-turn inbound for ${baseName}`, logger_default.errorData(err))
@@ -4054,7 +4182,6 @@ var DeliveryManager = class {
4054
4182
  const body = JSON.stringify({
4055
4183
  session,
4056
4184
  batch: { channels },
4057
- interrupt: interruptOverride ?? sessionConfig.interrupt,
4058
4185
  instructions: sessionConfig.instructions
4059
4186
  });
4060
4187
  try {
@@ -4078,7 +4205,6 @@ var DeliveryManager = class {
4078
4205
  }
4079
4206
  async enqueueBatch(mindName, session, payload, sessionConfig) {
4080
4207
  const delivery = sessionConfig.delivery;
4081
- const baseName = await getBaseName(mindName);
4082
4208
  const queueId = await this.persistToQueue(mindName, session, payload);
4083
4209
  if (queueId != null) this.inFlight.add(queueId);
4084
4210
  const msg = {
@@ -4096,12 +4222,6 @@ var DeliveryManager = class {
4096
4222
  return;
4097
4223
  }
4098
4224
  }
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
4225
  this.addToBatchBuffer(mindName, session, sessionConfig, msg);
4106
4226
  }
4107
4227
  addToBatchBuffer(mindName, session, sessionConfig, msg) {
@@ -4141,7 +4261,7 @@ var DeliveryManager = class {
4141
4261
  buffer.maxWaitTimer.unref();
4142
4262
  }
4143
4263
  }
4144
- async flushBatch(mindName, session, extra, interruptOverride) {
4264
+ async flushBatch(mindName, session, extra) {
4145
4265
  const bufferKey = `${mindName}:${session}`;
4146
4266
  const buffer = this.batchBuffers.get(bufferKey);
4147
4267
  const messages2 = [];
@@ -4158,14 +4278,10 @@ var DeliveryManager = class {
4158
4278
  const baseName = await getBaseName(mindName);
4159
4279
  const config = getRoutingConfig(baseName);
4160
4280
  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
- );
4281
+ dlog.info(`flushing batch for ${mindName}/${session}: ${messages2.length} messages`);
4282
+ this.deliverBatchToMind(mindName, session, messages2, sessionConfig).catch((err) => {
4283
+ dlog.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
4284
+ });
4169
4285
  }
4170
4286
  async gateMessage(mindName, session, payload) {
4171
4287
  const baseName = await getBaseName(mindName);
@@ -4173,10 +4289,10 @@ var DeliveryManager = class {
4173
4289
  try {
4174
4290
  const db = await getDb();
4175
4291
  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")
4292
+ and4(
4293
+ eq5(deliveryQueue.mind, baseName),
4294
+ eq5(deliveryQueue.channel, payload.channel),
4295
+ eq5(deliveryQueue.status, "gated")
4180
4296
  )
4181
4297
  );
4182
4298
  if ((count[0]?.count ?? 0) <= 1) {
@@ -4201,7 +4317,7 @@ var DeliveryManager = class {
4201
4317
  `To accept this channel, add a routing rule for "${channel}" to your routes.json.`,
4202
4318
  `Messages are being held until a route is configured.`
4203
4319
  ].filter((line) => line !== null).join("\n");
4204
- const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-KMQIP7VH.js");
4320
+ const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-4DQZOEZI.js");
4205
4321
  await sendSystemMessage2(mindName, notification);
4206
4322
  }
4207
4323
  /**
@@ -4237,10 +4353,10 @@ var DeliveryManager = class {
4237
4353
  async enrichWithProfiles(mindName, session, payload) {
4238
4354
  if (!payload.conversationId || !payload.channel) return payload;
4239
4355
  const mindSessions = this.sessionStates.get(mindName);
4240
- const state = mindSessions?.get(session);
4241
- if (!state) return payload;
4356
+ const state2 = mindSessions?.get(session);
4357
+ if (!state2) return payload;
4242
4358
  const channelKey = payload.channel;
4243
- if (state.seenChannelProfiles.has(channelKey)) return payload;
4359
+ if (state2.seenChannelProfiles.has(channelKey)) return payload;
4244
4360
  try {
4245
4361
  const participants = await getParticipants(payload.conversationId);
4246
4362
  const profiles = participants.map((p) => ({
@@ -4250,7 +4366,7 @@ var DeliveryManager = class {
4250
4366
  description: p.description
4251
4367
  }));
4252
4368
  const avatarBlocks = await this.loadAvatarBlocks(participants);
4253
- state.seenChannelProfiles.add(channelKey);
4369
+ state2.seenChannelProfiles.add(channelKey);
4254
4370
  const enriched = { ...payload, participantProfiles: profiles };
4255
4371
  if (avatarBlocks.length > 0) {
4256
4372
  const existing = Array.isArray(payload.content) ? payload.content : typeof payload.content === "string" ? [{ type: "text", text: payload.content }] : [];
@@ -4351,27 +4467,26 @@ var DeliveryManager = class {
4351
4467
  mindSessions = /* @__PURE__ */ new Map();
4352
4468
  this.sessionStates.set(mind, mindSessions);
4353
4469
  }
4354
- const state = mindSessions.get(session) ?? {
4470
+ const state2 = mindSessions.get(session) ?? {
4355
4471
  activeCount: 0,
4356
4472
  lastDeliveredAt: 0,
4357
4473
  lastDeliverySenders: /* @__PURE__ */ new Set(),
4358
4474
  lastDeliveryChannels: /* @__PURE__ */ new Set(),
4359
- lastInterruptAt: 0,
4360
4475
  seenChannelProfiles: /* @__PURE__ */ new Set()
4361
4476
  };
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);
4477
+ state2.activeCount++;
4478
+ state2.lastDeliveredAt = Date.now();
4479
+ if (senders) state2.lastDeliverySenders = senders;
4480
+ if (channels) state2.lastDeliveryChannels = channels;
4481
+ mindSessions.set(session, state2);
4367
4482
  }
4368
4483
  decrementActive(mind, session) {
4369
4484
  const mindSessions = this.sessionStates.get(mind);
4370
4485
  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) {
4486
+ const state2 = mindSessions.get(session);
4487
+ if (!state2) return;
4488
+ state2.activeCount = Math.max(0, state2.activeCount - 1);
4489
+ if (state2.activeCount === 0) {
4375
4490
  const bufferKey = `${mind}:${session}`;
4376
4491
  const buffer = this.batchBuffers.get(bufferKey);
4377
4492
  if (buffer && buffer.messages.length > 0) {
@@ -4453,7 +4568,7 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4453
4568
  content: mindHistory.content,
4454
4569
  message_id: mindHistory.message_id,
4455
4570
  turn_id: mindHistory.turn_id
4456
- }).from(mindHistory).where(and4(eq5(mindHistory.id, outboundId), eq5(mindHistory.mind, mind))).limit(1);
4571
+ }).from(mindHistory).where(and5(eq6(mindHistory.id, outboundId), eq6(mindHistory.mind, mind))).limit(1);
4457
4572
  const row = rows[0];
4458
4573
  if (!row) {
4459
4574
  dlog2.warn(`outbound marker references missing record: mind=${mind} id=${outboundId}`);
@@ -4461,13 +4576,13 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
4461
4576
  }
4462
4577
  const alreadyTagged = row.turn_id != null;
4463
4578
  if (!alreadyTagged) {
4464
- await db.update(mindHistory).set({ turn_id: turnId }).where(eq5(mindHistory.id, outboundId));
4579
+ await db.update(mindHistory).set({ turn_id: turnId }).where(eq6(mindHistory.id, outboundId));
4465
4580
  }
4466
4581
  if (row.message_id) {
4467
4582
  await db.update(messages).set({
4468
4583
  turn_id: turnId,
4469
4584
  ...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
4470
- }).where(eq5(messages.id, Number(row.message_id)));
4585
+ }).where(eq6(messages.id, Number(row.message_id)));
4471
4586
  }
4472
4587
  if (!alreadyTagged) {
4473
4588
  publish3(mind, {
@@ -4643,12 +4758,12 @@ async function generateSystemReply(conversationId, mindName, message) {
4643
4758
  if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
4644
4759
  }
4645
4760
  try {
4646
- const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-BVU5D2AE.js");
4761
+ const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-HINI6Y46.js");
4647
4762
  const sm = getSleepManagerIfReady2();
4648
4763
  if (sm) {
4649
- const state = sm.getState(mindName);
4650
- if (state.sleeping) {
4651
- contextParts.push(`Sleep state: sleeping since ${state.sleepingSince}`);
4764
+ const state2 = sm.getState(mindName);
4765
+ if (state2.sleeping) {
4766
+ contextParts.push(`Sleep state: sleeping since ${state2.sleepingSince}`);
4652
4767
  }
4653
4768
  }
4654
4769
  } catch (err) {
@@ -4704,6 +4819,8 @@ export {
4704
4819
  getTypingMap,
4705
4820
  isConversationId,
4706
4821
  publishTypingForChannels,
4822
+ checkStaleSend,
4823
+ formatHoldNotice,
4707
4824
  DeliveryManager,
4708
4825
  initDeliveryManager,
4709
4826
  getDeliveryManager,