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.
- package/dist/{accept-ZBDVVCEU.js → accept-AHAOUFBK.js} +4 -4
- package/dist/{activity-events-PWOGSMRL.js → activity-events-N6HCHU4P.js} +4 -4
- package/dist/{ai-service-GSZWIETO.js → ai-service-C2YNARGH.js} +5 -5
- package/dist/{api-client-3A77HMH7.js → api-client-LC5YRA32.js} +1 -1
- package/dist/{archive-Y2YEOCGB.js → archive-AWIJTVQV.js} +4 -4
- package/dist/{auth-YTQME4EV.js → auth-2QOOPMBX.js} +5 -5
- package/dist/{bridge-PXIO6PS2.js → bridge-F3ZJEKDN.js} +4 -4
- package/dist/{chat-ED7YOGKO.js → chat-5Y4FD77E.js} +9 -9
- package/dist/{chunk-75AJ54GM.js → chunk-2NHRJ3YO.js} +1 -1
- package/dist/{chunk-PJ4IPTIN.js → chunk-3F7XK5Q7.js} +1 -1
- package/dist/{chunk-6F3YNULE.js → chunk-4L2Q7IP5.js} +16 -16
- package/dist/{chunk-IIWF2IPD.js → chunk-5DPRTREW.js} +3 -3
- package/dist/{chunk-X2J7QUFH.js → chunk-7AZQFSOV.js} +1 -1
- package/dist/{chunk-NUX47Y2V.js → chunk-A6FLW5XD.js} +1 -1
- package/dist/{chunk-SWW6AUVW.js → chunk-BIEWHAAM.js} +1 -1
- package/dist/{chunk-TWAN7ZNO.js → chunk-CF4SO5L3.js} +3 -3
- package/dist/{chunk-7PTQGPJY.js → chunk-CJ26DXZL.js} +1 -1
- package/dist/{chunk-PY557GDR.js → chunk-GVVVMZ4J.js} +1 -1
- package/dist/chunk-K3NQKI34.js +10 -0
- package/dist/{chunk-BOLJUV77.js → chunk-KXXJYY62.js} +4 -4
- package/dist/chunk-LIIX2MOM.js +853 -0
- package/dist/chunk-LQ6Z4FXN.js +87 -0
- package/dist/{chunk-N2AUHW4C.js → chunk-MQRS4J24.js} +2 -2
- package/dist/{chunk-B35VNNSS.js → chunk-N42QMDID.js} +2 -2
- package/dist/{chunk-M5RYAA5I.js → chunk-ORNY3MZR.js} +4 -4
- package/dist/{chunk-YDBAY3NA.js → chunk-PMMHVSCR.js} +1 -1
- package/dist/{chunk-GBDVNPN2.js → chunk-QJGLTPAP.js} +8 -8
- package/dist/{chunk-DQ7VBXAP.js → chunk-RUIVWCYY.js} +122 -81
- package/dist/{chunk-L72WYMF7.js → chunk-SNW2NPP4.js} +1 -1
- package/dist/chunk-SZJWC2GA.js +125 -0
- package/dist/{chunk-DJT5Y4UF.js → chunk-TNZ5XQA4.js} +2 -2
- package/dist/{chunk-DMV5P2LU.js → chunk-UIM5NHPP.js} +3 -3
- package/dist/{chunk-ZTVKQOU7.js → chunk-WC635OPK.js} +1 -1
- package/dist/{chunk-YTWZORJN.js → chunk-XLBQYIHH.js} +1 -1
- package/dist/cli.js +24 -24
- package/dist/{clock-HSEKS5AR.js → clock-BMLJ2TR6.js} +8 -8
- package/dist/{cloud-sync-BOCZSDIA.js → cloud-sync-EZKCEIBX.js} +18 -18
- package/dist/{config-UTS7QULS.js → config-QB7W3Z7P.js} +4 -4
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-HH3CJD4E.js → conversations-G6YRSABR.js} +5 -5
- package/dist/{create-5BPOOJAN.js → create-C3BBFYV7.js} +4 -4
- package/dist/{create-QBEPSD2Z.js → create-PN73742N.js} +4 -4
- package/dist/{daemon-client-RVIKXGFQ.js → daemon-client-2MIPKY3E.js} +1 -1
- package/dist/{daemon-restart-SIR3UR4B.js → daemon-restart-S6SOD3C5.js} +11 -11
- package/dist/daemon.js +392 -1101
- package/dist/db-CBOCDYVA.js +9 -0
- package/dist/{db-BDMH4SZ2.js → db-IJL6B26S.js} +1 -1
- package/dist/{delete-L5PAVDGQ.js → delete-NLXES2C7.js} +3 -3
- package/dist/delivery-manager-3I7CA734.js +30 -0
- package/dist/{delivery-router-VSULHXNH.js → delivery-router-QTFEZ26O.js} +4 -4
- package/dist/down-25L2RKCQ.js +17 -0
- package/dist/echo-text-IWAQKNTC.js +31 -0
- package/dist/{env-E4XHO2BI.js → env-IQ6Q2333.js} +6 -6
- package/dist/exec-ONYZEA5B.js +17 -0
- package/dist/{export-HTFOHOKL.js → export-JPDBQESV.js} +6 -6
- package/dist/{extension-AKZ46YSL.js → extension-LZYHBNLV.js} +4 -4
- package/dist/{extensions-OOSFVH7U.js → extensions-PCOXTHNM.js} +17 -17
- package/dist/{files-H2YLRD37.js → files-LAQ3NXQK.js} +7 -7
- package/dist/{import-OL5BZX7S.js → import-EROF27RH.js} +11 -10
- package/dist/{isolation-N74RWOUX.js → isolation-G5J3MTKU.js} +4 -4
- package/dist/{join-DF5XSJAC.js → join-6SZCA5FX.js} +3 -3
- package/dist/{list-GJ4RUQQT.js → list-KHJZJPEJ.js} +4 -4
- package/dist/{login-JXRVMBRB.js → login-F6YMAVLE.js} +6 -6
- package/dist/{login-GOTAYLXP.js → login-GYTH67ES.js} +4 -4
- package/dist/{logout-FW243JBU.js → logout-HHPH52KZ.js} +6 -6
- package/dist/{logout-6KIA74EV.js → logout-YHQLOFLR.js} +4 -4
- package/dist/message-delivery-NWL7XEIX.js +40 -0
- package/dist/{mind-6VJJHF65.js → mind-MM2IYMJ3.js} +19 -19
- package/dist/{mind-activity-tracker-66UVYIFW.js → mind-activity-tracker-42ENM32S.js} +5 -5
- package/dist/{mind-history-MII2SK7F.js → mind-history-WHCNZ6I5.js} +5 -5
- package/dist/{mind-list-GEWHWAL4.js → mind-list-H3HC2ZRG.js} +4 -4
- package/dist/mind-manager-VOEQ2IZL.js +30 -0
- package/dist/{mind-profile-DCBDVF5B.js → mind-profile-7VYRJGFZ.js} +2 -2
- package/dist/mind-service-WRTOQSAL.js +36 -0
- package/dist/{mind-sleep-ITCF6OQA.js → mind-sleep-ZL5ZXFTM.js} +4 -4
- package/dist/{mind-status-X4SX3YUG.js → mind-status-ZWULKOUO.js} +4 -4
- package/dist/{mind-wake-KXMKMGWX.js → mind-wake-HK5ORGUK.js} +4 -4
- package/dist/{package-3W2MEXHB.js → package-TA6IHIED.js} +2 -2
- package/dist/{read-ZUDG4JWU.js → read-CP7MYMJQ.js} +4 -4
- package/dist/{read-stdin-3X5VYKNS.js → read-stdin-4B5UYPPM.js} +1 -1
- package/dist/{register-SB7NXCOE.js → register-XOBFEMI4.js} +4 -4
- package/dist/{registry-YPHK534W.js → registry-KMELPC3X.js} +3 -3
- package/dist/{reject-MUR2KWJ4.js → reject-43AGXB6B.js} +4 -4
- package/dist/{restart-5EGG4JXU.js → restart-O5QIYQJT.js} +5 -5
- package/dist/{sandbox-LP6YRAXS.js → sandbox-PQYEICEF.js} +5 -5
- package/dist/scheduler-355E746X.js +30 -0
- package/dist/{schema-MISD3JFG.js → schema-K575EBPE.js} +2 -2
- package/dist/{seed-CEC4RC23.js → seed-6S4Z6TAM.js} +2 -2
- package/dist/{seed-check-KJNTL72M.js → seed-check-HZPVFJKZ.js} +2 -2
- package/dist/{seed-cmd-WTTG7SRQ.js → seed-cmd-CBWLJWYD.js} +4 -4
- package/dist/{seed-create-M6RCC6RP.js → seed-create-NT6DG4SE.js} +6 -6
- package/dist/{seed-sprout-ZKCHFJKH.js → seed-sprout-GQLSK4EF.js} +14 -14
- package/dist/{send-LXUT2GGR.js → send-7CIP5GLS.js} +8 -8
- package/dist/{service-M6N3RUYU.js → service-YMHWPDXW.js} +6 -6
- package/dist/{setup-PJOF5UV5.js → setup-APNN7KJB.js} +32 -37
- package/dist/{setup-PMJHCZQX.js → setup-PF7JSFMO.js} +4 -4
- package/dist/{skill-TAAKEYBV.js → skill-ICN6Y2ZF.js} +6 -6
- package/dist/{skills-2PTRTBQP.js → skills-ZFVNN4TU.js} +7 -7
- package/dist/sleep-manager-PBOIEBJZ.js +34 -0
- package/dist/{spirit-6KVDIROQ.js → spirit-4QOYM33G.js} +9 -9
- package/dist/{split-AWVOYOPZ.js → split-5YBEQTBF.js} +3 -3
- package/dist/{sprout-WX2FFYLP.js → sprout-UDLZPMEO.js} +2 -2
- package/dist/{src-QEOLMAYC.js → src-LT6ZBYYX.js} +2 -2
- package/dist/{src-GW6FP6VL.js → src-O4PRLMKM.js} +1 -1
- package/dist/{src-FQE4BHRG.js → src-OYWRPLC6.js} +1 -1
- package/dist/{start-3UXOPXQG.js → start-LMXXRR3X.js} +5 -5
- package/dist/{status-3IVSLJDN.js → status-MC2P7DBG.js} +7 -7
- package/dist/{stop-3XYIBGFM.js → stop-TWDKVEUX.js} +5 -5
- package/dist/system-chat-7AIN3U5M.js +34 -0
- package/dist/{systems-O43WGQY6.js → systems-Y2WZV2K4.js} +7 -7
- package/dist/{tailscale-DZU4WM3E.js → tailscale-LTYNKIPZ.js} +3 -3
- package/dist/{template-hash-6ITI3WC4.js → template-hash-SSIBEEYK.js} +1 -1
- package/dist/up-4T32B7OB.js +19 -0
- package/dist/{update-RIQYUPVN.js → update-UOP2INF2.js} +7 -7
- package/dist/{update-check-4TIJKVGD.js → update-check-IKS7SGK5.js} +4 -4
- package/dist/{upgrade-ZMDGC7M2.js → upgrade-RXFZR5FI.js} +3 -3
- package/dist/{variant-QWL2WSRI.js → variant-HHDTW74J.js} +1 -1
- package/dist/{version-notify-UXSHBZ35.js → version-notify-NCRIN5QK.js} +18 -18
- package/dist/{volute-config-V7UFFBG3.js → volute-config-TS62GS6A.js} +1 -1
- package/dist/web-assets/assets/{index-C-eYso8Y.js → index-B3xLeex8.js} +16 -16
- package/dist/web-assets/assets/{index-CCv_fSte.css → index-Dr4A90Lo.css} +1 -1
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/templates/_base/src/lib/context-breakdown.ts +22 -16
- package/dist/chunk-7KJOFUNN.js +0 -22
- package/dist/chunk-PZYJBOQP.js +0 -207
- package/dist/db-URORGSXQ.js +0 -9
- package/dist/delivery-manager-WTGIPBGY.js +0 -30
- package/dist/down-DGGLZ5TA.js +0 -17
- package/dist/exec-X3C6ZZTQ.js +0 -17
- package/dist/lib-DYEZMGW7.js +0 -6588
- package/dist/message-delivery-YORUXKDQ.js +0 -40
- package/dist/mind-manager-TJ2SUPRX.js +0 -30
- package/dist/mind-service-E7FM2WZF.js +0 -36
- package/dist/scheduler-FRJ5DK24.js +0 -30
- package/dist/sleep-manager-WAZWMFJT.js +0 -34
- package/dist/system-chat-2IFS5HCX.js +0 -34
- 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
|
+
};
|