volute 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/{accept-ZBDVVCEU.js → accept-AHAOUFBK.js} +4 -4
  2. package/dist/{activity-events-PWOGSMRL.js → activity-events-N6HCHU4P.js} +4 -4
  3. package/dist/{ai-service-GSZWIETO.js → ai-service-C2YNARGH.js} +5 -5
  4. package/dist/{api-client-3A77HMH7.js → api-client-LC5YRA32.js} +1 -1
  5. package/dist/{archive-Y2YEOCGB.js → archive-AWIJTVQV.js} +4 -4
  6. package/dist/{auth-YTQME4EV.js → auth-2QOOPMBX.js} +5 -5
  7. package/dist/{bridge-PXIO6PS2.js → bridge-F3ZJEKDN.js} +4 -4
  8. package/dist/{chat-ED7YOGKO.js → chat-5Y4FD77E.js} +9 -9
  9. package/dist/{chunk-75AJ54GM.js → chunk-2NHRJ3YO.js} +1 -1
  10. package/dist/{chunk-PJ4IPTIN.js → chunk-3F7XK5Q7.js} +1 -1
  11. package/dist/{chunk-6F3YNULE.js → chunk-4L2Q7IP5.js} +16 -16
  12. package/dist/{chunk-IIWF2IPD.js → chunk-5DPRTREW.js} +3 -3
  13. package/dist/{chunk-X2J7QUFH.js → chunk-7AZQFSOV.js} +1 -1
  14. package/dist/{chunk-NUX47Y2V.js → chunk-A6FLW5XD.js} +1 -1
  15. package/dist/{chunk-SWW6AUVW.js → chunk-BIEWHAAM.js} +1 -1
  16. package/dist/{chunk-TWAN7ZNO.js → chunk-CF4SO5L3.js} +3 -3
  17. package/dist/{chunk-7PTQGPJY.js → chunk-CJ26DXZL.js} +1 -1
  18. package/dist/{chunk-PY557GDR.js → chunk-GVVVMZ4J.js} +1 -1
  19. package/dist/chunk-K3NQKI34.js +10 -0
  20. package/dist/{chunk-BOLJUV77.js → chunk-KXXJYY62.js} +4 -4
  21. package/dist/chunk-LIIX2MOM.js +853 -0
  22. package/dist/chunk-LQ6Z4FXN.js +87 -0
  23. package/dist/{chunk-N2AUHW4C.js → chunk-MQRS4J24.js} +2 -2
  24. package/dist/{chunk-B35VNNSS.js → chunk-N42QMDID.js} +2 -2
  25. package/dist/{chunk-M5RYAA5I.js → chunk-ORNY3MZR.js} +4 -4
  26. package/dist/{chunk-YDBAY3NA.js → chunk-PMMHVSCR.js} +1 -1
  27. package/dist/{chunk-GBDVNPN2.js → chunk-QJGLTPAP.js} +8 -8
  28. package/dist/{chunk-DQ7VBXAP.js → chunk-RUIVWCYY.js} +122 -81
  29. package/dist/{chunk-L72WYMF7.js → chunk-SNW2NPP4.js} +1 -1
  30. package/dist/chunk-SZJWC2GA.js +125 -0
  31. package/dist/{chunk-DJT5Y4UF.js → chunk-TNZ5XQA4.js} +2 -2
  32. package/dist/{chunk-DMV5P2LU.js → chunk-UIM5NHPP.js} +3 -3
  33. package/dist/{chunk-ZTVKQOU7.js → chunk-WC635OPK.js} +1 -1
  34. package/dist/{chunk-YTWZORJN.js → chunk-XLBQYIHH.js} +1 -1
  35. package/dist/cli.js +24 -24
  36. package/dist/{clock-HSEKS5AR.js → clock-BMLJ2TR6.js} +8 -8
  37. package/dist/{cloud-sync-BOCZSDIA.js → cloud-sync-EZKCEIBX.js} +18 -18
  38. package/dist/{config-UTS7QULS.js → config-QB7W3Z7P.js} +4 -4
  39. package/dist/connectors/discord-bridge.js +1 -1
  40. package/dist/connectors/slack-bridge.js +1 -1
  41. package/dist/connectors/telegram-bridge.js +1 -1
  42. package/dist/{conversations-HH3CJD4E.js → conversations-G6YRSABR.js} +5 -5
  43. package/dist/{create-5BPOOJAN.js → create-C3BBFYV7.js} +4 -4
  44. package/dist/{create-QBEPSD2Z.js → create-PN73742N.js} +4 -4
  45. package/dist/{daemon-client-RVIKXGFQ.js → daemon-client-2MIPKY3E.js} +1 -1
  46. package/dist/{daemon-restart-SIR3UR4B.js → daemon-restart-S6SOD3C5.js} +11 -11
  47. package/dist/daemon.js +392 -1101
  48. package/dist/db-CBOCDYVA.js +9 -0
  49. package/dist/{db-BDMH4SZ2.js → db-IJL6B26S.js} +1 -1
  50. package/dist/{delete-L5PAVDGQ.js → delete-NLXES2C7.js} +3 -3
  51. package/dist/delivery-manager-3I7CA734.js +30 -0
  52. package/dist/{delivery-router-VSULHXNH.js → delivery-router-QTFEZ26O.js} +4 -4
  53. package/dist/down-25L2RKCQ.js +17 -0
  54. package/dist/echo-text-IWAQKNTC.js +31 -0
  55. package/dist/{env-E4XHO2BI.js → env-IQ6Q2333.js} +6 -6
  56. package/dist/exec-ONYZEA5B.js +17 -0
  57. package/dist/{export-HTFOHOKL.js → export-JPDBQESV.js} +6 -6
  58. package/dist/{extension-AKZ46YSL.js → extension-LZYHBNLV.js} +4 -4
  59. package/dist/{extensions-OOSFVH7U.js → extensions-PCOXTHNM.js} +17 -17
  60. package/dist/{files-H2YLRD37.js → files-LAQ3NXQK.js} +7 -7
  61. package/dist/{import-OL5BZX7S.js → import-EROF27RH.js} +11 -10
  62. package/dist/{isolation-N74RWOUX.js → isolation-G5J3MTKU.js} +4 -4
  63. package/dist/{join-DF5XSJAC.js → join-6SZCA5FX.js} +3 -3
  64. package/dist/{list-GJ4RUQQT.js → list-KHJZJPEJ.js} +4 -4
  65. package/dist/{login-JXRVMBRB.js → login-F6YMAVLE.js} +6 -6
  66. package/dist/{login-GOTAYLXP.js → login-GYTH67ES.js} +4 -4
  67. package/dist/{logout-FW243JBU.js → logout-HHPH52KZ.js} +6 -6
  68. package/dist/{logout-6KIA74EV.js → logout-YHQLOFLR.js} +4 -4
  69. package/dist/message-delivery-NWL7XEIX.js +40 -0
  70. package/dist/{mind-6VJJHF65.js → mind-MM2IYMJ3.js} +19 -19
  71. package/dist/{mind-activity-tracker-66UVYIFW.js → mind-activity-tracker-42ENM32S.js} +5 -5
  72. package/dist/{mind-history-MII2SK7F.js → mind-history-WHCNZ6I5.js} +5 -5
  73. package/dist/{mind-list-GEWHWAL4.js → mind-list-H3HC2ZRG.js} +4 -4
  74. package/dist/mind-manager-VOEQ2IZL.js +30 -0
  75. package/dist/{mind-profile-DCBDVF5B.js → mind-profile-7VYRJGFZ.js} +2 -2
  76. package/dist/mind-service-WRTOQSAL.js +36 -0
  77. package/dist/{mind-sleep-ITCF6OQA.js → mind-sleep-ZL5ZXFTM.js} +4 -4
  78. package/dist/{mind-status-X4SX3YUG.js → mind-status-ZWULKOUO.js} +4 -4
  79. package/dist/{mind-wake-KXMKMGWX.js → mind-wake-HK5ORGUK.js} +4 -4
  80. package/dist/{package-3W2MEXHB.js → package-TA6IHIED.js} +2 -2
  81. package/dist/{read-ZUDG4JWU.js → read-CP7MYMJQ.js} +4 -4
  82. package/dist/{read-stdin-3X5VYKNS.js → read-stdin-4B5UYPPM.js} +1 -1
  83. package/dist/{register-SB7NXCOE.js → register-XOBFEMI4.js} +4 -4
  84. package/dist/{registry-YPHK534W.js → registry-KMELPC3X.js} +3 -3
  85. package/dist/{reject-MUR2KWJ4.js → reject-43AGXB6B.js} +4 -4
  86. package/dist/{restart-5EGG4JXU.js → restart-O5QIYQJT.js} +5 -5
  87. package/dist/{sandbox-LP6YRAXS.js → sandbox-PQYEICEF.js} +5 -5
  88. package/dist/scheduler-355E746X.js +30 -0
  89. package/dist/{schema-MISD3JFG.js → schema-K575EBPE.js} +2 -2
  90. package/dist/{seed-CEC4RC23.js → seed-6S4Z6TAM.js} +2 -2
  91. package/dist/{seed-check-KJNTL72M.js → seed-check-HZPVFJKZ.js} +2 -2
  92. package/dist/{seed-cmd-WTTG7SRQ.js → seed-cmd-CBWLJWYD.js} +4 -4
  93. package/dist/{seed-create-M6RCC6RP.js → seed-create-NT6DG4SE.js} +6 -6
  94. package/dist/{seed-sprout-ZKCHFJKH.js → seed-sprout-GQLSK4EF.js} +14 -14
  95. package/dist/{send-LXUT2GGR.js → send-7CIP5GLS.js} +8 -8
  96. package/dist/{service-M6N3RUYU.js → service-YMHWPDXW.js} +6 -6
  97. package/dist/{setup-PJOF5UV5.js → setup-APNN7KJB.js} +32 -37
  98. package/dist/{setup-PMJHCZQX.js → setup-PF7JSFMO.js} +4 -4
  99. package/dist/{skill-TAAKEYBV.js → skill-ICN6Y2ZF.js} +6 -6
  100. package/dist/{skills-2PTRTBQP.js → skills-ZFVNN4TU.js} +7 -7
  101. package/dist/sleep-manager-PBOIEBJZ.js +34 -0
  102. package/dist/{spirit-6KVDIROQ.js → spirit-4QOYM33G.js} +9 -9
  103. package/dist/{split-AWVOYOPZ.js → split-5YBEQTBF.js} +3 -3
  104. package/dist/{sprout-WX2FFYLP.js → sprout-UDLZPMEO.js} +2 -2
  105. package/dist/{src-QEOLMAYC.js → src-LT6ZBYYX.js} +2 -2
  106. package/dist/{src-GW6FP6VL.js → src-O4PRLMKM.js} +1 -1
  107. package/dist/{src-FQE4BHRG.js → src-OYWRPLC6.js} +1 -1
  108. package/dist/{start-3UXOPXQG.js → start-LMXXRR3X.js} +5 -5
  109. package/dist/{status-3IVSLJDN.js → status-MC2P7DBG.js} +7 -7
  110. package/dist/{stop-3XYIBGFM.js → stop-TWDKVEUX.js} +5 -5
  111. package/dist/system-chat-7AIN3U5M.js +34 -0
  112. package/dist/{systems-O43WGQY6.js → systems-Y2WZV2K4.js} +7 -7
  113. package/dist/{tailscale-DZU4WM3E.js → tailscale-LTYNKIPZ.js} +3 -3
  114. package/dist/{template-hash-6ITI3WC4.js → template-hash-SSIBEEYK.js} +1 -1
  115. package/dist/up-4T32B7OB.js +19 -0
  116. package/dist/{update-RIQYUPVN.js → update-UOP2INF2.js} +7 -7
  117. package/dist/{update-check-4TIJKVGD.js → update-check-IKS7SGK5.js} +4 -4
  118. package/dist/{upgrade-ZMDGC7M2.js → upgrade-RXFZR5FI.js} +3 -3
  119. package/dist/{variant-QWL2WSRI.js → variant-HHDTW74J.js} +1 -1
  120. package/dist/{version-notify-UXSHBZ35.js → version-notify-NCRIN5QK.js} +18 -18
  121. package/dist/{volute-config-V7UFFBG3.js → volute-config-TS62GS6A.js} +1 -1
  122. package/dist/web-assets/assets/{index-C-eYso8Y.js → index-B3xLeex8.js} +16 -16
  123. package/dist/web-assets/assets/{index-CCv_fSte.css → index-Dr4A90Lo.css} +1 -1
  124. package/dist/web-assets/index.html +2 -2
  125. package/package.json +1 -1
  126. package/templates/_base/src/lib/context-breakdown.ts +22 -16
  127. package/dist/chunk-7KJOFUNN.js +0 -22
  128. package/dist/chunk-PZYJBOQP.js +0 -207
  129. package/dist/db-URORGSXQ.js +0 -9
  130. package/dist/delivery-manager-WTGIPBGY.js +0 -30
  131. package/dist/down-DGGLZ5TA.js +0 -17
  132. package/dist/exec-X3C6ZZTQ.js +0 -17
  133. package/dist/lib-DYEZMGW7.js +0 -6588
  134. package/dist/message-delivery-YORUXKDQ.js +0 -40
  135. package/dist/mind-manager-TJ2SUPRX.js +0 -30
  136. package/dist/mind-service-E7FM2WZF.js +0 -36
  137. package/dist/scheduler-FRJ5DK24.js +0 -30
  138. package/dist/sleep-manager-WAZWMFJT.js +0 -34
  139. package/dist/system-chat-2IFS5HCX.js +0 -34
  140. package/dist/up-4SCIUIMG.js +0 -19
