volute 0.46.0 → 0.48.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.
- package/README.md +10 -10
- package/dist/{accept-N5XULCXK.js → accept-KYNC5X3U.js} +1 -1
- package/dist/{activity-events-NF6VWTJQ.js → activity-events-MIOK3IOU.js} +3 -3
- package/dist/{ai-service-UODCCE4O.js → ai-service-643TNFPH.js} +8 -4
- package/dist/{archive-WS2L2VIQ.js → archive-6Z4GFQPH.js} +3 -3
- package/dist/{auth-CCFAU4GQ.js → auth-A4VGHADI.js} +4 -4
- package/dist/avatar-image-Y45R7LW5.js +23 -0
- package/dist/{backup-TST5C2YN.js → backup-UDH4GMSW.js} +15 -15
- package/dist/{bridge-2PO3AI5D.js → bridge-3FNSWLNQ.js} +1 -1
- package/dist/channels-SGQZHNQJ.js +89 -0
- package/dist/{chat-ZIQQS25U.js → chat-X43VYHH2.js} +12 -8
- package/dist/{chunk-ZV7MFISX.js → chunk-2MJ7EOMT.js} +20 -2
- package/dist/{chunk-R77O2WMK.js → chunk-2OIYNHWO.js} +1 -1
- package/dist/{chunk-QCN24XEN.js → chunk-3YUBFTDN.js} +1 -1
- package/dist/chunk-4FCL4DVZ.js +48 -0
- package/dist/{chunk-F7H6ATM4.js → chunk-5KXCM5AW.js} +18 -2
- package/dist/chunk-7KRIHYLR.js +124 -0
- package/dist/{chunk-RBNMDAKB.js → chunk-BCG45AQB.js} +15 -1
- package/dist/{chunk-QMJCNZ6C.js → chunk-C3KLHJDB.js} +1 -1
- package/dist/{chunk-H2X54EHC.js → chunk-DGGZVZTM.js} +1 -1
- package/dist/chunk-DW4TRDOZ.js +29 -0
- package/dist/{chunk-4JOQZPCE.js → chunk-EIQDZC43.js} +1 -1
- package/dist/{chunk-TQJ6ZYVY.js → chunk-FJ2MZ5PH.js} +41 -5
- package/dist/{chunk-BJZ4PV7R.js → chunk-HNGFKPNS.js} +26 -10
- package/dist/{chunk-5ZQZDDIK.js → chunk-HPGN6XRT.js} +3 -3
- package/dist/{chunk-KGIXX4NM.js → chunk-HUNDXZBZ.js} +5 -5
- package/dist/{chunk-ZDZISKCL.js → chunk-IJPMIEVE.js} +1 -1
- package/dist/chunk-IYCV34IU.js +158 -0
- package/dist/{chunk-ZNMCCHCP.js → chunk-LTLFOSA4.js} +2 -2
- package/dist/{chunk-K6T43YBD.js → chunk-MJRXFUMS.js} +26 -6
- package/dist/{chunk-4YU7HF4L.js → chunk-MOERAQXH.js} +6 -4
- package/dist/{chunk-KQSFTPIM.js → chunk-MTRMQFCQ.js} +1 -1
- package/dist/{chunk-OV7ZQ2GH.js → chunk-ODDGGLM6.js} +3 -3
- package/dist/{chunk-A7WQZKOU.js → chunk-OQJMTHEH.js} +2 -2
- package/dist/{chunk-RRNXJOKB.js → chunk-PJVPDY2Z.js} +2 -2
- package/dist/{chunk-FXORA5JG.js → chunk-Q7HOAVFR.js} +3 -3
- package/dist/{chunk-KDLYIECM.js → chunk-R772ET2M.js} +18 -4
- package/dist/{chunk-QLWG3C2Z.js → chunk-RRZ4RQQU.js} +2 -2
- package/dist/{mind-list-4D7J4CHD.js → chunk-SHKM5RGD.js} +18 -4
- package/dist/{chunk-RXEEYDIB.js → chunk-SZJCJVXV.js} +1 -1
- package/dist/{chunk-E3DA4HYV.js → chunk-T4VS7UDW.js} +15 -15
- package/dist/{chunk-YXSUMLTC.js → chunk-USD45JKR.js} +322 -76
- package/dist/{chunk-JO5IO4FH.js → chunk-VB2QXUJQ.js} +391 -294
- package/dist/{chunk-JZ62SYWR.js → chunk-VZRA5TYO.js} +1 -1
- package/dist/{chunk-PJ45FQPT.js → chunk-WARH6GIV.js} +8 -8
- package/dist/{chunk-PFZBNKUH.js → chunk-WCFSBH33.js} +0 -1
- package/dist/{chunk-DRJ24GXK.js → chunk-WQ46E33M.js} +59 -39
- package/dist/{chunk-ALKYQULU.js → chunk-WW6C3PLV.js} +1 -1
- package/dist/{chunk-CDPBTTZO.js → chunk-XVBSFLM7.js} +2 -2
- package/dist/{chunk-6EMWJZBK.js → chunk-YKA7DCVW.js} +1 -1
- package/dist/{chunk-IHKGWTGT.js → chunk-YNBXEQAP.js} +31 -1
- package/dist/cli.js +34 -26
- package/dist/{clock-RHS2JEDL.js → clock-CNRF6F7T.js} +3 -3
- package/dist/{cloud-sync-46RS4SDA.js → cloud-sync-WHKCB4CD.js} +22 -19
- package/dist/{config-TJLYUV3U.js → config-RPSLA2HX.js} +3 -3
- package/dist/{conversations-CJOOCGNW.js → conversations-C7IKYSHD.js} +4 -4
- package/dist/{create-IOV4GCRX.js → create-GJKHABJB.js} +16 -5
- package/dist/{create-DF3KISTW.js → create-VIQVP3QB.js} +1 -1
- package/dist/{daemon-client-IYR4KCPU.js → daemon-client-ZFY5MOAL.js} +5 -3
- package/dist/{daemon-restart-VFEI57UO.js → daemon-restart-7KUHVZIN.js} +7 -7
- package/dist/daemon.js +829 -496
- package/dist/{db-MF2JNRUW.js → db-FO6LXYCW.js} +2 -2
- package/dist/{db-LZTLA7XC.js → db-IPXRJUI7.js} +1 -1
- package/dist/{delete-HPLC7ETU.js → delete-ESEP7NGQ.js} +1 -1
- package/dist/delivery-manager-HZTT5SJR.js +36 -0
- package/dist/{delivery-router-N4A7ZC5T.js → delivery-router-3QFG2ZEX.js} +3 -3
- package/dist/{down-U2CZ23M4.js → down-G6IADJXV.js} +5 -5
- package/dist/echo-text-H3SH337Q.js +36 -0
- package/dist/env-6TGPO5WG.js +18 -0
- package/dist/{env-VZ4MLFQW.js → env-C7Z55QCJ.js} +6 -6
- package/dist/{exec-G3ZRST4Z.js → exec-QIFYZG4C.js} +3 -3
- package/dist/{export-LY723I3Z.js → export-C2K3NIRD.js} +4 -4
- package/dist/{extension-SEID47SR.js → extension-J4ZQ2GHV.js} +1 -1
- package/dist/{extensions-ZELQLTSR.js → extensions-GEKF47A4.js} +24 -19
- package/dist/{files-4LN3EN7Q.js → files-LXQSQOFG.js} +4 -4
- package/dist/{import-42YNQBF4.js → import-SX3ZXHB4.js} +8 -8
- package/dist/{isolation-YMGZZ4E5.js → isolation-MCT4ZYLT.js} +3 -3
- package/dist/{join-ZJV7W6MO.js → join-34TR2GXQ.js} +1 -1
- package/dist/{list-OXIAY3W7.js → list-PMUXZNAM.js} +1 -1
- package/dist/{login-MY6ETFYB.js → login-ARRZ4ORJ.js} +3 -3
- package/dist/{login-M6OJUPQ2.js → login-CL4IT2ED.js} +14 -5
- package/dist/{logout-WQBWO77G.js → logout-HZYJCYCA.js} +3 -3
- package/dist/{logout-NCG3TMDL.js → logout-YZRMZ3O2.js} +1 -1
- package/dist/message-delivery-GPQ5SE5N.js +40 -0
- package/dist/{mind-LHKS55DI.js → mind-46AOK373.js} +23 -19
- package/dist/{mind-activity-tracker-3QRNJVR4.js → mind-activity-tracker-YVZPC4A5.js} +4 -4
- package/dist/mind-contacts-GV7XYL5D.js +64 -0
- package/dist/{mind-history-VRO5A76U.js → mind-history-TTGA5BHZ.js} +5 -5
- package/dist/mind-list-OTHV4ZO4.js +13 -0
- package/dist/{mind-profile-PEQYASQX.js → mind-profile-LUG353ZT.js} +1 -1
- package/dist/mind-service-2ARGJNKP.js +44 -0
- package/dist/{mind-sleep-74TGOQBC.js → mind-sleep-DV7KP255.js} +3 -3
- package/dist/{mind-status-UO34ASXZ.js → mind-status-BJNPGXKC.js} +23 -1
- package/dist/{mind-wake-LCLGTXA2.js → mind-wake-LVTZ55EZ.js} +1 -1
- package/dist/notices-2KU5NTRY.js +25 -0
- package/dist/{package-DOD6CPO6.js → package-RTN2PS7O.js} +5 -3
- package/dist/{read-QVAXMTPV.js → read-YSWUXYL7.js} +1 -1
- package/dist/{register-Y2H4PY5R.js → register-42VBJKTP.js} +3 -3
- package/dist/{registry-OUZBZ3Q4.js → registry-UMMDRPRM.js} +2 -2
- package/dist/{reject-EV35X64Y.js → reject-SXGCYN4A.js} +1 -1
- package/dist/{restart-FJQ662XJ.js → restart-YYEGZJDD.js} +5 -5
- package/dist/{sandbox-ZJ7QA6Z6.js → sandbox-5VQ454XP.js} +4 -4
- package/dist/scheduler-I7JZCT47.js +34 -0
- package/dist/{schema-PVNG3PQM.js → schema-VEVO7HG6.js} +3 -1
- package/dist/{seed-CNYIHPOH.js → seed-F2HXZQU4.js} +1 -1
- package/dist/{seed-check-VLTVILOV.js → seed-check-TYD7RRZY.js} +1 -1
- package/dist/{seed-cmd-AVNUNLZV.js → seed-cmd-MLCTGSX5.js} +3 -3
- package/dist/{seed-create-ANXGYSCC.js → seed-create-KQ7OILWB.js} +6 -4
- package/dist/seed-readiness-RBPGOLSZ.js +18 -0
- package/dist/{seed-sprout-NIUFTX32.js → seed-sprout-HVYCFJLA.js} +30 -31
- package/dist/{send-SPE6DVNJ.js → send-TD2X5U3K.js} +6 -6
- package/dist/{service-UOCLXDWR.js → service-CVZMQYK6.js} +4 -4
- package/dist/{service-install-4HHFDDWY.js → service-install-VCRMFWX5.js} +5 -5
- package/dist/{setup-Y2EJI3GG.js → setup-5ECWTYKG.js} +7 -3
- package/dist/{setup-QT2A25SY.js → setup-AE4CVUC4.js} +7 -7
- package/dist/{skill-FKFHIJRR.js → skill-UUM4JZRF.js} +5 -5
- package/dist/skills/dreaming/SKILL.md +5 -1
- package/dist/skills/dreaming/scripts/dream.ts +5 -1
- package/dist/skills/orientation/SKILL.md +8 -7
- package/dist/skills/tending/SKILL.md +51 -2
- package/dist/skills/volute-mind/SKILL.md +2 -0
- package/dist/{skills-AKCFA7BN.js → skills-B3SVDCEB.js} +5 -5
- package/dist/sleep-manager-Z27R3WTW.js +38 -0
- package/dist/spirit-WXS3HCLN.js +25 -0
- package/dist/{split-WBMZXBHF.js → split-SKZ4Y2D6.js} +1 -1
- package/dist/{sprout-SMX42ZBP.js → sprout-KQ43BWZJ.js} +1 -1
- package/dist/{src-OJHARHTF.js → src-WLJE73NP.js} +14 -5
- package/dist/{start-5JE3TV66.js → start-O6UKK7BG.js} +3 -3
- package/dist/{status-SWRVGXBI.js → status-F4PGREUU.js} +19 -7
- package/dist/{stop-AAMTG3TU.js → stop-UBX6X5RA.js} +5 -5
- package/dist/system-chat-63ABFF6V.js +40 -0
- package/dist/{systems-VNB5WKSS.js → systems-JJNGQHAO.js} +4 -4
- package/dist/{tailscale-3XOKOW7T.js → tailscale-MCLY5GYE.js} +2 -2
- package/dist/{template-hash-446HHH5V.js → template-hash-GSOUARW4.js} +2 -2
- package/dist/template-staleness-EMQORSGG.js +15 -0
- package/dist/{turn-tracker-FOIBNUUA.js → turn-tracker-KLPV3PAU.js} +6 -6
- package/dist/up-RVGDE6CQ.js +20 -0
- package/dist/{update-SWAYZRWJ.js → update-OXQEGGXD.js} +5 -5
- package/dist/{update-check-PQ2MZ47K.js → update-check-266R4ZSY.js} +3 -3
- package/dist/{upgrade-NWAZ7MLD.js → upgrade-K5FIQPQD.js} +1 -1
- package/dist/{version-notify-C5CH7CVP.js → version-notify-73YURTFR.js} +41 -25
- package/dist/web-assets/assets/index-WdZ60R6c.css +1 -0
- package/dist/web-assets/assets/index-taX6Ezfa.js +92 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0011_drop_turn_summary_event_id.sql +3 -0
- package/drizzle/0012_channel_gates.sql +17 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +5 -3
- package/packages/extensions/pages/dist/ui/assets/index-BuXnE-rb.css +1 -0
- package/packages/extensions/pages/dist/ui/assets/index-DtzftuPA.js +2 -0
- package/packages/extensions/pages/dist/ui/index.html +2 -2
- package/templates/_base/src/lib/startup.ts +59 -3
- package/templates/claude/src/agent.ts +37 -4
- package/templates/claude/src/lib/session-reaper.ts +32 -0
- package/templates/claude/src/server.ts +14 -3
- package/templates/pi/src/agent.ts +7 -10
- package/templates/pi/src/lib/dispatch.ts +65 -0
- package/dist/avatar-image-6KY6UU4D.js +0 -16
- package/dist/delivery-manager-JINZRG2W.js +0 -33
- package/dist/echo-text-VQPB5L2X.js +0 -33
- package/dist/message-delivery-R7JSQGYX.js +0 -37
- package/dist/mind-service-NB2JMWQZ.js +0 -39
- package/dist/scheduler-WVKII3LA.js +0 -31
- package/dist/sleep-manager-G6TXSDP7.js +0 -35
- package/dist/spirit-RPBLLUQ5.js +0 -23
- package/dist/system-chat-NDAW6YP7.js +0 -37
- package/dist/up-PS6PFS75.js +0 -18
- package/dist/volute-config-A52FHWKM.js +0 -14
- package/dist/web-assets/assets/index-B1JBQv2m.css +0 -1
- package/dist/web-assets/assets/index-CU6XWICM.js +0 -86
- package/packages/extensions/pages/dist/ui/assets/index-BcY6lnq4.css +0 -1
- package/packages/extensions/pages/dist/ui/assets/index-CQjEQg1r.js +0 -2
|
@@ -1,82 +1,92 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
loadMergedEnv
|
|
4
|
-
} from "./chunk-ZDZISKCL.js";
|
|
5
2
|
import {
|
|
6
3
|
spiritDir
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HNGFKPNS.js";
|
|
5
|
+
import {
|
|
6
|
+
clearMind,
|
|
7
|
+
linkInboundToActiveTurn,
|
|
8
|
+
publish as publish3,
|
|
9
|
+
summarizeOrphanedTurns
|
|
10
|
+
} from "./chunk-USD45JKR.js";
|
|
11
|
+
import {
|
|
12
|
+
getPrompt
|
|
13
|
+
} from "./chunk-MOERAQXH.js";
|
|
8
14
|
import {
|
|
9
15
|
hashSkillDir,
|
|
10
16
|
importSkillFromDir,
|
|
11
17
|
removeSharedSkill,
|
|
12
18
|
sharedSkillsDir
|
|
13
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HUNDXZBZ.js";
|
|
20
|
+
import {
|
|
21
|
+
getOrCreateMindUser,
|
|
22
|
+
getOrCreateSystemUser,
|
|
23
|
+
getUser,
|
|
24
|
+
getUserByUsername,
|
|
25
|
+
syncMindProfile
|
|
26
|
+
} from "./chunk-ODDGGLM6.js";
|
|
14
27
|
import {
|
|
15
28
|
addMessage,
|
|
16
29
|
createChannel,
|
|
17
30
|
createConversation,
|
|
18
31
|
findDMConversation,
|
|
19
32
|
getChannelByName,
|
|
33
|
+
getChannelSettings,
|
|
20
34
|
getParticipants,
|
|
21
35
|
joinChannel,
|
|
22
|
-
publish as publish2
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
readVoluteConfig,
|
|
26
|
-
resolveWakeTriggers,
|
|
27
|
-
writeVoluteConfig
|
|
28
|
-
} from "./chunk-PFZBNKUH.js";
|
|
36
|
+
publish as publish2,
|
|
37
|
+
updateChannelSettings
|
|
38
|
+
} from "./chunk-HPGN6XRT.js";
|
|
29
39
|
import {
|
|
30
40
|
isSandboxEnabled,
|
|
31
41
|
wrapForSandbox
|
|
32
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-VZRA5TYO.js";
|
|
43
|
+
import {
|
|
44
|
+
MIND_LEVEL_SESSION,
|
|
45
|
+
recordNotice
|
|
46
|
+
} from "./chunk-IYCV34IU.js";
|
|
33
47
|
import {
|
|
34
48
|
markIdle,
|
|
35
49
|
onMindEvent
|
|
36
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-MTRMQFCQ.js";
|
|
51
|
+
import {
|
|
52
|
+
renderAvatarBlock
|
|
53
|
+
} from "./chunk-WQ46E33M.js";
|
|
54
|
+
import {
|
|
55
|
+
readVoluteConfig,
|
|
56
|
+
resolveWakeTriggers,
|
|
57
|
+
writeVoluteConfig
|
|
58
|
+
} from "./chunk-WCFSBH33.js";
|
|
37
59
|
import {
|
|
38
60
|
extractTextContent,
|
|
39
61
|
getRoutingConfig,
|
|
40
62
|
resolveDeliveryMode,
|
|
41
63
|
resolveRoute,
|
|
42
64
|
setRoutesChangeListener
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
import {
|
|
45
|
-
clearMind,
|
|
46
|
-
linkInboundToActiveTurn,
|
|
47
|
-
publish as publish3,
|
|
48
|
-
summarizeOrphanedTurns
|
|
49
|
-
} from "./chunk-YXSUMLTC.js";
|
|
50
|
-
import {
|
|
51
|
-
getPrompt
|
|
52
|
-
} from "./chunk-4YU7HF4L.js";
|
|
65
|
+
} from "./chunk-EIQDZC43.js";
|
|
53
66
|
import {
|
|
54
67
|
chownMindDir,
|
|
55
68
|
exec,
|
|
56
69
|
isIsolationEnabled,
|
|
57
70
|
mindUserName,
|
|
58
71
|
wrapForIsolation
|
|
59
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-DGGZVZTM.js";
|
|
73
|
+
import {
|
|
74
|
+
loadMergedEnv
|
|
75
|
+
} from "./chunk-IJPMIEVE.js";
|
|
60
76
|
import {
|
|
61
77
|
aiCompleteUtility,
|
|
62
78
|
getAiConfig,
|
|
79
|
+
missingCredentialWarning,
|
|
63
80
|
resolveApiKey
|
|
64
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-YNBXEQAP.js";
|
|
65
82
|
import {
|
|
66
83
|
readGlobalConfig,
|
|
67
84
|
writeGlobalConfig
|
|
68
|
-
} from "./chunk-
|
|
69
|
-
import {
|
|
70
|
-
getOrCreateMindUser,
|
|
71
|
-
getOrCreateSystemUser,
|
|
72
|
-
getUser,
|
|
73
|
-
getUserByUsername,
|
|
74
|
-
syncMindProfile
|
|
75
|
-
} from "./chunk-OV7ZQ2GH.js";
|
|
85
|
+
} from "./chunk-5KXCM5AW.js";
|
|
76
86
|
import {
|
|
77
87
|
publish,
|
|
78
88
|
subscribe
|
|
79
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-LTLFOSA4.js";
|
|
80
90
|
import {
|
|
81
91
|
logger_default
|
|
82
92
|
} from "./chunk-T2TP6ZC6.js";
|
|
@@ -88,38 +98,39 @@ import {
|
|
|
88
98
|
isSpiritName,
|
|
89
99
|
mindDir,
|
|
90
100
|
mindTmpDir,
|
|
101
|
+
readRegistry,
|
|
91
102
|
setMindRunning,
|
|
92
103
|
stateDir,
|
|
93
104
|
voluteHome,
|
|
94
105
|
voluteSystemDir
|
|
95
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-WW6C3PLV.js";
|
|
96
107
|
import {
|
|
97
108
|
activity,
|
|
109
|
+
channelGates,
|
|
98
110
|
deliveryQueue,
|
|
99
111
|
messages,
|
|
100
112
|
mindHistory,
|
|
101
|
-
mindNotices,
|
|
102
113
|
sessions
|
|
103
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-2MJ7EOMT.js";
|
|
104
115
|
|
|
105
116
|
// packages/daemon/src/lib/delivery/message-delivery.ts
|
|
106
|
-
import { and as
|
|
117
|
+
import { and as and4, eq as eq5, inArray as inArray3 } from "drizzle-orm";
|
|
107
118
|
|
|
108
119
|
// packages/daemon/src/lib/daemon/sleep-manager.ts
|
|
109
120
|
import { execFile as execFile2, spawn as spawnChild } from "child_process";
|
|
110
121
|
import {
|
|
111
|
-
existsSync as
|
|
122
|
+
existsSync as existsSync9,
|
|
112
123
|
mkdirSync as mkdirSync5,
|
|
113
124
|
readdirSync as readdirSync2,
|
|
114
|
-
readFileSync as
|
|
125
|
+
readFileSync as readFileSync8,
|
|
115
126
|
readlinkSync,
|
|
116
127
|
renameSync,
|
|
117
128
|
writeFileSync as writeFileSync6
|
|
118
129
|
} from "fs";
|
|
119
|
-
import { resolve as
|
|
130
|
+
import { resolve as resolve8 } from "path";
|
|
120
131
|
import { promisify as promisify2 } from "util";
|
|
121
132
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
122
|
-
import { and
|
|
133
|
+
import { and, eq as eq2, inArray } from "drizzle-orm";
|
|
123
134
|
|
|
124
135
|
// packages/daemon/src/lib/daemon/mind-manager.ts
|
|
125
136
|
import { execFile, spawn } from "child_process";
|
|
@@ -344,106 +355,6 @@ function getMindToken(mindName) {
|
|
|
344
355
|
return mindToToken.get(mindName) ?? null;
|
|
345
356
|
}
|
|
346
357
|
|
|
347
|
-
// packages/daemon/src/lib/daemon/notices.ts
|
|
348
|
-
import { and, asc, eq, lte, or, sql } from "drizzle-orm";
|
|
349
|
-
var nlog = logger_default.child("notices");
|
|
350
|
-
var MIND_LEVEL_SESSION = "";
|
|
351
|
-
var MAX_NOTICES_PER_SESSION = 100;
|
|
352
|
-
async function recordNotice(input) {
|
|
353
|
-
try {
|
|
354
|
-
const db = await getDb();
|
|
355
|
-
await db.insert(mindNotices).values({
|
|
356
|
-
mind: input.mind,
|
|
357
|
-
session: input.session,
|
|
358
|
-
kind: input.kind,
|
|
359
|
-
reason: input.reason,
|
|
360
|
-
detail: input.detail,
|
|
361
|
-
raw: input.raw ?? null
|
|
362
|
-
});
|
|
363
|
-
await db.run(
|
|
364
|
-
sql`DELETE FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} AND id NOT IN (SELECT id FROM mind_notices WHERE mind = ${input.mind} AND session = ${input.session} ORDER BY id DESC LIMIT ${MAX_NOTICES_PER_SESSION})`
|
|
365
|
-
);
|
|
366
|
-
} catch (err) {
|
|
367
|
-
nlog.error(`failed to record notice for ${input.mind}:${input.session}`, logger_default.errorData(err));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
async function drainNotices(mind, session, limit = MAX_NOTICES_PER_SESSION) {
|
|
371
|
-
const db = await getDb();
|
|
372
|
-
return db.select().from(mindNotices).where(
|
|
373
|
-
and(
|
|
374
|
-
eq(mindNotices.mind, mind),
|
|
375
|
-
or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION))
|
|
376
|
-
)
|
|
377
|
-
).orderBy(asc(mindNotices.id)).limit(limit);
|
|
378
|
-
}
|
|
379
|
-
async function clearDeliveredNotices(mind, session, uptoId) {
|
|
380
|
-
try {
|
|
381
|
-
const db = await getDb();
|
|
382
|
-
await db.delete(mindNotices).where(
|
|
383
|
-
and(
|
|
384
|
-
eq(mindNotices.mind, mind),
|
|
385
|
-
or(eq(mindNotices.session, session), eq(mindNotices.session, MIND_LEVEL_SESSION)),
|
|
386
|
-
lte(mindNotices.id, uptoId)
|
|
387
|
-
)
|
|
388
|
-
);
|
|
389
|
-
} catch (err) {
|
|
390
|
-
nlog.warn(`failed to clear delivered notices for ${mind}:${session}`, logger_default.errorData(err));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
var NOTICE_HEADER = "[Notices] While you were unavailable, one or more turns failed. You're back now:";
|
|
394
|
-
function formatNotices(notices) {
|
|
395
|
-
if (notices.length === 0) return null;
|
|
396
|
-
const failures = notices.filter((n) => n.kind !== "extension" && n.kind !== "budget");
|
|
397
|
-
const budgets = notices.filter((n) => n.kind === "budget");
|
|
398
|
-
const extensions = notices.filter((n) => n.kind === "extension");
|
|
399
|
-
const blocks = [];
|
|
400
|
-
if (failures.length > 0) {
|
|
401
|
-
const groups = /* @__PURE__ */ new Map();
|
|
402
|
-
for (const n of failures) {
|
|
403
|
-
const time = localHM(n.created_at);
|
|
404
|
-
const g = groups.get(n.reason);
|
|
405
|
-
if (g) {
|
|
406
|
-
g.count += 1;
|
|
407
|
-
g.detail = n.detail;
|
|
408
|
-
g.last = time;
|
|
409
|
-
} else {
|
|
410
|
-
groups.set(n.reason, { count: 1, detail: n.detail, first: time, last: time });
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const lines = [...groups.values()].map((g) => {
|
|
414
|
-
const span = g.first === g.last ? g.first : `${g.first}\u2013${g.last}`;
|
|
415
|
-
const plural = g.count === 1 ? "turn" : "turns";
|
|
416
|
-
return `- ${g.count} ${plural} failed (${span}): ${g.detail}`;
|
|
417
|
-
});
|
|
418
|
-
blocks.push(`${NOTICE_HEADER}
|
|
419
|
-
${lines.join("\n")}`);
|
|
420
|
-
}
|
|
421
|
-
if (budgets.length > 0) {
|
|
422
|
-
const lines = budgets.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
|
|
423
|
-
blocks.push(`[Budget]
|
|
424
|
-
${lines.join("\n")}`);
|
|
425
|
-
}
|
|
426
|
-
if (extensions.length > 0) {
|
|
427
|
-
const byExt = /* @__PURE__ */ new Map();
|
|
428
|
-
for (const n of extensions) {
|
|
429
|
-
const arr = byExt.get(n.reason);
|
|
430
|
-
if (arr) arr.push(n);
|
|
431
|
-
else byExt.set(n.reason, [n]);
|
|
432
|
-
}
|
|
433
|
-
for (const [ext, items] of byExt) {
|
|
434
|
-
const header = `[${ext.charAt(0).toUpperCase()}${ext.slice(1)}]`;
|
|
435
|
-
const lines = items.map((n) => `- ${localHM(n.created_at)} ${n.detail}`);
|
|
436
|
-
blocks.push(`${header}
|
|
437
|
-
${lines.join("\n")}`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return blocks.join("\n\n");
|
|
441
|
-
}
|
|
442
|
-
function localHM(createdAt) {
|
|
443
|
-
const iso = createdAt.endsWith("Z") ? createdAt : `${createdAt.replace(" ", "T")}Z`;
|
|
444
|
-
return new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
445
|
-
}
|
|
446
|
-
|
|
447
358
|
// packages/daemon/src/lib/daemon/restart-tracker.ts
|
|
448
359
|
var DEFAULT_MAX_ATTEMPTS = 5;
|
|
449
360
|
var DEFAULT_BASE_DELAY = 3e3;
|
|
@@ -804,7 +715,7 @@ var MindManager = class {
|
|
|
804
715
|
while (recentStderr.length > 20) recentStderr.shift();
|
|
805
716
|
});
|
|
806
717
|
try {
|
|
807
|
-
await new Promise((
|
|
718
|
+
await new Promise((resolve10, reject) => {
|
|
808
719
|
let settled = false;
|
|
809
720
|
const deadline = Date.now() + 3e4;
|
|
810
721
|
const onExit = (code) => {
|
|
@@ -834,7 +745,7 @@ var MindManager = class {
|
|
|
834
745
|
const { ok } = await checkHealth(port);
|
|
835
746
|
if (settled) return;
|
|
836
747
|
if (ok) {
|
|
837
|
-
finish(() =>
|
|
748
|
+
finish(() => resolve10());
|
|
838
749
|
return;
|
|
839
750
|
}
|
|
840
751
|
if (Date.now() >= deadline) {
|
|
@@ -934,7 +845,7 @@ var MindManager = class {
|
|
|
934
845
|
if (this.shuttingDown || this.stopping.has(name)) return;
|
|
935
846
|
mlog.error(`mind ${name} exited with code ${code}`);
|
|
936
847
|
try {
|
|
937
|
-
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-
|
|
848
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
|
|
938
849
|
const sleepMgr = getSleepManagerIfReady2();
|
|
939
850
|
const sleepState = sleepMgr?.getState(name);
|
|
940
851
|
if (sleepState?.sleeping) {
|
|
@@ -964,15 +875,15 @@ var MindManager = class {
|
|
|
964
875
|
(err) => mlog.warn(`failed to clear turn state for ${name} after crash`, logger_default.errorData(err))
|
|
965
876
|
);
|
|
966
877
|
try {
|
|
967
|
-
const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-
|
|
878
|
+
const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-HZTT5SJR.js");
|
|
968
879
|
getDeliveryManager2().clearMindSessions(name);
|
|
969
880
|
} catch (err) {
|
|
970
881
|
if (!(err instanceof Error && err.message.includes("not initialized"))) {
|
|
971
882
|
mlog.warn(`failed to clear delivery state for ${name} after crash`, logger_default.errorData(err));
|
|
972
883
|
}
|
|
973
884
|
}
|
|
974
|
-
import("./mind-activity-tracker-
|
|
975
|
-
import("./activity-events-
|
|
885
|
+
import("./mind-activity-tracker-YVZPC4A5.js").then(({ markIdle: markIdle2 }) => markIdle2(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
|
|
886
|
+
import("./activity-events-MIOK3IOU.js").then(
|
|
976
887
|
({ publish: publish4 }) => publish4({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
|
|
977
888
|
).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
|
|
978
889
|
const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
|
|
@@ -1002,23 +913,23 @@ var MindManager = class {
|
|
|
1002
913
|
this.stopping.add(name);
|
|
1003
914
|
const { child } = tracked;
|
|
1004
915
|
this.minds.delete(name);
|
|
1005
|
-
await new Promise((
|
|
916
|
+
await new Promise((resolve10) => {
|
|
1006
917
|
const killTimer = setTimeout(() => {
|
|
1007
918
|
try {
|
|
1008
919
|
process.kill(-child.pid, "SIGKILL");
|
|
1009
920
|
} catch {
|
|
1010
921
|
}
|
|
1011
|
-
|
|
922
|
+
resolve10();
|
|
1012
923
|
}, 5e3);
|
|
1013
924
|
child.on("exit", () => {
|
|
1014
925
|
clearTimeout(killTimer);
|
|
1015
|
-
|
|
926
|
+
resolve10();
|
|
1016
927
|
});
|
|
1017
928
|
try {
|
|
1018
929
|
process.kill(-child.pid, "SIGTERM");
|
|
1019
930
|
} catch {
|
|
1020
931
|
clearTimeout(killTimer);
|
|
1021
|
-
|
|
932
|
+
resolve10();
|
|
1022
933
|
}
|
|
1023
934
|
});
|
|
1024
935
|
this.stopping.delete(name);
|
|
@@ -1030,7 +941,7 @@ var MindManager = class {
|
|
|
1030
941
|
mlog.warn(`failed to clear turn state for ${name} on stop`, logger_default.errorData(err));
|
|
1031
942
|
}
|
|
1032
943
|
try {
|
|
1033
|
-
const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-
|
|
944
|
+
const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-HZTT5SJR.js");
|
|
1034
945
|
getDeliveryManager2().clearMindSessions(name);
|
|
1035
946
|
} catch (err) {
|
|
1036
947
|
if (!(err instanceof Error && err.message.includes("not initialized"))) {
|
|
@@ -1038,7 +949,7 @@ var MindManager = class {
|
|
|
1038
949
|
}
|
|
1039
950
|
}
|
|
1040
951
|
try {
|
|
1041
|
-
const { clearEchoTextCache } = await import("./echo-text-
|
|
952
|
+
const { clearEchoTextCache } = await import("./echo-text-H3SH337Q.js");
|
|
1042
953
|
clearEchoTextCache(name);
|
|
1043
954
|
} catch (err) {
|
|
1044
955
|
mlog.debug(`failed to clear echo-text cache for ${name}`, logger_default.errorData(err));
|
|
@@ -1122,17 +1033,30 @@ function tryGetMindManager() {
|
|
|
1122
1033
|
return instance;
|
|
1123
1034
|
}
|
|
1124
1035
|
|
|
1036
|
+
// packages/daemon/src/lib/daemon/mind-service.ts
|
|
1037
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
1038
|
+
import { resolve as resolve7 } from "path";
|
|
1039
|
+
|
|
1125
1040
|
// packages/daemon/src/lib/chat/system-channel.ts
|
|
1126
1041
|
var SYSTEM_CHANNEL_NAME = "system";
|
|
1042
|
+
var SYSTEM_CHANNEL_DESCRIPTION = "The commons \u2014 a shared room for every mind and the spirit on this system. Think out loud, check in, riff, coordinate.";
|
|
1127
1043
|
var cachedChannelId = null;
|
|
1128
1044
|
async function ensureSystemChannel() {
|
|
1129
1045
|
if (cachedChannelId) return cachedChannelId;
|
|
1130
1046
|
const existing = await getChannelByName(SYSTEM_CHANNEL_NAME);
|
|
1131
1047
|
if (existing) {
|
|
1132
1048
|
cachedChannelId = existing.id;
|
|
1049
|
+
const settings = await getChannelSettings(SYSTEM_CHANNEL_NAME);
|
|
1050
|
+
if (settings && settings.description == null) {
|
|
1051
|
+
await updateChannelSettings(SYSTEM_CHANNEL_NAME, {
|
|
1052
|
+
description: SYSTEM_CHANNEL_DESCRIPTION
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1133
1055
|
return existing.id;
|
|
1134
1056
|
}
|
|
1135
|
-
const conv = await createChannel(SYSTEM_CHANNEL_NAME
|
|
1057
|
+
const conv = await createChannel(SYSTEM_CHANNEL_NAME, void 0, {
|
|
1058
|
+
description: SYSTEM_CHANNEL_DESCRIPTION
|
|
1059
|
+
});
|
|
1136
1060
|
cachedChannelId = conv.id;
|
|
1137
1061
|
logger_default.info("created #system channel");
|
|
1138
1062
|
return conv.id;
|
|
@@ -1145,6 +1069,25 @@ async function joinSystemChannelForMind(mindName) {
|
|
|
1145
1069
|
const user = await getOrCreateMindUser(mindName);
|
|
1146
1070
|
await joinSystemChannel(user.id);
|
|
1147
1071
|
}
|
|
1072
|
+
async function joinSystemChannelForSpirit() {
|
|
1073
|
+
const user = await getOrCreateSystemUser();
|
|
1074
|
+
await joinSystemChannel(user.id);
|
|
1075
|
+
}
|
|
1076
|
+
async function backfillSystemChannelMembers() {
|
|
1077
|
+
const entries = await readRegistry();
|
|
1078
|
+
for (const entry of entries) {
|
|
1079
|
+
if (entry.stage === "seed") continue;
|
|
1080
|
+
try {
|
|
1081
|
+
if (entry.mindType === "spirit") {
|
|
1082
|
+
await joinSystemChannelForSpirit();
|
|
1083
|
+
} else {
|
|
1084
|
+
await joinSystemChannelForMind(entry.name);
|
|
1085
|
+
}
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
logger_default.error(`failed to backfill ${entry.name} into #system`, logger_default.errorData(err));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1148
1091
|
async function announceToSystem(text) {
|
|
1149
1092
|
const channelId = await ensureSystemChannel();
|
|
1150
1093
|
const systemUser = await getOrCreateSystemUser();
|
|
@@ -1174,7 +1117,7 @@ import { dirname, resolve as resolve4 } from "path";
|
|
|
1174
1117
|
|
|
1175
1118
|
// packages/daemon/src/web/middleware/auth.ts
|
|
1176
1119
|
import { timingSafeEqual } from "crypto";
|
|
1177
|
-
import { eq
|
|
1120
|
+
import { eq, lt } from "drizzle-orm";
|
|
1178
1121
|
import { getCookie } from "hono/cookie";
|
|
1179
1122
|
import { createMiddleware } from "hono/factory";
|
|
1180
1123
|
var MIND_USER_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -1202,14 +1145,14 @@ async function createSession(userId) {
|
|
|
1202
1145
|
async function deleteSession(sessionId) {
|
|
1203
1146
|
sessionCache.delete(sessionId);
|
|
1204
1147
|
const db = await getDb();
|
|
1205
|
-
await db.delete(sessions).where(
|
|
1148
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
1206
1149
|
}
|
|
1207
1150
|
async function getSessionUserId(sessionId) {
|
|
1208
1151
|
const db = await getDb();
|
|
1209
|
-
const row = await db.select().from(sessions).where(
|
|
1152
|
+
const row = await db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
|
|
1210
1153
|
if (!row) return void 0;
|
|
1211
1154
|
if (Date.now() - row.createdAt > SESSION_MAX_AGE) {
|
|
1212
|
-
await db.delete(sessions).where(
|
|
1155
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
1213
1156
|
return void 0;
|
|
1214
1157
|
}
|
|
1215
1158
|
return row.userId;
|
|
@@ -1362,10 +1305,54 @@ function deleteSystemsConfig() {
|
|
|
1362
1305
|
}
|
|
1363
1306
|
}
|
|
1364
1307
|
|
|
1308
|
+
// packages/daemon/src/lib/util/sanitize-svg.ts
|
|
1309
|
+
import DOMPurify from "isomorphic-dompurify";
|
|
1310
|
+
var ALLOWED_TAGS = ["svg", "path", "circle", "rect", "line", "polyline", "polygon", "g"];
|
|
1311
|
+
var ALLOWED_ATTR = [
|
|
1312
|
+
"viewBox",
|
|
1313
|
+
"fill",
|
|
1314
|
+
"fill-opacity",
|
|
1315
|
+
"fill-rule",
|
|
1316
|
+
"stroke",
|
|
1317
|
+
"stroke-width",
|
|
1318
|
+
"stroke-linecap",
|
|
1319
|
+
"stroke-linejoin",
|
|
1320
|
+
"stroke-dasharray",
|
|
1321
|
+
"stroke-opacity",
|
|
1322
|
+
"opacity",
|
|
1323
|
+
"d",
|
|
1324
|
+
"cx",
|
|
1325
|
+
"cy",
|
|
1326
|
+
"r",
|
|
1327
|
+
"x",
|
|
1328
|
+
"y",
|
|
1329
|
+
"width",
|
|
1330
|
+
"height",
|
|
1331
|
+
"points",
|
|
1332
|
+
"transform"
|
|
1333
|
+
];
|
|
1334
|
+
function sanitizeSvgIcon(html) {
|
|
1335
|
+
return DOMPurify.sanitize(html, {
|
|
1336
|
+
ALLOWED_TAGS,
|
|
1337
|
+
ALLOWED_ATTR,
|
|
1338
|
+
FORBID_TAGS: ["script", "foreignObject"],
|
|
1339
|
+
FORBID_ATTR: ["href", "xlink:href"]
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1365
1343
|
// packages/daemon/src/lib/extensions.ts
|
|
1366
1344
|
var VALID_EXTENSION_ID = /^[a-z0-9][a-z0-9_-]*$/;
|
|
1367
1345
|
var loaded = [];
|
|
1368
1346
|
var discovered = [];
|
|
1347
|
+
function enrichActivityMetadata(manifest, metadata) {
|
|
1348
|
+
const enriched = {
|
|
1349
|
+
...metadata,
|
|
1350
|
+
...manifest.icon && !metadata?.icon ? { icon: manifest.icon } : {},
|
|
1351
|
+
...manifest.color && !metadata?.color ? { color: manifest.color } : {}
|
|
1352
|
+
};
|
|
1353
|
+
if (typeof enriched.icon === "string") enriched.icon = sanitizeSvgIcon(enriched.icon);
|
|
1354
|
+
return enriched;
|
|
1355
|
+
}
|
|
1369
1356
|
function toCommandInfo(cmd) {
|
|
1370
1357
|
const { handler: _, ...info } = cmd;
|
|
1371
1358
|
return info;
|
|
@@ -1469,14 +1456,8 @@ async function buildContext(manifest, dataDir, authMw) {
|
|
|
1469
1456
|
getUser: async (id) => getUser(id),
|
|
1470
1457
|
getUserByUsername: async (username) => getUserByUsername(username),
|
|
1471
1458
|
publishActivity: (event) => {
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
metadata: {
|
|
1475
|
-
...event.metadata,
|
|
1476
|
-
...manifest.icon && !event.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1477
|
-
...manifest.color && !event.metadata?.color ? { color: manifest.color } : {}
|
|
1478
|
-
}
|
|
1479
|
-
};
|
|
1459
|
+
const metadata = enrichActivityMetadata(manifest, event.metadata);
|
|
1460
|
+
const enriched = { ...event, metadata };
|
|
1480
1461
|
publish(enriched).catch(
|
|
1481
1462
|
(err) => logger_default.error(`extension ${manifest.id}: failed to publish activity`, logger_default.errorData(err))
|
|
1482
1463
|
);
|
|
@@ -1554,14 +1535,8 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1554
1535
|
const result = await cmd.handler(parsed, {
|
|
1555
1536
|
...context,
|
|
1556
1537
|
publishActivity: (rawEvent) => {
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
metadata: {
|
|
1560
|
-
...rawEvent.metadata,
|
|
1561
|
-
...manifest.icon && !rawEvent.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1562
|
-
...manifest.color && !rawEvent.metadata?.color ? { color: manifest.color } : {}
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1538
|
+
const metadata = enrichActivityMetadata(manifest, rawEvent.metadata);
|
|
1539
|
+
const event = { ...rawEvent, metadata };
|
|
1565
1540
|
activityPromises.push(
|
|
1566
1541
|
publish(event).catch((err) => {
|
|
1567
1542
|
logger_default.error(
|
|
@@ -1601,7 +1576,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1601
1576
|
}
|
|
1602
1577
|
if (resolvedAssetsDir && existsSync6(resolvedAssetsDir)) {
|
|
1603
1578
|
const assetsDir = resolvedAssetsDir;
|
|
1604
|
-
const { readFile
|
|
1579
|
+
const { readFile, stat: fsStat } = await import("fs/promises");
|
|
1605
1580
|
const { extname: ext } = await import("path");
|
|
1606
1581
|
const mimeTypes = {
|
|
1607
1582
|
".html": "text/html",
|
|
@@ -1626,11 +1601,11 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1626
1601
|
const s = await fsStat(filePath).catch(() => null);
|
|
1627
1602
|
if (s?.isFile()) {
|
|
1628
1603
|
const mime = mimeTypes[ext(filePath)] || "application/octet-stream";
|
|
1629
|
-
const body = await
|
|
1604
|
+
const body = await readFile(filePath);
|
|
1630
1605
|
return c.body(body, 200, { "Content-Type": mime });
|
|
1631
1606
|
}
|
|
1632
1607
|
if (existsSync6(indexPath)) {
|
|
1633
|
-
const body = await
|
|
1608
|
+
const body = await readFile(indexPath, "utf-8");
|
|
1634
1609
|
return c.html(body);
|
|
1635
1610
|
}
|
|
1636
1611
|
return c.text("Not found", 404);
|
|
@@ -1698,7 +1673,7 @@ function resolveSkillsDir(manifest) {
|
|
|
1698
1673
|
async function discoverBuiltinExtensions(disabledIds) {
|
|
1699
1674
|
const builtins = [
|
|
1700
1675
|
{ id: "notes", load: async () => (await import("./src-YH6PQ7LJ.js")).default },
|
|
1701
|
-
{ id: "pages", load: async () => (await import("./src-
|
|
1676
|
+
{ id: "pages", load: async () => (await import("./src-WLJE73NP.js")).default },
|
|
1702
1677
|
{ id: "plan", load: async () => (await import("./src-JAOVXO5W.js")).default }
|
|
1703
1678
|
];
|
|
1704
1679
|
const results = [];
|
|
@@ -1948,7 +1923,7 @@ async function installNpmExtension(pkg) {
|
|
|
1948
1923
|
throw new Error(`Extension "${pkg}" is already installed`);
|
|
1949
1924
|
}
|
|
1950
1925
|
const dir = ensureExtensionsNpmDir();
|
|
1951
|
-
const { exec: exec2 } = await import("./exec-
|
|
1926
|
+
const { exec: exec2 } = await import("./exec-QIFYZG4C.js");
|
|
1952
1927
|
try {
|
|
1953
1928
|
await exec2("npm", ["install", "--ignore-scripts", pkg], { cwd: dir });
|
|
1954
1929
|
} catch (err) {
|
|
@@ -1969,7 +1944,7 @@ async function uninstallNpmExtension(pkg) {
|
|
|
1969
1944
|
packages.splice(idx, 1);
|
|
1970
1945
|
writeExtensionsConfig(packages);
|
|
1971
1946
|
try {
|
|
1972
|
-
const { exec: exec2 } = await import("./exec-
|
|
1947
|
+
const { exec: exec2 } = await import("./exec-QIFYZG4C.js");
|
|
1973
1948
|
await exec2("npm", ["uninstall", "--ignore-scripts", pkg], { cwd: extensionsNpmDir() });
|
|
1974
1949
|
} catch (err) {
|
|
1975
1950
|
logger_default.warn(
|
|
@@ -2431,10 +2406,17 @@ var Scheduler = class {
|
|
|
2431
2406
|
${stderr}` : ""}`;
|
|
2432
2407
|
slog2.warn(`script "${schedule.id}" failed for ${mindName}`, logger_default.errorData(err));
|
|
2433
2408
|
}
|
|
2409
|
+
} else if (Array.isArray(schedule.messages) && schedule.messages.length > 0) {
|
|
2410
|
+
const pick = schedule.messages[Math.floor(Math.random() * schedule.messages.length)];
|
|
2411
|
+
if (typeof pick !== "string" || !pick.trim()) {
|
|
2412
|
+
slog2.warn(`schedule "${schedule.id}" for ${mindName} has a malformed messages pool`);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
text = pick;
|
|
2434
2416
|
} else if (schedule.message) {
|
|
2435
2417
|
text = schedule.message;
|
|
2436
2418
|
} else {
|
|
2437
|
-
slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message or script`);
|
|
2419
|
+
slog2.warn(`schedule "${schedule.id}" for ${mindName} has no message, messages, or script`);
|
|
2438
2420
|
return;
|
|
2439
2421
|
}
|
|
2440
2422
|
await this.deliverSystem(mindName, `[${schedule.id}] ${text}`, {
|
|
@@ -2754,6 +2736,9 @@ async function startMindFull(name) {
|
|
|
2754
2736
|
summary: `${name} started`
|
|
2755
2737
|
}).catch((err) => logger_default.error("failed to publish mind_started activity", logger_default.errorData(err)));
|
|
2756
2738
|
if (entry?.parent) return;
|
|
2739
|
+
recordMissingCredentialsNotice(baseName, entry?.template).catch(
|
|
2740
|
+
(err) => logger_default.error(`failed to check credentials for ${baseName}`, logger_default.errorData(err))
|
|
2741
|
+
);
|
|
2757
2742
|
if (!entry || entry.stage === "seed") {
|
|
2758
2743
|
if (entry?.stage === "seed") {
|
|
2759
2744
|
sendSeedOrientation(baseName, entry.createdBy).catch(
|
|
@@ -2798,6 +2783,31 @@ async function startMindFull(name) {
|
|
|
2798
2783
|
}
|
|
2799
2784
|
notifyExtensionsMindStart(baseName);
|
|
2800
2785
|
}
|
|
2786
|
+
async function recordMissingCredentialsNotice(mind, template) {
|
|
2787
|
+
const { hasUndeliveredNotice, recordNotice: recordNotice2, MIND_LEVEL_SESSION: MIND_LEVEL_SESSION2 } = await import("./notices-2KU5NTRY.js");
|
|
2788
|
+
const warning = await missingCredentialWarning(template, readMindModel(mind, template), mind);
|
|
2789
|
+
if (!warning) return;
|
|
2790
|
+
if (await hasUndeliveredNotice(mind, "no_credentials")) return;
|
|
2791
|
+
await recordNotice2({
|
|
2792
|
+
mind,
|
|
2793
|
+
session: MIND_LEVEL_SESSION2,
|
|
2794
|
+
kind: "startup",
|
|
2795
|
+
reason: "no_credentials",
|
|
2796
|
+
detail: warning
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
function readMindModel(mind, template) {
|
|
2800
|
+
if (template !== "pi") return void 0;
|
|
2801
|
+
try {
|
|
2802
|
+
const configPath2 = resolve7(mindDir(mind), "home/.config/config.json");
|
|
2803
|
+
if (!existsSync8(configPath2)) return void 0;
|
|
2804
|
+
const model = JSON.parse(readFileSync7(configPath2, "utf-8")).model;
|
|
2805
|
+
return typeof model === "string" ? model : void 0;
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
logger_default.warn(`failed to read model for ${mind}`, logger_default.errorData(err));
|
|
2808
|
+
return void 0;
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2801
2811
|
async function sleepMind(name) {
|
|
2802
2812
|
markIdle(name);
|
|
2803
2813
|
await getMindManager().stopMind(name);
|
|
@@ -2818,10 +2828,13 @@ async function wakeMind(name) {
|
|
|
2818
2828
|
async function startSpiritFull(name) {
|
|
2819
2829
|
const entry = await findMind(name);
|
|
2820
2830
|
if (entry?.dir) {
|
|
2821
|
-
const { registerMindDir } = await import("./delivery-router-
|
|
2831
|
+
const { registerMindDir } = await import("./delivery-router-3QFG2ZEX.js");
|
|
2822
2832
|
registerMindDir(name, entry.dir);
|
|
2823
2833
|
}
|
|
2824
2834
|
await getMindManager().startMind(name);
|
|
2835
|
+
joinSystemChannelForSpirit().catch(
|
|
2836
|
+
(err) => logger_default.error(`failed to join #system for ${name}`, logger_default.errorData(err))
|
|
2837
|
+
);
|
|
2825
2838
|
getScheduler().loadSchedules(name, entry?.dir ?? spiritDir());
|
|
2826
2839
|
publish({
|
|
2827
2840
|
type: "mind_started",
|
|
@@ -2848,14 +2861,14 @@ function buildSeedOrientation(createdBy, creatorType) {
|
|
|
2848
2861
|
async function sendSeedOrientation(mindName, createdBy) {
|
|
2849
2862
|
let creatorType;
|
|
2850
2863
|
if (createdBy) {
|
|
2851
|
-
const { getUserByUsername: getUserByUsername2 } = await import("./auth-
|
|
2864
|
+
const { getUserByUsername: getUserByUsername2 } = await import("./auth-A4VGHADI.js");
|
|
2852
2865
|
creatorType = (await getUserByUsername2(createdBy))?.user_type;
|
|
2853
2866
|
}
|
|
2854
2867
|
await sendSystemMessage(mindName, buildSeedOrientation(createdBy, creatorType));
|
|
2855
2868
|
}
|
|
2856
2869
|
async function ensureCreatorDM(mindName, creatorUsername) {
|
|
2857
|
-
const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-
|
|
2858
|
-
const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-
|
|
2870
|
+
const { getOrCreateMindUser: getOrCreateMindUser2, getUserByUsername: getUserByUsername2 } = await import("./auth-A4VGHADI.js");
|
|
2871
|
+
const { findDMConversation: findDMConversation2, createConversation: createConversation2 } = await import("./conversations-C7IKYSHD.js");
|
|
2859
2872
|
const mindUser = await getOrCreateMindUser2(mindName);
|
|
2860
2873
|
const creatorUser = await getUserByUsername2(creatorUsername);
|
|
2861
2874
|
if (!creatorUser) {
|
|
@@ -2951,7 +2964,7 @@ var SleepManager = class {
|
|
|
2951
2964
|
transitioning = /* @__PURE__ */ new Set();
|
|
2952
2965
|
sleepConfigs = /* @__PURE__ */ new Map();
|
|
2953
2966
|
get statePath() {
|
|
2954
|
-
return
|
|
2967
|
+
return resolve8(voluteSystemDir(), "sleep-state.json");
|
|
2955
2968
|
}
|
|
2956
2969
|
start() {
|
|
2957
2970
|
this.loadState();
|
|
@@ -2967,8 +2980,8 @@ var SleepManager = class {
|
|
|
2967
2980
|
// --- State persistence ---
|
|
2968
2981
|
loadState() {
|
|
2969
2982
|
try {
|
|
2970
|
-
if (
|
|
2971
|
-
const data = JSON.parse(
|
|
2983
|
+
if (existsSync9(this.statePath)) {
|
|
2984
|
+
const data = JSON.parse(readFileSync8(this.statePath, "utf-8"));
|
|
2972
2985
|
for (const [name, state2] of Object.entries(data)) {
|
|
2973
2986
|
state2.triggerWakeHistory ??= [];
|
|
2974
2987
|
state2.wakeFailures ??= 0;
|
|
@@ -3259,7 +3272,7 @@ var SleepManager = class {
|
|
|
3259
3272
|
async flushQueuedMessages(name) {
|
|
3260
3273
|
try {
|
|
3261
3274
|
const db = await getDb();
|
|
3262
|
-
const rows = await db.select().from(deliveryQueue).where(
|
|
3275
|
+
const rows = await db.select().from(deliveryQueue).where(and(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
|
|
3263
3276
|
if (rows.length === 0) return 0;
|
|
3264
3277
|
const groups = /* @__PURE__ */ new Map();
|
|
3265
3278
|
for (const row of rows) {
|
|
@@ -3271,7 +3284,7 @@ var SleepManager = class {
|
|
|
3271
3284
|
`dropping unparseable queued message ${row.id} for ${name}`,
|
|
3272
3285
|
logger_default.errorData(err)
|
|
3273
3286
|
);
|
|
3274
|
-
await db.delete(deliveryQueue).where(
|
|
3287
|
+
await db.delete(deliveryQueue).where(eq2(deliveryQueue.id, row.id));
|
|
3275
3288
|
continue;
|
|
3276
3289
|
}
|
|
3277
3290
|
const channel = payload.channel ?? "unknown";
|
|
@@ -3314,7 +3327,7 @@ var SleepManager = class {
|
|
|
3314
3327
|
* the real delivery stack. Returns whether the batch was handed off to the mind.
|
|
3315
3328
|
*/
|
|
3316
3329
|
async deliverQueuedBatch(name, payloads) {
|
|
3317
|
-
const { deliverBatch: deliverBatch2 } = await import("./message-delivery-
|
|
3330
|
+
const { deliverBatch: deliverBatch2 } = await import("./message-delivery-GPQ5SE5N.js");
|
|
3318
3331
|
return deliverBatch2(name, payloads);
|
|
3319
3332
|
}
|
|
3320
3333
|
// --- Wake failure handling (#419) ---
|
|
@@ -3492,17 +3505,17 @@ var SleepManager = class {
|
|
|
3492
3505
|
}
|
|
3493
3506
|
}
|
|
3494
3507
|
async waitForIdle(name, timeoutMs) {
|
|
3495
|
-
return new Promise((
|
|
3508
|
+
return new Promise((resolve10) => {
|
|
3496
3509
|
const timeout = setTimeout(() => {
|
|
3497
3510
|
unsub();
|
|
3498
|
-
|
|
3511
|
+
resolve10();
|
|
3499
3512
|
}, timeoutMs);
|
|
3500
3513
|
const unsub = subscribe((event) => {
|
|
3501
3514
|
if (event.mind !== name) return;
|
|
3502
3515
|
if (event.type === "mind_done" || event.type === "mind_idle") {
|
|
3503
3516
|
clearTimeout(timeout);
|
|
3504
3517
|
unsub();
|
|
3505
|
-
|
|
3518
|
+
resolve10();
|
|
3506
3519
|
}
|
|
3507
3520
|
});
|
|
3508
3521
|
});
|
|
@@ -3510,15 +3523,15 @@ var SleepManager = class {
|
|
|
3510
3523
|
async archiveSessions(name) {
|
|
3511
3524
|
const dir = mindDir(name);
|
|
3512
3525
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
3513
|
-
const sessionsDir =
|
|
3514
|
-
if (
|
|
3515
|
-
const archiveDir =
|
|
3526
|
+
const sessionsDir = resolve8(dir, ".mind", "sessions");
|
|
3527
|
+
if (existsSync9(sessionsDir)) {
|
|
3528
|
+
const archiveDir = resolve8(sessionsDir, "archive");
|
|
3516
3529
|
mkdirSync5(archiveDir, { recursive: true });
|
|
3517
3530
|
for (const file of readdirSync2(sessionsDir)) {
|
|
3518
3531
|
if (file === "archive" || !file.endsWith(".json")) continue;
|
|
3519
|
-
const src =
|
|
3532
|
+
const src = resolve8(sessionsDir, file);
|
|
3520
3533
|
const base = file.replace(/\.json$/, "");
|
|
3521
|
-
const dest =
|
|
3534
|
+
const dest = resolve8(archiveDir, `${base}-${timestamp}.json`);
|
|
3522
3535
|
try {
|
|
3523
3536
|
renameSync(src, dest);
|
|
3524
3537
|
} catch (err) {
|
|
@@ -3526,14 +3539,14 @@ var SleepManager = class {
|
|
|
3526
3539
|
}
|
|
3527
3540
|
}
|
|
3528
3541
|
}
|
|
3529
|
-
const piSessionsDir =
|
|
3530
|
-
if (
|
|
3531
|
-
const archiveDir =
|
|
3542
|
+
const piSessionsDir = resolve8(dir, ".mind", "pi-sessions");
|
|
3543
|
+
if (existsSync9(piSessionsDir)) {
|
|
3544
|
+
const archiveDir = resolve8(piSessionsDir, "archive");
|
|
3532
3545
|
mkdirSync5(archiveDir, { recursive: true });
|
|
3533
3546
|
for (const entry of readdirSync2(piSessionsDir, { withFileTypes: true })) {
|
|
3534
3547
|
if (entry.name === "archive" || !entry.isDirectory()) continue;
|
|
3535
|
-
const src =
|
|
3536
|
-
const dest =
|
|
3548
|
+
const src = resolve8(piSessionsDir, entry.name);
|
|
3549
|
+
const dest = resolve8(archiveDir, `${entry.name}-${timestamp}`);
|
|
3537
3550
|
try {
|
|
3538
3551
|
renameSync(src, dest);
|
|
3539
3552
|
} catch (err) {
|
|
@@ -3543,8 +3556,8 @@ var SleepManager = class {
|
|
|
3543
3556
|
}
|
|
3544
3557
|
}
|
|
3545
3558
|
async runWakeContextScript(name, sleepingSince, duration) {
|
|
3546
|
-
const scriptPath =
|
|
3547
|
-
if (!
|
|
3559
|
+
const scriptPath = resolve8(mindDir(name), "home", ".local", "hooks", "wake-context.sh");
|
|
3560
|
+
if (!existsSync9(scriptPath)) return "";
|
|
3548
3561
|
const input = JSON.stringify({
|
|
3549
3562
|
sleepingSince,
|
|
3550
3563
|
duration,
|
|
@@ -3594,7 +3607,7 @@ var SleepManager = class {
|
|
|
3594
3607
|
async buildQueuedSummary(name) {
|
|
3595
3608
|
try {
|
|
3596
3609
|
const db = await getDb();
|
|
3597
|
-
const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(
|
|
3610
|
+
const rows = await db.select({ channel: deliveryQueue.channel, sender: deliveryQueue.sender }).from(deliveryQueue).where(and(eq2(deliveryQueue.mind, name), eq2(deliveryQueue.status, "sleep-queued"))).all();
|
|
3598
3611
|
if (rows.length === 0) return "No messages arrived while you slept.";
|
|
3599
3612
|
const channelCounts = /* @__PURE__ */ new Map();
|
|
3600
3613
|
const senders = /* @__PURE__ */ new Set();
|
|
@@ -3641,7 +3654,7 @@ var SleepManager = class {
|
|
|
3641
3654
|
} catch {
|
|
3642
3655
|
try {
|
|
3643
3656
|
const portHex = port.toString(16).toUpperCase().padStart(4, "0");
|
|
3644
|
-
const tcp6 =
|
|
3657
|
+
const tcp6 = readFileSync8("/proc/net/tcp6", "utf-8");
|
|
3645
3658
|
for (const line of tcp6.split("\n")) {
|
|
3646
3659
|
if (!line.includes(`:${portHex} `)) continue;
|
|
3647
3660
|
const fields = line.trim().split(/\s+/);
|
|
@@ -3709,9 +3722,9 @@ function getSleepManagerIfReady() {
|
|
|
3709
3722
|
}
|
|
3710
3723
|
|
|
3711
3724
|
// packages/daemon/src/lib/delivery/delivery-manager.ts
|
|
3712
|
-
import {
|
|
3713
|
-
import {
|
|
3714
|
-
import { and as
|
|
3725
|
+
import { realpath } from "fs/promises";
|
|
3726
|
+
import { resolve as resolve9 } from "path";
|
|
3727
|
+
import { and as and3, eq as eq4, inArray as inArray2, sql } from "drizzle-orm";
|
|
3715
3728
|
|
|
3716
3729
|
// packages/daemon/src/lib/chat/typing.ts
|
|
3717
3730
|
var DEFAULT_TTL_MS = 1e4;
|
|
@@ -3805,7 +3818,7 @@ function publishTypingForChannels(channels, map) {
|
|
|
3805
3818
|
}
|
|
3806
3819
|
|
|
3807
3820
|
// packages/daemon/src/lib/delivery/send-gate.ts
|
|
3808
|
-
import { and as
|
|
3821
|
+
import { and as and2, asc, desc, eq as eq3, gt } from "drizzle-orm";
|
|
3809
3822
|
var state = /* @__PURE__ */ new Map();
|
|
3810
3823
|
function extractText(raw) {
|
|
3811
3824
|
if (!raw) return "";
|
|
@@ -3820,7 +3833,7 @@ function extractText(raw) {
|
|
|
3820
3833
|
}
|
|
3821
3834
|
async function maxMessageId(conversationId) {
|
|
3822
3835
|
const db = await getDb();
|
|
3823
|
-
const rows = await db.select({ id: messages.id }).from(messages).where(
|
|
3836
|
+
const rows = await db.select({ id: messages.id }).from(messages).where(eq3(messages.conversation_id, conversationId)).orderBy(desc(messages.id)).limit(1);
|
|
3824
3837
|
return rows[0]?.id ?? 0;
|
|
3825
3838
|
}
|
|
3826
3839
|
async function onDeliveredToMind(mindBase, conversationId) {
|
|
@@ -3839,7 +3852,7 @@ async function checkStaleSend(mindBase, conversationId, excludeSenders) {
|
|
|
3839
3852
|
const entry = state.get(mindBase)?.get(conversationId);
|
|
3840
3853
|
if (!entry?.turnOpen || entry.heldThisTurn) return { held: false };
|
|
3841
3854
|
const db = await getDb();
|
|
3842
|
-
const rows = await db.select({ id: messages.id, sender: messages.sender_name, content: messages.content }).from(messages).where(
|
|
3855
|
+
const rows = await db.select({ id: messages.id, sender: messages.sender_name, content: messages.content }).from(messages).where(and2(eq3(messages.conversation_id, conversationId), gt(messages.id, entry.baselineId))).orderBy(asc(messages.id));
|
|
3843
3856
|
const excluded = new Set(excludeSenders.filter(Boolean));
|
|
3844
3857
|
const unseen = rows.filter((r) => r.sender && !excluded.has(r.sender));
|
|
3845
3858
|
if (unseen.length === 0) return { held: false };
|
|
@@ -3876,6 +3889,8 @@ var REDRIVE_INTERVAL_MS = 15e3;
|
|
|
3876
3889
|
var RETRY_BASE_MS = 5e3;
|
|
3877
3890
|
var RETRY_MAX_MS = 5 * 6e4;
|
|
3878
3891
|
var REDRIVE_BATCH_LIMIT = 200;
|
|
3892
|
+
var GATED_RELEASE_LIMIT_PER_CHANNEL = 10;
|
|
3893
|
+
var GATED_NOTIFY_EVERY = 10;
|
|
3879
3894
|
var mentionRegexCache = /* @__PURE__ */ new Map();
|
|
3880
3895
|
var avatarBlocksCache = /* @__PURE__ */ new Map();
|
|
3881
3896
|
var AVATAR_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -3896,6 +3911,11 @@ var DeliveryManager = class {
|
|
|
3896
3911
|
redriveTimer = null;
|
|
3897
3912
|
/** Predicate for whether a mind is up; overridable in tests. */
|
|
3898
3913
|
isMindRunning = (name) => tryGetMindManager()?.isRunning(name) ?? false;
|
|
3914
|
+
/** Sends a system message to a mind (invites, release summaries); overridable in tests. */
|
|
3915
|
+
notify = async (mindName, text) => {
|
|
3916
|
+
const { sendSystemMessage: sendSystemMessage2 } = await import("./system-chat-63ABFF6V.js");
|
|
3917
|
+
await sendSystemMessage2(mindName, text);
|
|
3918
|
+
};
|
|
3899
3919
|
constructor() {
|
|
3900
3920
|
setRoutesChangeListener((mind) => {
|
|
3901
3921
|
this.releaseGated(mind).catch(
|
|
@@ -3907,6 +3927,10 @@ var DeliveryManager = class {
|
|
|
3907
3927
|
setRunningCheck(fn) {
|
|
3908
3928
|
this.isMindRunning = fn;
|
|
3909
3929
|
}
|
|
3930
|
+
/** Test seam: capture/override system notifications sent to minds. */
|
|
3931
|
+
setNotifier(fn) {
|
|
3932
|
+
this.notify = fn;
|
|
3933
|
+
}
|
|
3910
3934
|
// --- Public API ---
|
|
3911
3935
|
/**
|
|
3912
3936
|
* Route and deliver a message to a mind. This is the main entry point.
|
|
@@ -4023,9 +4047,9 @@ var DeliveryManager = class {
|
|
|
4023
4047
|
try {
|
|
4024
4048
|
const db = await getDb();
|
|
4025
4049
|
rows = await db.select().from(deliveryQueue).where(
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4050
|
+
and3(
|
|
4051
|
+
eq4(deliveryQueue.status, "pending"),
|
|
4052
|
+
sql`(${deliveryQueue.next_attempt_at} IS NULL OR ${deliveryQueue.next_attempt_at} <= datetime('now'))`
|
|
4029
4053
|
)
|
|
4030
4054
|
).orderBy(deliveryQueue.id).limit(REDRIVE_BATCH_LIMIT);
|
|
4031
4055
|
} catch (err) {
|
|
@@ -4069,9 +4093,15 @@ var DeliveryManager = class {
|
|
|
4069
4093
|
if (redriven > 0) dlog.info(`redrove ${redriven} pending delivery queue rows`);
|
|
4070
4094
|
}
|
|
4071
4095
|
/**
|
|
4072
|
-
* Re-evaluate a mind's `gated` rows against its current routes.json
|
|
4073
|
-
*
|
|
4074
|
-
*
|
|
4096
|
+
* Re-evaluate a mind's `gated` rows against its current routes.json when the routing
|
|
4097
|
+
* config changes. For each channel that now matches a `mind` route:
|
|
4098
|
+
* - the newest {@link GATED_RELEASE_LIMIT_PER_CHANNEL} rows are promoted to `pending`
|
|
4099
|
+
* and re-stamped with the freshly-resolved session (NOT the gate-time fallback), so
|
|
4100
|
+
* they land in the correct session instead of `main` (#537 bug 1);
|
|
4101
|
+
* - any older rows are `archived` (inert) so a long backlog can't flood the mind
|
|
4102
|
+
* in one sweep (#537 bug 2), and the mind gets one summary rather than a flood.
|
|
4103
|
+
* Rows resolving to a `file` destination are archived (the delivery manager doesn't
|
|
4104
|
+
* deliver file routes). Declined channels are skipped entirely — they stay gated.
|
|
4075
4105
|
*/
|
|
4076
4106
|
async releaseGated(mindName) {
|
|
4077
4107
|
const baseName = await getBaseName(mindName);
|
|
@@ -4079,12 +4109,13 @@ var DeliveryManager = class {
|
|
|
4079
4109
|
let rows;
|
|
4080
4110
|
try {
|
|
4081
4111
|
const db = await getDb();
|
|
4082
|
-
rows = await db.select().from(deliveryQueue).where(
|
|
4112
|
+
rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, baseName), eq4(deliveryQueue.status, "gated")));
|
|
4083
4113
|
} catch (err) {
|
|
4084
4114
|
dlog.warn(`failed to read gated rows for ${baseName}`, logger_default.errorData(err));
|
|
4085
4115
|
return;
|
|
4086
4116
|
}
|
|
4087
|
-
const
|
|
4117
|
+
const byChannel = /* @__PURE__ */ new Map();
|
|
4118
|
+
const archiveIds = [];
|
|
4088
4119
|
for (const row of rows) {
|
|
4089
4120
|
let payload;
|
|
4090
4121
|
try {
|
|
@@ -4099,27 +4130,127 @@ var DeliveryManager = class {
|
|
|
4099
4130
|
participantCount: payload.participantCount
|
|
4100
4131
|
};
|
|
4101
4132
|
const route = resolveRoute(config, meta);
|
|
4102
|
-
if (route.matched
|
|
4103
|
-
|
|
4133
|
+
if (!route.matched) continue;
|
|
4134
|
+
if (route.destination === "file") {
|
|
4135
|
+
archiveIds.push(row.id);
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
let session = route.session;
|
|
4139
|
+
if (session === "$new") {
|
|
4140
|
+
session = `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4141
|
+
}
|
|
4142
|
+
const channel = row.channel ?? payload.channel ?? "unknown";
|
|
4143
|
+
const list = byChannel.get(channel) ?? [];
|
|
4144
|
+
list.push({ id: row.id, session });
|
|
4145
|
+
byChannel.set(channel, list);
|
|
4146
|
+
}
|
|
4147
|
+
const promote = [];
|
|
4148
|
+
const truncationNotes = [];
|
|
4149
|
+
for (const [channel, items] of byChannel) {
|
|
4150
|
+
if (await this.isChannelDeclined(baseName, channel)) continue;
|
|
4151
|
+
items.sort((a, b) => b.id - a.id);
|
|
4152
|
+
const keep = items.slice(0, GATED_RELEASE_LIMIT_PER_CHANNEL);
|
|
4153
|
+
const drop = items.slice(GATED_RELEASE_LIMIT_PER_CHANNEL);
|
|
4154
|
+
promote.push(...keep);
|
|
4155
|
+
for (const d of drop) archiveIds.push(d.id);
|
|
4156
|
+
if (drop.length > 0) {
|
|
4157
|
+
truncationNotes.push(
|
|
4158
|
+
`${channel}: released the ${keep.length} most recent message(s); ${drop.length} earlier message(s) were held while unrouted and remain in the channel history (volute chat read ${channel}).`
|
|
4159
|
+
);
|
|
4160
|
+
dlog.info(
|
|
4161
|
+
`truncated gated release for ${baseName} on ${channel}: kept ${keep.length}, archived ${drop.length}`
|
|
4162
|
+
);
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
if (archiveIds.length > 0) {
|
|
4166
|
+
try {
|
|
4167
|
+
const db = await getDb();
|
|
4168
|
+
for (let i = 0; i < archiveIds.length; i += 500) {
|
|
4169
|
+
await db.update(deliveryQueue).set({ status: "archived" }).where(inArray2(deliveryQueue.id, archiveIds.slice(i, i + 500)));
|
|
4170
|
+
}
|
|
4171
|
+
} catch (err) {
|
|
4172
|
+
dlog.warn(`failed to archive gated rows for ${baseName}`, logger_default.errorData(err));
|
|
4104
4173
|
}
|
|
4105
4174
|
}
|
|
4106
|
-
if (promote.length === 0)
|
|
4175
|
+
if (promote.length === 0) {
|
|
4176
|
+
if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4107
4179
|
try {
|
|
4108
4180
|
const db = await getDb();
|
|
4109
|
-
|
|
4181
|
+
for (const p of promote) {
|
|
4182
|
+
await db.update(deliveryQueue).set({ status: "pending", session: p.session, attempts: 0, next_attempt_at: null }).where(eq4(deliveryQueue.id, p.id));
|
|
4183
|
+
}
|
|
4110
4184
|
dlog.info(`released ${promote.length} gated message(s) for ${baseName} after route change`);
|
|
4111
4185
|
} catch (err) {
|
|
4112
4186
|
dlog.warn(`failed to promote gated rows for ${baseName}`, logger_default.errorData(err));
|
|
4113
4187
|
return;
|
|
4114
4188
|
}
|
|
4189
|
+
if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
|
|
4115
4190
|
await this.redrive();
|
|
4116
4191
|
}
|
|
4192
|
+
/**
|
|
4193
|
+
* Whether the mind has explicitly declined a channel. A declined channel keeps
|
|
4194
|
+
* persisting history but never notifies and is never released. #537
|
|
4195
|
+
*/
|
|
4196
|
+
async isChannelDeclined(baseName, channel) {
|
|
4197
|
+
if (!channel) return false;
|
|
4198
|
+
try {
|
|
4199
|
+
const db = await getDb();
|
|
4200
|
+
const rows = await db.select({ state: channelGates.state }).from(channelGates).where(and3(eq4(channelGates.mind, baseName), eq4(channelGates.channel, channel)));
|
|
4201
|
+
return rows[0]?.state === "declined";
|
|
4202
|
+
} catch (err) {
|
|
4203
|
+
dlog.warn(`failed to read gate state for ${baseName}/${channel}`, logger_default.errorData(err));
|
|
4204
|
+
return false;
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
/**
|
|
4208
|
+
* Record that a mind has declined an unrouted channel: future messages are still
|
|
4209
|
+
* persisted (history is preserved) but never notify, and any currently-gated rows are
|
|
4210
|
+
* archived so they're inert. Returns the number of held messages archived. #537
|
|
4211
|
+
*/
|
|
4212
|
+
async declineChannel(mindName, channel) {
|
|
4213
|
+
const baseName = await getBaseName(mindName);
|
|
4214
|
+
const db = await getDb();
|
|
4215
|
+
await db.insert(channelGates).values({ mind: baseName, channel, state: "declined" }).onConflictDoUpdate({
|
|
4216
|
+
target: [channelGates.mind, channelGates.channel],
|
|
4217
|
+
set: { state: "declined", updated_at: sql`(datetime('now'))` }
|
|
4218
|
+
});
|
|
4219
|
+
const archived = await db.update(deliveryQueue).set({ status: "archived" }).where(
|
|
4220
|
+
and3(
|
|
4221
|
+
eq4(deliveryQueue.mind, baseName),
|
|
4222
|
+
eq4(deliveryQueue.channel, channel),
|
|
4223
|
+
eq4(deliveryQueue.status, "gated")
|
|
4224
|
+
)
|
|
4225
|
+
).returning({ id: deliveryQueue.id });
|
|
4226
|
+
dlog.info(
|
|
4227
|
+
`declined channel ${channel} for ${baseName}; archived ${archived.length} held row(s)`
|
|
4228
|
+
);
|
|
4229
|
+
return archived.length;
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* Send the mind a single summary when a routing change released a truncated backlog,
|
|
4233
|
+
* rather than a flood of individual messages. #537
|
|
4234
|
+
*/
|
|
4235
|
+
async sendReleaseSummary(mindName, notes) {
|
|
4236
|
+
const body = [
|
|
4237
|
+
`[Channel backlog released]`,
|
|
4238
|
+
`A routing change matched channel(s) that had held messages while unrouted. To avoid flooding you, only the ${GATED_RELEASE_LIMIT_PER_CHANNEL} most recent per channel were delivered:`,
|
|
4239
|
+
"",
|
|
4240
|
+
...notes.map((n) => `- ${n}`)
|
|
4241
|
+
].join("\n");
|
|
4242
|
+
try {
|
|
4243
|
+
await this.notify(mindName, body);
|
|
4244
|
+
} catch (err) {
|
|
4245
|
+
dlog.warn(`failed to send release summary for ${mindName}`, logger_default.errorData(err));
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4117
4248
|
/**
|
|
4118
4249
|
* Get pending (gated) messages for a mind.
|
|
4119
4250
|
*/
|
|
4120
4251
|
async getPending(mindName) {
|
|
4121
4252
|
const db = await getDb();
|
|
4122
|
-
const rows = await db.select().from(deliveryQueue).where(
|
|
4253
|
+
const rows = await db.select().from(deliveryQueue).where(and3(eq4(deliveryQueue.mind, mindName), eq4(deliveryQueue.status, "gated")));
|
|
4123
4254
|
const byChannel = /* @__PURE__ */ new Map();
|
|
4124
4255
|
for (const row of rows) {
|
|
4125
4256
|
const ch = row.channel ?? "unknown";
|
|
@@ -4289,7 +4420,7 @@ var DeliveryManager = class {
|
|
|
4289
4420
|
const backoffSec = Math.round(
|
|
4290
4421
|
Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(attempts, 20)) / 1e3
|
|
4291
4422
|
);
|
|
4292
|
-
await db.update(deliveryQueue).set({ attempts, next_attempt_at:
|
|
4423
|
+
await db.update(deliveryQueue).set({ attempts, next_attempt_at: sql`datetime('now', ${`+${backoffSec} seconds`})` }).where(eq4(deliveryQueue.id, row.id));
|
|
4293
4424
|
}
|
|
4294
4425
|
} catch (err) {
|
|
4295
4426
|
dlog.warn("failed to schedule delivery retry", logger_default.errorData(err));
|
|
@@ -4513,41 +4644,44 @@ var DeliveryManager = class {
|
|
|
4513
4644
|
}
|
|
4514
4645
|
async gateMessage(mindName, session, payload) {
|
|
4515
4646
|
const baseName = await getBaseName(mindName);
|
|
4516
|
-
await this.
|
|
4647
|
+
const declined = await this.isChannelDeclined(baseName, payload.channel);
|
|
4648
|
+
await this.persistToQueue(mindName, session, payload, declined ? "archived" : "gated");
|
|
4649
|
+
if (declined) return;
|
|
4517
4650
|
try {
|
|
4518
4651
|
const db = await getDb();
|
|
4519
|
-
const
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4652
|
+
const rows = await db.select({ count: sql`count(*)` }).from(deliveryQueue).where(
|
|
4653
|
+
and3(
|
|
4654
|
+
eq4(deliveryQueue.mind, baseName),
|
|
4655
|
+
eq4(deliveryQueue.channel, payload.channel),
|
|
4656
|
+
inArray2(deliveryQueue.status, ["gated", "archived"])
|
|
4524
4657
|
)
|
|
4525
4658
|
);
|
|
4526
|
-
|
|
4527
|
-
|
|
4659
|
+
const count = rows[0]?.count ?? 0;
|
|
4660
|
+
if (count === 1 || count % GATED_NOTIFY_EVERY === 0) {
|
|
4661
|
+
await this.sendInviteNotification(mindName, payload, count);
|
|
4528
4662
|
}
|
|
4529
4663
|
} catch (err) {
|
|
4530
4664
|
dlog.warn(`failed to check gated count for ${baseName}`, logger_default.errorData(err));
|
|
4531
4665
|
}
|
|
4532
4666
|
}
|
|
4533
|
-
async sendInviteNotification(mindName, payload) {
|
|
4667
|
+
async sendInviteNotification(mindName, payload, gatedCount = 1) {
|
|
4534
4668
|
const text = extractTextContent(payload.content);
|
|
4535
4669
|
const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;
|
|
4536
4670
|
const channel = payload.channel ?? "unknown";
|
|
4671
|
+
const heldLine = gatedCount > 1 ? `${gatedCount} messages from this channel are being held, unrouted \u2014 you've not routed it yet.` : `Someone new is reaching out \u2014 you don't have a route for this channel yet.`;
|
|
4537
4672
|
const notification = [
|
|
4538
4673
|
`[New channel: ${channel}]`,
|
|
4539
|
-
|
|
4674
|
+
heldLine,
|
|
4540
4675
|
`Sender: ${payload.sender ?? "unknown"}`,
|
|
4541
4676
|
payload.platform ? `Platform: ${payload.platform}` : null,
|
|
4542
4677
|
payload.participantCount ? `Participants: ${payload.participantCount}` : null,
|
|
4543
4678
|
"",
|
|
4544
4679
|
`Preview: ${preview}`,
|
|
4545
4680
|
"",
|
|
4546
|
-
`To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json.`,
|
|
4547
|
-
`
|
|
4681
|
+
`To start hearing this channel, add a routing rule for "${channel}" to .config/routes.json \u2014 only the ${GATED_RELEASE_LIMIT_PER_CHANNEL} most recent held messages are replayed; older ones stay in the channel history (volute chat read ${channel}).`,
|
|
4682
|
+
`To stop hearing about it, decline the channel: volute chat channels decline ${channel}`
|
|
4548
4683
|
].filter((line) => line !== null).join("\n");
|
|
4549
|
-
|
|
4550
|
-
await sendSystemMessage2(mindName, notification);
|
|
4684
|
+
await this.notify(mindName, notification);
|
|
4551
4685
|
}
|
|
4552
4686
|
/**
|
|
4553
4687
|
* Insert a delivery_queue row and return its id. The `mind` column is always keyed by
|
|
@@ -4612,18 +4746,6 @@ var DeliveryManager = class {
|
|
|
4612
4746
|
const cached = avatarBlocksCache.get(cacheKey);
|
|
4613
4747
|
if (cached && cached.expiresAt > Date.now()) return cached.blocks;
|
|
4614
4748
|
const blocks = [];
|
|
4615
|
-
let sharpDefault = null;
|
|
4616
|
-
try {
|
|
4617
|
-
const mod = await import("sharp");
|
|
4618
|
-
sharpDefault = mod.default ?? mod;
|
|
4619
|
-
} catch (err) {
|
|
4620
|
-
const code = err.code;
|
|
4621
|
-
if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
|
|
4622
|
-
dlog.debug("sharp not available, sending full-size avatars");
|
|
4623
|
-
} else {
|
|
4624
|
-
dlog.warn("sharp import failed, sending full-size avatars", logger_default.errorData(err));
|
|
4625
|
-
}
|
|
4626
|
-
}
|
|
4627
4749
|
for (const p of participants) {
|
|
4628
4750
|
if (!p.avatar) continue;
|
|
4629
4751
|
try {
|
|
@@ -4632,8 +4754,8 @@ var DeliveryManager = class {
|
|
|
4632
4754
|
const dir = mindDir(p.username);
|
|
4633
4755
|
const config = readVoluteConfig(dir);
|
|
4634
4756
|
if (!config?.profile?.avatar) continue;
|
|
4635
|
-
filePath =
|
|
4636
|
-
const homeDir =
|
|
4757
|
+
filePath = resolve9(dir, "home", config.profile.avatar);
|
|
4758
|
+
const homeDir = resolve9(dir, "home");
|
|
4637
4759
|
if (!filePath.startsWith(`${homeDir}/`)) {
|
|
4638
4760
|
dlog.warn(`avatar path for ${p.username} escapes home directory, skipping`);
|
|
4639
4761
|
continue;
|
|
@@ -4652,34 +4774,10 @@ var DeliveryManager = class {
|
|
|
4652
4774
|
throw err;
|
|
4653
4775
|
}
|
|
4654
4776
|
} else {
|
|
4655
|
-
filePath =
|
|
4656
|
-
}
|
|
4657
|
-
const ext = extname(filePath).toLowerCase();
|
|
4658
|
-
const mimeMap = {
|
|
4659
|
-
".png": "image/png",
|
|
4660
|
-
".jpg": "image/jpeg",
|
|
4661
|
-
".jpeg": "image/jpeg",
|
|
4662
|
-
".gif": "image/gif",
|
|
4663
|
-
".webp": "image/webp"
|
|
4664
|
-
};
|
|
4665
|
-
const mediaType = mimeMap[ext];
|
|
4666
|
-
if (!mediaType) continue;
|
|
4667
|
-
const data = await readFile(filePath);
|
|
4668
|
-
let imageData = data;
|
|
4669
|
-
if (sharpDefault) {
|
|
4670
|
-
try {
|
|
4671
|
-
imageData = await sharpDefault(data).resize(128, 128, { fit: "cover" }).toBuffer();
|
|
4672
|
-
} catch (err) {
|
|
4673
|
-
dlog.warn(
|
|
4674
|
-
`avatar resize failed for ${p.username}, sending original`,
|
|
4675
|
-
logger_default.errorData(err)
|
|
4676
|
-
);
|
|
4677
|
-
}
|
|
4777
|
+
filePath = resolve9(voluteHome(), "avatars", p.avatar);
|
|
4678
4778
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
{ type: "image", media_type: mediaType, data: imageData.toString("base64") }
|
|
4682
|
-
);
|
|
4779
|
+
const rendered = await renderAvatarBlock(filePath, p.username);
|
|
4780
|
+
if (rendered) blocks.push(...rendered);
|
|
4683
4781
|
} catch (err) {
|
|
4684
4782
|
const code = err.code;
|
|
4685
4783
|
if (code !== "ENOENT") {
|
|
@@ -4804,7 +4902,7 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
|
|
|
4804
4902
|
content: mindHistory.content,
|
|
4805
4903
|
message_id: mindHistory.message_id,
|
|
4806
4904
|
turn_id: mindHistory.turn_id
|
|
4807
|
-
}).from(mindHistory).where(
|
|
4905
|
+
}).from(mindHistory).where(and4(eq5(mindHistory.id, outboundId), eq5(mindHistory.mind, mind))).limit(1);
|
|
4808
4906
|
const row = rows[0];
|
|
4809
4907
|
if (!row) {
|
|
4810
4908
|
dlog2.warn(`outbound marker references missing record: mind=${mind} id=${outboundId}`);
|
|
@@ -4812,13 +4910,13 @@ async function linkToolResultToTurn(mind, turnId, toolResultContent, toolUseEven
|
|
|
4812
4910
|
}
|
|
4813
4911
|
const alreadyTagged = row.turn_id != null;
|
|
4814
4912
|
if (!alreadyTagged) {
|
|
4815
|
-
await db.update(mindHistory).set({ turn_id: turnId }).where(
|
|
4913
|
+
await db.update(mindHistory).set({ turn_id: turnId }).where(eq5(mindHistory.id, outboundId));
|
|
4816
4914
|
}
|
|
4817
4915
|
if (row.message_id) {
|
|
4818
4916
|
await db.update(messages).set({
|
|
4819
4917
|
turn_id: turnId,
|
|
4820
4918
|
...toolUseEventId != null ? { source_event_id: toolUseEventId } : {}
|
|
4821
|
-
}).where(
|
|
4919
|
+
}).where(eq5(messages.id, Number(row.message_id)));
|
|
4822
4920
|
}
|
|
4823
4921
|
if (!alreadyTagged) {
|
|
4824
4922
|
publish3(mind, {
|
|
@@ -5007,7 +5105,7 @@ async function spiritWillHandle() {
|
|
|
5007
5105
|
const spiritEntry = await findMind(SPIRIT_NAME);
|
|
5008
5106
|
if (spiritEntry?.running && spiritEntry.mindType === "spirit") return true;
|
|
5009
5107
|
try {
|
|
5010
|
-
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-
|
|
5108
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
|
|
5011
5109
|
const sm = getSleepManagerIfReady2();
|
|
5012
5110
|
if (sm?.isSleeping(SPIRIT_NAME)) return true;
|
|
5013
5111
|
} catch (err) {
|
|
@@ -5035,7 +5133,7 @@ async function generateSystemFallbackReply(conversationId, mindName, message) {
|
|
|
5035
5133
|
if (config.sleep.schedule?.wake) contextParts.push(`Wake cron: ${config.sleep.schedule.wake}`);
|
|
5036
5134
|
}
|
|
5037
5135
|
try {
|
|
5038
|
-
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-
|
|
5136
|
+
const { getSleepManagerIfReady: getSleepManagerIfReady2 } = await import("./sleep-manager-Z27R3WTW.js");
|
|
5039
5137
|
const sm = getSleepManagerIfReady2();
|
|
5040
5138
|
if (sm) {
|
|
5041
5139
|
const state2 = sm.getState(mindName);
|
|
@@ -5089,10 +5187,6 @@ export {
|
|
|
5089
5187
|
checkHealth,
|
|
5090
5188
|
RotatingLog,
|
|
5091
5189
|
syncProviderToMinds,
|
|
5092
|
-
recordNotice,
|
|
5093
|
-
drainNotices,
|
|
5094
|
-
clearDeliveredNotices,
|
|
5095
|
-
formatNotices,
|
|
5096
5190
|
RestartTracker,
|
|
5097
5191
|
getTypingMap,
|
|
5098
5192
|
isConversationId,
|
|
@@ -5120,6 +5214,7 @@ export {
|
|
|
5120
5214
|
readSystemsConfig,
|
|
5121
5215
|
writeSystemsConfig,
|
|
5122
5216
|
deleteSystemsConfig,
|
|
5217
|
+
enrichActivityMetadata,
|
|
5123
5218
|
parseCommandArgs,
|
|
5124
5219
|
loadAllExtensions,
|
|
5125
5220
|
getLoadedExtensions,
|
|
@@ -5140,6 +5235,7 @@ export {
|
|
|
5140
5235
|
initTokenBudget,
|
|
5141
5236
|
getTokenBudget,
|
|
5142
5237
|
startMindFull,
|
|
5238
|
+
recordMissingCredentialsNotice,
|
|
5143
5239
|
sleepMind,
|
|
5144
5240
|
wakeMind,
|
|
5145
5241
|
startSpiritFull,
|
|
@@ -5159,5 +5255,6 @@ export {
|
|
|
5159
5255
|
deliverBatch,
|
|
5160
5256
|
ensureSystemChannel,
|
|
5161
5257
|
joinSystemChannel,
|
|
5258
|
+
backfillSystemChannelMembers,
|
|
5162
5259
|
announceToSystem
|
|
5163
5260
|
};
|