@@ -0,0 +1,853 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findBridgeForChannel,
4
+ getBridgeConfig
5
+ } from "./chunk-LQ6Z4FXN.js";
6
+ import {
7
+ publish,
8
+ recordOutbound
9
+ } from "./chunk-RUIVWCYY.js";
10
+ import {
11
+ readEnv,
12
+ sharedEnvPath
13
+ } from "./chunk-PMMHVSCR.js";
14
+ import {
15
+ readVoluteConfig
16
+ } from "./chunk-CU6OFXMM.js";
17
+ import {
18
+ getOrCreateMindUser,
19
+ getUserByUsername
20
+ } from "./chunk-UIM5NHPP.js";
21
+ import {
22
+ addMessage,
23
+ findDMConversation,
24
+ getChannelByName,
25
+ getChannelName,
26
+ getConversation,
27
+ getParticipants
28
+ } from "./chunk-5DPRTREW.js";
29
+ import {
30
+ logger_default
31
+ } from "./chunk-T2TP6ZC6.js";
32
+ import {
33
+ buildVoluteSlug
34
+ } from "./chunk-LGNUFVMR.js";
35
+ import {
36
+ mindDir,
37
+ voluteSystemDir
38
+ } from "./chunk-2NHRJ3YO.js";
39
+ import {
40
+ __export
41
+ } from "./chunk-K3NQKI34.js";
42
+
43
+ // packages/platforms/src/drivers/discord.ts
44
+ var discord_exports = {};
45
+ __export(discord_exports, {
46
+ createConversation: () => createConversation,
47
+ listConversations: () => listConversations,
48
+ listUsers: () => listUsers,
49
+ read: () => read,
50
+ send: () => send
51
+ });
52
+
53
+ // packages/platforms/src/slugify.ts
54
+ function slugify(text) {
55
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
56
+ }
57
+
58
+ // packages/platforms/src/split-message.ts
59
+ function splitMessage(text, maxLength) {
60
+ const chunks = [];
61
+ while (text.length > maxLength) {
62
+ let splitAt = text.lastIndexOf("\n", maxLength);
63
+ if (splitAt < maxLength / 2) splitAt = maxLength;
64
+ chunks.push(text.slice(0, splitAt));
65
+ text = text.slice(splitAt).replace(/^\n/, "");
66
+ }
67
+ if (text) chunks.push(text);
68
+ return chunks;
69
+ }
70
+
71
+ // packages/platforms/src/drivers/discord.ts
72
+ var DISCORD_MAX_LENGTH = 2e3;
73
+ var API_BASE = "https://discord.com/api/v10";
74
+ function requireToken(env) {
75
+ const token = env.DISCORD_TOKEN;
76
+ if (!token) throw new Error("DISCORD_TOKEN not set");
77
+ return token;
78
+ }
79
+ async function discordGet(token, path) {
80
+ const res = await fetch(`${API_BASE}${path}`, {
81
+ headers: { Authorization: `Bot ${token}` }
82
+ });
83
+ if (!res.ok) {
84
+ throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
85
+ }
86
+ return res.json();
87
+ }
88
+ async function read(env, channelSlug, limit) {
89
+ const token = requireToken(env);
90
+ const channelId = resolvePlatformId(channelSlug);
91
+ const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
92
+ headers: { Authorization: `Bot ${token}` }
93
+ });
94
+ if (!res.ok) {
95
+ throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
96
+ }
97
+ const messages = await res.json();
98
+ return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
99
+ }
100
+ async function send(env, channelSlug, message, images) {
101
+ const token = requireToken(env);
102
+ const channelId = resolvePlatformId(channelSlug);
103
+ if (images?.length) {
104
+ for (let i = 0; i < images.length; i++) {
105
+ const img = images[i];
106
+ const ext = img.media_type.split("/")[1] || "png";
107
+ const form = new FormData();
108
+ const content = i === 0 ? message.slice(0, DISCORD_MAX_LENGTH) : "";
109
+ form.append("payload_json", JSON.stringify({ content }));
110
+ form.append(
111
+ "files[0]",
112
+ new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
113
+ `image.${ext}`
114
+ );
115
+ const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
116
+ method: "POST",
117
+ headers: { Authorization: `Bot ${token}` },
118
+ body: form
119
+ });
120
+ if (!res.ok) {
121
+ const body = await res.text().catch(() => "");
122
+ const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
123
+ throw new Error(`Discord API error: ${res.status} ${body || res.statusText}${partial}`);
124
+ }
125
+ }
126
+ return;
127
+ }
128
+ const chunks = splitMessage(message, DISCORD_MAX_LENGTH);
129
+ for (let i = 0; i < chunks.length; i++) {
130
+ const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
131
+ method: "POST",
132
+ headers: {
133
+ Authorization: `Bot ${token}`,
134
+ "Content-Type": "application/json"
135
+ },
136
+ body: JSON.stringify({ content: chunks[i] })
137
+ });
138
+ if (!res.ok) {
139
+ const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
140
+ throw new Error(`Discord API error: ${res.status} ${res.statusText}${partial}`);
141
+ }
142
+ }
143
+ }
144
+ async function listConversations(env) {
145
+ const token = requireToken(env);
146
+ const results = [];
147
+ const guilds = await discordGet(token, "/users/@me/guilds");
148
+ for (const guild of guilds) {
149
+ const channels = await discordGet(token, `/guilds/${guild.id}/channels`);
150
+ for (const ch of channels) {
151
+ if (ch.type !== 0) continue;
152
+ results.push({
153
+ id: `discord:${slugify(guild.name)}/${slugify(ch.name)}`,
154
+ platformId: ch.id,
155
+ name: `#${ch.name}`,
156
+ type: "channel"
157
+ });
158
+ }
159
+ }
160
+ const dms = await discordGet(token, "/users/@me/channels");
161
+ for (const dm of dms) {
162
+ const recipients = dm.recipients?.map((r) => r.username) ?? [];
163
+ const slug = recipients.length === 0 ? `discord:${dm.id}` : recipients.length === 1 ? `discord:@${slugify(recipients[0])}` : `discord:@${recipients.map(slugify).sort().join(",")}`;
164
+ results.push({
165
+ id: slug,
166
+ platformId: dm.id,
167
+ name: recipients.join(", ") || "DM",
168
+ type: dm.type === 1 ? "dm" : "channel"
169
+ });
170
+ }
171
+ return results;
172
+ }
173
+ async function listUsers(env) {
174
+ const token = requireToken(env);
175
+ const seen = /* @__PURE__ */ new Map();
176
+ const guilds = await discordGet(token, "/users/@me/guilds");
177
+ for (const guild of guilds) {
178
+ const members = await discordGet(token, `/guilds/${guild.id}/members?limit=1000`);
179
+ for (const m of members) {
180
+ if (!seen.has(m.user.id)) {
181
+ seen.set(m.user.id, {
182
+ id: m.user.id,
183
+ username: m.user.username,
184
+ type: m.user.bot ? "bot" : "human"
185
+ });
186
+ }
187
+ }
188
+ }
189
+ return [...seen.values()];
190
+ }
191
+ async function createConversation(env, participants, _name) {
192
+ const token = requireToken(env);
193
+ if (participants.length !== 1) {
194
+ throw new Error(
195
+ "Discord group creation not supported via bot \u2014 use threads in an existing channel"
196
+ );
197
+ }
198
+ const allUsers = await listUsers(env);
199
+ const target = allUsers.find((u) => u.username.toLowerCase() === participants[0].toLowerCase());
200
+ if (!target) {
201
+ throw new Error(`User not found: ${participants[0]}`);
202
+ }
203
+ const res = await fetch(`${API_BASE}/users/@me/channels`, {
204
+ method: "POST",
205
+ headers: {
206
+ Authorization: `Bot ${token}`,
207
+ "Content-Type": "application/json"
208
+ },
209
+ body: JSON.stringify({ recipient_id: target.id })
210
+ });
211
+ if (!res.ok) {
212
+ throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
213
+ }
214
+ const dmChannel = await res.json();
215
+ return `discord:${dmChannel.id}`;
216
+ }
217
+
218
+ // packages/platforms/src/drivers/slack.ts
219
+ var slack_exports = {};
220
+ __export(slack_exports, {
221
+ createConversation: () => createConversation2,
222
+ listConversations: () => listConversations2,
223
+ listUsers: () => listUsers2,
224
+ read: () => read2,
225
+ send: () => send2
226
+ });
227
+ var SLACK_MAX_LENGTH = 4e3;
228
+ var API_BASE2 = "https://slack.com/api";
229
+ function requireToken2(env) {
230
+ const token = env.SLACK_BOT_TOKEN;
231
+ if (!token) throw new Error("SLACK_BOT_TOKEN not set");
232
+ return token;
233
+ }
234
+ async function slackApi(token, method, body) {
235
+ const res = await fetch(`${API_BASE2}/${method}`, {
236
+ method: "POST",
237
+ headers: {
238
+ Authorization: `Bearer ${token}`,
239
+ "Content-Type": "application/json"
240
+ },
241
+ body: JSON.stringify(body)
242
+ });
243
+ if (!res.ok) {
244
+ throw new Error(`Slack API HTTP error: ${res.status} ${res.statusText}`);
245
+ }
246
+ const data = await res.json();
247
+ if (!data.ok) {
248
+ throw new Error(`Slack API error: ${data.error}`);
249
+ }
250
+ return data;
251
+ }
252
+ async function read2(env, channelSlug, limit) {
253
+ const token = requireToken2(env);
254
+ const channelId = resolvePlatformId(channelSlug);
255
+ const data = await slackApi(token, "conversations.history", {
256
+ channel: channelId,
257
+ limit
258
+ });
259
+ return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
260
+ }
261
+ async function send2(env, channelSlug, message, images) {
262
+ const token = requireToken2(env);
263
+ const channelId = resolvePlatformId(channelSlug);
264
+ if (images?.length) {
265
+ for (const img of images) {
266
+ const ext = img.media_type.split("/")[1] || "png";
267
+ const filename = `image.${ext}`;
268
+ const binary = Buffer.from(img.data, "base64");
269
+ const uploadData = await slackApi(token, "files.getUploadURLExternal", {
270
+ filename,
271
+ length: binary.length
272
+ });
273
+ const uploadRes = await fetch(uploadData.upload_url, {
274
+ method: "POST",
275
+ body: binary
276
+ });
277
+ if (!uploadRes.ok) {
278
+ throw new Error(`Slack file upload failed: ${uploadRes.status} ${uploadRes.statusText}`);
279
+ }
280
+ await slackApi(token, "files.completeUploadExternal", {
281
+ files: [{ id: uploadData.file_id }],
282
+ channel_id: channelId
283
+ });
284
+ }
285
+ if (message) {
286
+ const chunks2 = splitMessage(message, SLACK_MAX_LENGTH);
287
+ for (const chunk of chunks2) {
288
+ await slackApi(token, "chat.postMessage", {
289
+ channel: channelId,
290
+ text: chunk
291
+ });
292
+ }
293
+ }
294
+ return;
295
+ }
296
+ const chunks = splitMessage(message, SLACK_MAX_LENGTH);
297
+ for (let i = 0; i < chunks.length; i++) {
298
+ try {
299
+ await slackApi(token, "chat.postMessage", {
300
+ channel: channelId,
301
+ text: chunks[i]
302
+ });
303
+ } catch (err) {
304
+ const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
305
+ throw new Error(`${err instanceof Error ? err.message : err}${partial}`);
306
+ }
307
+ }
308
+ }
309
+ async function listConversations2(env) {
310
+ const token = requireToken2(env);
311
+ const authData = await slackApi(token, "auth.test", {});
312
+ const teamName = authData.team ?? "workspace";
313
+ const data = await slackApi(token, "conversations.list", {
314
+ types: "public_channel,private_channel,mpim,im",
315
+ limit: 1e3
316
+ });
317
+ const userMap = /* @__PURE__ */ new Map();
318
+ const imChannels = data.channels.filter((ch) => ch.is_im && ch.user);
319
+ if (imChannels.length > 0) {
320
+ const users = await listUsers2(env);
321
+ for (const u of users) {
322
+ userMap.set(u.id, u.username);
323
+ }
324
+ }
325
+ return data.channels.map((ch) => {
326
+ let type = "channel";
327
+ if (ch.is_im) type = "dm";
328
+ let slug;
329
+ let name;
330
+ if (ch.is_im && ch.user) {
331
+ const username = userMap.get(ch.user) ?? ch.user;
332
+ slug = `slack:@${slugify(username)}`;
333
+ name = username;
334
+ } else if (ch.name) {
335
+ slug = `slack:${slugify(teamName)}/${slugify(ch.name)}`;
336
+ name = ch.name;
337
+ } else {
338
+ slug = `slack:${ch.id}`;
339
+ name = ch.id;
340
+ }
341
+ return {
342
+ id: slug,
343
+ platformId: ch.id,
344
+ name,
345
+ type,
346
+ participantCount: ch.num_members
347
+ };
348
+ });
349
+ }
350
+ async function listUsers2(env) {
351
+ const token = requireToken2(env);
352
+ const data = await slackApi(token, "users.list", {});
353
+ return data.members.filter((m) => !m.deleted).map((m) => ({
354
+ id: m.id,
355
+ username: m.name,
356
+ type: m.is_bot ? "bot" : "human"
357
+ }));
358
+ }
359
+ async function createConversation2(env, participants, name) {
360
+ const token = requireToken2(env);
361
+ const allUsers = await listUsers2(env);
362
+ const ids = [];
363
+ for (const p of participants) {
364
+ const user = allUsers.find((u) => u.username.toLowerCase() === p.toLowerCase());
365
+ if (!user) throw new Error(`User not found: ${p}`);
366
+ ids.push(user.id);
367
+ }
368
+ if (name) {
369
+ const createData = await slackApi(token, "conversations.create", {
370
+ name,
371
+ is_private: true
372
+ });
373
+ const channelId = createData.channel.id;
374
+ for (const userId of ids) {
375
+ await slackApi(token, "conversations.invite", {
376
+ channel: channelId,
377
+ users: userId
378
+ });
379
+ }
380
+ const authData = await slackApi(token, "auth.test", {});
381
+ const teamName = authData.team ?? "workspace";
382
+ const slug = `slack:${slugify(teamName)}/${slugify(name)}`;
383
+ return slug;
384
+ }
385
+ const openData = await slackApi(token, "conversations.open", { users: ids.join(",") });
386
+ return `slack:${openData.channel.id}`;
387
+ }
388
+
389
+ // packages/platforms/src/drivers/telegram.ts
390
+ var telegram_exports = {};
391
+ __export(telegram_exports, {
392
+ createConversation: () => createConversation3,
393
+ listConversations: () => listConversations3,
394
+ listUsers: () => listUsers3,
395
+ read: () => read3,
396
+ send: () => send3
397
+ });
398
+ var TELEGRAM_MAX_LENGTH = 4096;
399
+ var API_BASE3 = "https://api.telegram.org";
400
+ function requireToken3(env) {
401
+ const token = env.TELEGRAM_BOT_TOKEN;
402
+ if (!token) throw new Error("TELEGRAM_BOT_TOKEN not set");
403
+ return token;
404
+ }
405
+ async function read3(_env, _channelSlug, _limit) {
406
+ throw new Error(
407
+ "Telegram Bot API does not support reading chat history. Use volute chat send instead."
408
+ );
409
+ }
410
+ async function send3(env, channelSlug, message, images) {
411
+ const token = requireToken3(env);
412
+ const chatId = resolvePlatformId(channelSlug);
413
+ if (images?.length) {
414
+ const CAPTION_MAX = 1024;
415
+ for (let i = 0; i < images.length; i++) {
416
+ const img = images[i];
417
+ const ext = img.media_type.split("/")[1] || "png";
418
+ const form = new FormData();
419
+ form.append("chat_id", chatId);
420
+ form.append(
421
+ "photo",
422
+ new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
423
+ `image.${ext}`
424
+ );
425
+ if (i === 0 && message) {
426
+ form.append("caption", message.slice(0, CAPTION_MAX));
427
+ }
428
+ const res = await fetch(`${API_BASE3}/bot${token}/sendPhoto`, {
429
+ method: "POST",
430
+ body: form
431
+ });
432
+ if (!res.ok) {
433
+ const body = await res.text().catch(() => "");
434
+ const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
435
+ throw new Error(`Telegram API error: ${res.status} ${body}${partial}`);
436
+ }
437
+ }
438
+ if (message && message.length > CAPTION_MAX) {
439
+ const remaining = message.slice(CAPTION_MAX);
440
+ const chunks2 = splitMessage(remaining, TELEGRAM_MAX_LENGTH);
441
+ for (const chunk of chunks2) {
442
+ const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
443
+ method: "POST",
444
+ headers: { "Content-Type": "application/json" },
445
+ body: JSON.stringify({ chat_id: chatId, text: chunk })
446
+ });
447
+ if (!res.ok) {
448
+ const body = await res.text().catch(() => "");
449
+ throw new Error(`Telegram API error: ${res.status} ${body}`);
450
+ }
451
+ }
452
+ }
453
+ return;
454
+ }
455
+ const chunks = splitMessage(message, TELEGRAM_MAX_LENGTH);
456
+ for (let i = 0; i < chunks.length; i++) {
457
+ const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
458
+ method: "POST",
459
+ headers: { "Content-Type": "application/json" },
460
+ body: JSON.stringify({ chat_id: chatId, text: chunks[i] })
461
+ });
462
+ if (!res.ok) {
463
+ const body = await res.text().catch(() => "");
464
+ const partial = i > 0 ? ` (${i}/${chunks.length} chunks were already sent)` : "";
465
+ throw new Error(`Telegram API error: ${res.status} ${body}${partial}`);
466
+ }
467
+ }
468
+ }
469
+ async function listConversations3() {
470
+ throw new Error(
471
+ "Telegram Bot API does not support listing conversations. Users must message the bot first."
472
+ );
473
+ }
474
+ async function listUsers3() {
475
+ throw new Error(
476
+ "Telegram Bot API does not support listing users. Users must message the bot first."
477
+ );
478
+ }
479
+ async function createConversation3() {
480
+ throw new Error(
481
+ "Telegram Bot API does not support creating conversations. Users must message the bot first."
482
+ );
483
+ }
484
+
485
+ // packages/platforms/src/index.ts
486
+ var PLATFORMS = {
487
+ discord: {
488
+ name: "discord",
489
+ displayName: "Discord",
490
+ driver: discord_exports
491
+ },
492
+ slack: {
493
+ name: "slack",
494
+ displayName: "Slack",
495
+ driver: slack_exports
496
+ },
497
+ telegram: {
498
+ name: "telegram",
499
+ displayName: "Telegram",
500
+ driver: telegram_exports
501
+ },
502
+ volute: { name: "volute", displayName: "Volute" },
503
+ mail: { name: "mail", displayName: "Email" },
504
+ system: { name: "system", displayName: "System" }
505
+ };
506
+ function registerPlatform(name, platform) {
507
+ PLATFORMS[name] = platform;
508
+ }
509
+ function getPlatformDriver(platform) {
510
+ return PLATFORMS[platform]?.driver ?? null;
511
+ }
512
+ function resolvePlatformId(slug) {
513
+ const colonIdx = slug.indexOf(":");
514
+ return colonIdx !== -1 ? slug.slice(colonIdx + 1) : slug;
515
+ }
516
+
517
+ // packages/daemon/src/lib/platforms/volute.ts
518
+ var volute_exports = {};
519
+ __export(volute_exports, {
520
+ createConversation: () => createConversation4,
521
+ listConversations: () => listConversations4,
522
+ listUsers: () => listUsers4,
523
+ read: () => read4,
524
+ send: () => send4
525
+ });
526
+ import { existsSync, readFileSync } from "fs";
527
+ import { resolve } from "path";
528
+ function readSessionFile(mindDir2) {
529
+ try {
530
+ const p = resolve(mindDir2, ".mind", "current-session");
531
+ if (existsSync(p)) return readFileSync(p, "utf-8").trim() || void 0;
532
+ } catch (err) {
533
+ const code = err.code;
534
+ if (code !== "ENOENT") {
535
+ console.error(`[volute] failed to read session file: ${code ?? err}`);
536
+ }
537
+ }
538
+ return void 0;
539
+ }
540
+ function getDaemonConfig() {
541
+ const configPath = resolve(voluteSystemDir(), "daemon.json");
542
+ if (!existsSync(configPath)) {
543
+ throw new Error("Volute daemon is not running");
544
+ }
545
+ let config;
546
+ try {
547
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
548
+ } catch (err) {
549
+ throw new Error(`Failed to parse ${configPath}: ${err}`);
550
+ }
551
+ if (typeof config.port !== "number") {
552
+ throw new Error(`Invalid or missing port in ${configPath}`);
553
+ }
554
+ const url = new URL("http://localhost");
555
+ url.hostname = config.hostname || "localhost";
556
+ url.port = String(config.port);
557
+ return { url: url.origin, token: config.token };
558
+ }
559
+ async function read4(env, channelSlug, limit) {
560
+ const mindName = env.VOLUTE_MIND;
561
+ if (!mindName) throw new Error("VOLUTE_MIND not set");
562
+ const conversationId = resolvePlatformId(channelSlug);
563
+ const { url, token } = getDaemonConfig();
564
+ const headers = { Origin: url };
565
+ if (token) headers.Authorization = `Bearer ${token}`;
566
+ const res = await fetch(
567
+ `${url}/api/minds/${encodeURIComponent(mindName)}/conversations/${encodeURIComponent(conversationId)}/messages`,
568
+ { headers }
569
+ );
570
+ if (!res.ok) {
571
+ throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
572
+ }
573
+ const data = await res.json();
574
+ if (!Array.isArray(data.items)) {
575
+ throw new Error("Unexpected response format when reading conversation messages");
576
+ }
577
+ return data.items.slice(-limit).map((m) => {
578
+ const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
579
+ return `${m.sender_name ?? m.role}: ${text}`;
580
+ }).join("\n");
581
+ }
582
+ async function send4(env, channelSlug, message, images) {
583
+ const mindName = env.VOLUTE_MIND;
584
+ if (!mindName) throw new Error("VOLUTE_MIND not set");
585
+ const conversationId = resolvePlatformId(channelSlug);
586
+ const { url, token } = getDaemonConfig();
587
+ const headers = {
588
+ "Content-Type": "application/json",
589
+ Origin: url
590
+ };
591
+ if (token) headers.Authorization = `Bearer ${token}`;
592
+ const voluteSession = env.VOLUTE_SESSION || (env.VOLUTE_MIND_DIR ? readSessionFile(env.VOLUTE_MIND_DIR) : void 0);
593
+ if (voluteSession) headers["X-Volute-Session"] = voluteSession;
594
+ const res = await fetch(`${url}/api/v1/chat`, {
595
+ method: "POST",
596
+ headers,
597
+ body: JSON.stringify({
598
+ message,
599
+ conversationId,
600
+ sender: env.VOLUTE_SENDER ?? mindName,
601
+ images,
602
+ targetMind: mindName
603
+ })
604
+ });
605
+ if (!res.ok) {
606
+ const data = await res.json().catch(() => ({}));
607
+ throw new Error(data.error ?? `Failed to send: ${res.status}`);
608
+ }
609
+ }
610
+ async function listConversations4(env) {
611
+ const mindName = env.VOLUTE_MIND;
612
+ if (!mindName) throw new Error("VOLUTE_MIND not set");
613
+ const { url, token } = getDaemonConfig();
614
+ const headers = { Origin: url };
615
+ if (token) headers.Authorization = `Bearer ${token}`;
616
+ const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/conversations`, {
617
+ headers
618
+ });
619
+ if (!res.ok) {
620
+ throw new Error(`Failed to list conversations: ${res.status} ${res.statusText}`);
621
+ }
622
+ const convs = await res.json();
623
+ const results = [];
624
+ for (const conv of convs) {
625
+ const participants = conv.participants ?? [];
626
+ const slug = buildVoluteSlug({
627
+ participants,
628
+ mindUsername: mindName,
629
+ conversationId: conv.id,
630
+ convType: conv.type,
631
+ convName: conv.channel_name
632
+ });
633
+ const convType = conv.type === "channel" ? "channel" : "dm";
634
+ const other = participants.find((p) => p.username !== mindName);
635
+ const displayName = conv.type === "channel" && conv.channel_name ? `#${conv.channel_name}` : other ? `@${other.username}` : "(untitled)";
636
+ results.push({
637
+ id: slug,
638
+ platformId: conv.id,
639
+ name: displayName,
640
+ type: convType,
641
+ participantCount: participants.length
642
+ });
643
+ }
644
+ return results;
645
+ }
646
+ async function listUsers4(_env) {
647
+ const { url, token } = getDaemonConfig();
648
+ const headers = { Origin: url };
649
+ if (token) headers.Authorization = `Bearer ${token}`;
650
+ const res = await fetch(`${url}/api/auth/users`, { headers });
651
+ if (!res.ok) {
652
+ throw new Error(`Failed to list users: ${res.status} ${res.statusText}`);
653
+ }
654
+ const data = await res.json();
655
+ return data.map((u) => ({
656
+ id: String(u.id),
657
+ username: u.username,
658
+ type: u.user_type
659
+ }));
660
+ }
661
+ async function createConversation4(env, participants, name) {
662
+ const mindName = env.VOLUTE_MIND;
663
+ if (!mindName) throw new Error("VOLUTE_MIND not set");
664
+ const { url, token } = getDaemonConfig();
665
+ const headers = {
666
+ "Content-Type": "application/json",
667
+ Origin: url
668
+ };
669
+ if (token) headers.Authorization = `Bearer ${token}`;
670
+ const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/conversations`, {
671
+ method: "POST",
672
+ headers,
673
+ body: JSON.stringify({ participantNames: participants })
674
+ });
675
+ if (!res.ok) {
676
+ const data = await res.json().catch(() => ({}));
677
+ throw new Error(data.error ?? `Failed to create conversation: ${res.status}`);
678
+ }
679
+ const conv = await res.json();
680
+ return conv.id;
681
+ }
682
+
683
+ // packages/daemon/src/lib/platforms.ts
684
+ registerPlatform("volute", {
685
+ name: "volute",
686
+ displayName: "Volute",
687
+ builtIn: true,
688
+ driver: volute_exports
689
+ });
690
+
691
+ // packages/daemon/src/lib/bridges/bridge-outbound.ts
692
+ function extractContent(contentBlocks) {
693
+ const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
694
+ const images = contentBlocks.filter((b) => b.type === "image").map((b) => ({ media_type: b.media_type, data: b.data }));
695
+ return { text, images };
696
+ }
697
+ async function routeOutboundBridge(conversationId, senderName, contentBlocks) {
698
+ try {
699
+ const conv = await getConversation(conversationId);
700
+ if (!conv) return;
701
+ if (conv.type === "channel") {
702
+ const channelName = await getChannelName(conversationId);
703
+ if (channelName) {
704
+ await routeChannelOutbound(channelName, senderName, contentBlocks);
705
+ } else {
706
+ logger_default.warn(
707
+ `channel conversation ${conversationId} has no channel name \u2014 skipping bridge outbound`
708
+ );
709
+ }
710
+ } else if (conv.type === "dm") {
711
+ await routeDMOutbound(conversationId, senderName, contentBlocks);
712
+ }
713
+ } catch (err) {
714
+ logger_default.error(`bridge outbound failed for conversation ${conversationId}`, logger_default.errorData(err));
715
+ }
716
+ }
717
+ async function routeChannelOutbound(channelName, senderName, contentBlocks) {
718
+ const bridgeInfo = findBridgeForChannel(channelName);
719
+ if (!bridgeInfo) return;
720
+ const driver = getPlatformDriver(bridgeInfo.platform);
721
+ if (!driver) {
722
+ logger_default.warn(`no channel driver for bridge platform: ${bridgeInfo.platform}`);
723
+ return;
724
+ }
725
+ const { text, images } = extractContent(contentBlocks);
726
+ if (!text) return;
727
+ const env = readEnv(sharedEnvPath());
728
+ env.VOLUTE_SENDER = senderName;
729
+ await driver.send(env, bridgeInfo.externalChannel, text, images.length > 0 ? images : void 0);
730
+ logger_default.debug(`bridge outbound: sent to ${bridgeInfo.platform}:${bridgeInfo.externalChannel}`);
731
+ }
732
+ async function routeDMOutbound(conversationId, senderName, contentBlocks) {
733
+ const participants = await getParticipants(conversationId);
734
+ const puppets = participants.filter((p) => p.userType === "puppet");
735
+ if (puppets.length === 0) return;
736
+ const { text, images } = extractContent(contentBlocks);
737
+ if (!text) return;
738
+ for (const puppet of puppets) {
739
+ const colonIdx = puppet.username.indexOf(":");
740
+ if (colonIdx === -1) {
741
+ logger_default.warn(
742
+ `puppet user ${puppet.username} has malformed username (expected platform:id format)`
743
+ );
744
+ continue;
745
+ }
746
+ const platform = puppet.username.slice(0, colonIdx);
747
+ const externalUserId = puppet.username.slice(colonIdx + 1);
748
+ const bridgeConfig = getBridgeConfig(platform);
749
+ if (!bridgeConfig?.enabled) continue;
750
+ const driver = getPlatformDriver(platform);
751
+ if (!driver?.createConversation) {
752
+ logger_default.warn(`no channel driver with DM support for bridge platform: ${platform}`);
753
+ continue;
754
+ }
755
+ try {
756
+ const env = readEnv(sharedEnvPath());
757
+ env.VOLUTE_SENDER = senderName;
758
+ env.VOLUTE_MIND = senderName;
759
+ env.VOLUTE_MIND_DIR = mindDir(senderName);
760
+ const slug = await driver.createConversation(env, [externalUserId]);
761
+ await driver.send(env, slug, text, images.length > 0 ? images : void 0);
762
+ logger_default.debug(`bridge outbound DM: sent to ${platform}:${externalUserId}`);
763
+ } catch (err) {
764
+ logger_default.error(`bridge outbound DM failed for puppet ${puppet.username}`, logger_default.errorData(err));
765
+ }
766
+ }
767
+ }
768
+
769
+ // packages/daemon/src/lib/delivery/echo-text.ts
770
+ var dlog = logger_default.child("echo-text");
771
+ var echoTextCache = /* @__PURE__ */ new Map();
772
+ var channelConvCache = /* @__PURE__ */ new Map();
773
+ function clearEchoTextCache(mind) {
774
+ if (mind) {
775
+ echoTextCache.delete(mind);
776
+ for (const key of channelConvCache.keys()) {
777
+ if (key.startsWith(`${mind}:`)) channelConvCache.delete(key);
778
+ }
779
+ } else {
780
+ echoTextCache.clear();
781
+ channelConvCache.clear();
782
+ }
783
+ }
784
+ function isEchoEnabled(mind) {
785
+ const cached = echoTextCache.get(mind);
786
+ if (cached !== void 0) return cached;
787
+ const config = readVoluteConfig(mindDir(mind));
788
+ const enabled = config?.echoText === true;
789
+ echoTextCache.set(mind, enabled);
790
+ return enabled;
791
+ }
792
+ async function resolveConversationId(mind, channel) {
793
+ const cacheKey = `${mind}:${channel}`;
794
+ const cached = channelConvCache.get(cacheKey);
795
+ if (cached !== void 0) return cached;
796
+ let conversationId = null;
797
+ if (channel.startsWith("#")) {
798
+ const conv = await getChannelByName(channel.slice(1));
799
+ if (conv) conversationId = conv.id;
800
+ } else if (channel.startsWith("@")) {
801
+ const otherUsername = channel.slice(1);
802
+ const [mindUser, otherUser] = await Promise.all([
803
+ getOrCreateMindUser(mind),
804
+ getUserByUsername(otherUsername)
805
+ ]);
806
+ if (otherUser) {
807
+ conversationId = await findDMConversation([mindUser.id, otherUser.id]);
808
+ }
809
+ }
810
+ if (conversationId) {
811
+ channelConvCache.set(cacheKey, conversationId);
812
+ }
813
+ return conversationId;
814
+ }
815
+ async function echoTextToChannel(mind, channel, text, turnId, textEventId) {
816
+ if (!isEchoEnabled(mind)) return void 0;
817
+ if (!text.trim()) return void 0;
818
+ const conversationId = await resolveConversationId(mind, channel);
819
+ if (!conversationId) {
820
+ dlog.debug(`echo-text: could not resolve channel "${channel}" to conversation`);
821
+ return void 0;
822
+ }
823
+ const contentBlocks = [{ type: "text", text }];
824
+ const message = await addMessage(conversationId, "user", mind, contentBlocks, {
825
+ turnId,
826
+ sourceEventId: textEventId
827
+ });
828
+ routeOutboundBridge(conversationId, mind, contentBlocks).catch((err) => {
829
+ dlog.warn(`echo-text: bridge routing failed for ${mind} on ${channel}`, logger_default.errorData(err));
830
+ });
831
+ const outboundId = await recordOutbound(mind, channel, text, {
832
+ messageId: message != null ? String(message.id) : void 0,
833
+ turnId
834
+ });
835
+ if (outboundId != null) {
836
+ publish(mind, {
837
+ mind,
838
+ type: "outbound",
839
+ channel,
840
+ content: text,
841
+ turnId
842
+ });
843
+ }
844
+ return outboundId;
845
+ }
846
+
847
+ export {
848
+ PLATFORMS,
849
+ getPlatformDriver,
850
+ routeOutboundBridge,
851
+ clearEchoTextCache,
852
+ echoTextToChannel
853
+ };