volute 0.20.0 → 0.21.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 +7 -7
- package/dist/{activity-events-OMXKXD5N.js → activity-events-3WHHCOBB.js} +3 -4
- package/dist/{archive-ZCFOSTKB.js → archive-4ZQYK5MN.js} +4 -2
- package/dist/auth-HM2RSPY7.js +37 -0
- package/dist/{channel-PUQKGSQM.js → channel-BOOMFULW.js} +2 -2
- package/dist/{chunk-IKMY5X76.js → chunk-5462YKWP.js} +12 -9
- package/dist/{chunk-PUVXOZ6T.js → chunk-7LPTHFIL.js} +63 -64
- package/dist/{chunk-UU7A7KLB.js → chunk-A4S7H6G6.js} +5 -7
- package/dist/chunk-AKPFNL7L.js +148 -0
- package/dist/{chunk-EBGCNDMM.js → chunk-B2CPS4QU.js} +128 -114
- package/dist/{chunk-FCDU5BFX.js → chunk-HFCBO2GL.js} +2 -2
- package/dist/{chunk-GZ7DW4YL.js → chunk-HGCDWKSP.js} +2 -2
- package/dist/{chunk-DYZGP3EW.js → chunk-IPJXU366.js} +1 -1
- package/dist/{chunk-7UFKREVW.js → chunk-J5A3DF2U.js} +2 -2
- package/dist/{chunk-WC6ZHVRL.js → chunk-KFI7TQJ6.js} +2 -2
- package/dist/{chunk-AW7P4EVV.js → chunk-KTJGZ7M7.js} +55 -7
- package/dist/{chunk-TIWH32HP.js → chunk-L3LHXZD7.js} +3 -3
- package/dist/{chunk-OGXOMR65.js → chunk-NWPT4ASZ.js} +1 -1
- package/dist/{chunk-FGSYHIS3.js → chunk-OGZYB5GL.js} +252 -296
- package/dist/{chunk-SCUDS4US.js → chunk-ON3FF5JA.js} +1 -1
- package/dist/{chunk-O6ASDHFO.js → chunk-PC6R6UUW.js} +4 -4
- package/dist/{chunk-VDWCHYTS.js → chunk-PHU4DEAJ.js} +1 -1
- package/dist/{chunk-7NO7EV5Z.js → chunk-Q7AITQ44.js} +2 -2
- package/dist/{chunk-32VR2EOH.js → chunk-QUJUKM4U.js} +2 -2
- package/dist/{chunk-NSE7VJQA.js → chunk-SGPEZ32F.js} +29 -1
- package/dist/{chunk-RHEGSQFJ.js → chunk-WSLPZF72.js} +1 -1
- package/dist/cli.js +57 -119
- package/dist/{connector-JBVNZ7VK.js → connector-PYT5UOTZ.js} +6 -6
- package/dist/connectors/discord.js +2 -2
- package/dist/connectors/slack.js +2 -2
- package/dist/connectors/telegram.js +2 -2
- package/dist/{create-HP4OVVHF.js → create-WIDA3M4C.js} +1 -1
- package/dist/{daemon-client-ITWUCNFO.js → daemon-client-ZHCDL4RS.js} +2 -2
- package/dist/{daemon-restart-KPSWNYTH.js → daemon-restart-BH67ZOTE.js} +6 -6
- package/dist/daemon.js +1538 -687
- package/dist/{delete-BSU7K3RY.js → delete-LOIANQGD.js} +1 -1
- package/dist/down-LIOQ5JDH.js +14 -0
- package/dist/{env-A3LMO777.js → env-4PHIHTF4.js} +2 -2
- package/dist/{export-6QBUOQGC.js → export-XD6PJBQP.js} +19 -8
- package/dist/{file-C57SK5DK.js → file-X4L5TTOL.js} +2 -2
- package/dist/{history-WNK3DFUM.js → history-HTEKRNID.js} +2 -2
- package/dist/{import-XEC34Y4Z.js → import-E433B4KG.js} +3 -3
- package/dist/{log-PPPZDVEF.js → log-SRO5Q6AD.js} +2 -2
- package/dist/{login-HNH3EUQV.js → login-UO6AOVEA.js} +4 -4
- package/dist/{logout-I5CB5UZS.js → logout-UKD5LA37.js} +2 -2
- package/dist/{logs-SF2IMJN4.js → logs-HNTNNBDW.js} +2 -2
- package/dist/{merge-33C237A4.js → merge-B6SYTGI7.js} +2 -2
- package/dist/{mind-Z7CKD6DG.js → mind-BIDOF65R.js} +27 -11
- package/dist/{mind-activity-tracker-624QLQLC.js → mind-activity-tracker-PGC3DBJ7.js} +4 -5
- package/dist/{mind-manager-3DMYKZPB.js → mind-manager-3V2NXX4I.js} +5 -6
- package/dist/{package-4NHAVUUI.js → package-HQR52XSG.js} +1 -1
- package/dist/{pages-4DGQT7ZA.js → pages-KQBR5TAZ.js} +6 -6
- package/dist/{publish-TAJUET4I.js → publish-OJ4QMXVZ.js} +6 -6
- package/dist/{pull-XAEWQJ47.js → pull-GRQAXM2E.js} +2 -2
- package/dist/{register-VSPCMHKX.js → register-U2UO6TC4.js} +5 -5
- package/dist/registry-D2BSQ2X5.js +42 -0
- package/dist/{restart-IQKMCK5M.js → restart-CIDAKGG2.js} +3 -6
- package/dist/{schedule-FFZG23IW.js → schedule-NLR3LZLY.js} +2 -2
- package/dist/{seed-J43YDKXG.js → seed-3H2MRREW.js} +2 -2
- package/dist/{send-KVIZIGCE.js → send-RP2TA7SG.js} +132 -36
- package/dist/{service-LUR7WDO7.js → service-TVNEORO7.js} +31 -13
- package/dist/{setup-52YRV7VP.js → setup-OZDYCKDI.js} +9 -34
- package/dist/{shared-KO35ZM44.js → shared-DCQ2UXOM.js} +4 -4
- package/dist/{skill-BCVNI6TV.js → skill-Q2Y6PQ3L.js} +2 -2
- package/dist/skills/orientation/SKILL.md +2 -2
- package/dist/skills/volute-mind/SKILL.md +5 -5
- package/dist/{sprout-QN7Y4VVO.js → sprout-6Z6C42YM.js} +34 -30
- package/dist/{start-I5JYB65M.js → start-JR6CUUWF.js} +3 -6
- package/dist/{status-D7E5HHBV.js → status-5XDGYHKP.js} +2 -2
- package/dist/{status-FU2PFVVF.js → status-LV34BG6G.js} +3 -3
- package/dist/{status-4ESFLGH4.js → status-Z7NAFMBI.js} +5 -5
- package/dist/{stop-NBVKEFQQ.js → stop-VKPGK25U.js} +2 -5
- package/dist/template-hash-BIMA4ILT.js +8 -0
- package/dist/{up-FS7CKM6V.js → up-7BGDMFRT.js} +5 -5
- package/dist/{update-FJIHDJKM.js → update-4WT7VWHW.js} +5 -5
- package/dist/{update-check-MWE5AH4U.js → update-check-F5Z3ALXX.js} +2 -2
- package/dist/{upgrade-AIT24B5I.js → upgrade-ZEC2GGFO.js} +1 -1
- package/dist/{variant-63ZWO2W7.js → variant-A4I7PHXS.js} +16 -24
- package/dist/version-notify-TFS2U5CF.js +173 -0
- package/dist/web-assets/assets/index-BR3gtK3E.css +1 -0
- package/dist/web-assets/assets/index-CWmrZRQd.js +64 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-5XNT2472.js +0 -36
- package/dist/chunk-UJ6GHNR7.js +0 -675
- package/dist/db-C2CJ46ZU.js +0 -10
- package/dist/delivery-manager-CSG7LXA4.js +0 -16
- package/dist/down-ZY35KMHR.js +0 -14
- package/dist/schema-GFH6RV3W.js +0 -26
- package/dist/variants-JAGWGBXG.js +0 -26
- package/dist/web-assets/assets/index-CUZTZzaW.js +0 -64
- package/dist/web-assets/assets/index-adVuCkqy.css +0 -1
|
@@ -1,41 +1,173 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
logger_default
|
|
4
|
-
} from "./chunk-YUIHSKR6.js";
|
|
5
|
-
import {
|
|
6
|
-
getDb
|
|
7
|
-
} from "./chunk-5XNT2472.js";
|
|
8
2
|
import {
|
|
9
3
|
deliveryQueue,
|
|
4
|
+
getDb,
|
|
10
5
|
mindHistory
|
|
11
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SGPEZ32F.js";
|
|
7
|
+
import {
|
|
8
|
+
logger_default
|
|
9
|
+
} from "./chunk-YUIHSKR6.js";
|
|
12
10
|
import {
|
|
13
11
|
findMind,
|
|
14
12
|
findVariant,
|
|
15
13
|
mindDir
|
|
16
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-B2CPS4QU.js";
|
|
17
15
|
|
|
18
|
-
// src/lib/delivery-manager.ts
|
|
16
|
+
// src/lib/delivery/delivery-manager.ts
|
|
19
17
|
import { and, eq, sql } from "drizzle-orm";
|
|
20
18
|
|
|
21
|
-
// src/lib/
|
|
19
|
+
// src/lib/events/conversation-events.ts
|
|
20
|
+
var subscribers = /* @__PURE__ */ new Map();
|
|
21
|
+
function subscribe(conversationId, callback) {
|
|
22
|
+
let set = subscribers.get(conversationId);
|
|
23
|
+
if (!set) {
|
|
24
|
+
set = /* @__PURE__ */ new Set();
|
|
25
|
+
subscribers.set(conversationId, set);
|
|
26
|
+
}
|
|
27
|
+
set.add(callback);
|
|
28
|
+
return () => {
|
|
29
|
+
set.delete(callback);
|
|
30
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function publish(conversationId, event) {
|
|
34
|
+
const set = subscribers.get(conversationId);
|
|
35
|
+
if (!set) return;
|
|
36
|
+
for (const cb of set) {
|
|
37
|
+
try {
|
|
38
|
+
cb(event);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error("[conversation-events] subscriber threw:", err);
|
|
41
|
+
set.delete(cb);
|
|
42
|
+
if (set.size === 0) subscribers.delete(conversationId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/lib/typing.ts
|
|
48
|
+
var DEFAULT_TTL_MS = 1e4;
|
|
49
|
+
var SWEEP_INTERVAL_MS = 5e3;
|
|
50
|
+
var VOLUTE_PREFIX = "volute:";
|
|
51
|
+
var TypingMap = class {
|
|
52
|
+
channels = /* @__PURE__ */ new Map();
|
|
53
|
+
sweepTimer;
|
|
54
|
+
constructor() {
|
|
55
|
+
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
56
|
+
this.sweepTimer.unref();
|
|
57
|
+
}
|
|
58
|
+
set(channel, sender, opts) {
|
|
59
|
+
const expiresAt = opts?.persistent ? Infinity : Date.now() + (opts?.ttlMs ?? DEFAULT_TTL_MS);
|
|
60
|
+
let senders = this.channels.get(channel);
|
|
61
|
+
if (!senders) {
|
|
62
|
+
senders = /* @__PURE__ */ new Map();
|
|
63
|
+
this.channels.set(channel, senders);
|
|
64
|
+
}
|
|
65
|
+
senders.set(sender, { expiresAt });
|
|
66
|
+
}
|
|
67
|
+
delete(channel, sender) {
|
|
68
|
+
const senders = this.channels.get(channel);
|
|
69
|
+
if (senders) {
|
|
70
|
+
senders.delete(sender);
|
|
71
|
+
if (senders.size === 0) {
|
|
72
|
+
this.channels.delete(channel);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Remove a sender from all channels (e.g. when a mind finishes processing). Returns affected channel names. */
|
|
77
|
+
deleteSender(sender) {
|
|
78
|
+
const affected = [];
|
|
79
|
+
for (const [channel, senders] of this.channels) {
|
|
80
|
+
if (senders.has(sender)) {
|
|
81
|
+
senders.delete(sender);
|
|
82
|
+
affected.push(channel);
|
|
83
|
+
}
|
|
84
|
+
if (senders.size === 0) {
|
|
85
|
+
this.channels.delete(channel);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return affected;
|
|
89
|
+
}
|
|
90
|
+
get(channel) {
|
|
91
|
+
const senders = this.channels.get(channel);
|
|
92
|
+
if (!senders) return [];
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const result = [];
|
|
95
|
+
for (const [sender, entry] of senders) {
|
|
96
|
+
if (entry.expiresAt > now) {
|
|
97
|
+
result.push(sender);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
dispose() {
|
|
103
|
+
clearInterval(this.sweepTimer);
|
|
104
|
+
this.channels.clear();
|
|
105
|
+
if (instance === this) instance = void 0;
|
|
106
|
+
}
|
|
107
|
+
sweep() {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
for (const [channel, senders] of this.channels) {
|
|
110
|
+
for (const [sender, entry] of senders) {
|
|
111
|
+
if (entry.expiresAt <= now) {
|
|
112
|
+
senders.delete(sender);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (senders.size === 0) {
|
|
116
|
+
this.channels.delete(channel);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var instance;
|
|
122
|
+
function getTypingMap() {
|
|
123
|
+
if (!instance) {
|
|
124
|
+
instance = new TypingMap();
|
|
125
|
+
}
|
|
126
|
+
return instance;
|
|
127
|
+
}
|
|
128
|
+
function publishTypingForChannels(channels, map) {
|
|
129
|
+
for (const channel of channels) {
|
|
130
|
+
if (channel.startsWith(VOLUTE_PREFIX)) {
|
|
131
|
+
const conversationId = channel.slice(VOLUTE_PREFIX.length);
|
|
132
|
+
publish(conversationId, { type: "typing", senders: map.get(channel) });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/lib/delivery/delivery-router.ts
|
|
22
138
|
import { readFileSync, statSync } from "fs";
|
|
23
139
|
import { resolve } from "path";
|
|
140
|
+
function extractTextContent(content) {
|
|
141
|
+
if (typeof content === "string") return content;
|
|
142
|
+
if (Array.isArray(content)) {
|
|
143
|
+
return content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
144
|
+
}
|
|
145
|
+
return JSON.stringify(content);
|
|
146
|
+
}
|
|
24
147
|
var configCache = /* @__PURE__ */ new Map();
|
|
148
|
+
var statCheckCache = /* @__PURE__ */ new Map();
|
|
149
|
+
var STAT_TTL_MS = 5e3;
|
|
25
150
|
var dlog = logger_default.child("delivery-router");
|
|
26
151
|
function configPath(mindName) {
|
|
27
152
|
return resolve(mindDir(mindName), "home/.config/routes.json");
|
|
28
153
|
}
|
|
29
154
|
function getRoutingConfig(mindName) {
|
|
30
155
|
const path = configPath(mindName);
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const statCached = statCheckCache.get(mindName);
|
|
158
|
+
const cached = configCache.get(mindName);
|
|
159
|
+
if (statCached && cached && now - statCached.checkedAt < STAT_TTL_MS) {
|
|
160
|
+
return cached.config;
|
|
161
|
+
}
|
|
31
162
|
let mtime;
|
|
32
163
|
try {
|
|
33
164
|
mtime = statSync(path).mtimeMs;
|
|
34
165
|
} catch {
|
|
35
166
|
configCache.delete(mindName);
|
|
167
|
+
statCheckCache.delete(mindName);
|
|
36
168
|
return {};
|
|
37
169
|
}
|
|
38
|
-
|
|
170
|
+
statCheckCache.set(mindName, { mtime, checkedAt: now });
|
|
39
171
|
if (cached && cached.mtime === mtime) {
|
|
40
172
|
return cached.config;
|
|
41
173
|
}
|
|
@@ -50,9 +182,15 @@ function getRoutingConfig(mindName) {
|
|
|
50
182
|
return {};
|
|
51
183
|
}
|
|
52
184
|
}
|
|
185
|
+
var globRegexCache = /* @__PURE__ */ new Map();
|
|
53
186
|
function globMatch(pattern, value) {
|
|
54
|
-
|
|
55
|
-
|
|
187
|
+
let regex = globRegexCache.get(pattern);
|
|
188
|
+
if (!regex) {
|
|
189
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
190
|
+
regex = new RegExp(`^${escaped}$`);
|
|
191
|
+
globRegexCache.set(pattern, regex);
|
|
192
|
+
}
|
|
193
|
+
return regex.test(value);
|
|
56
194
|
}
|
|
57
195
|
var GLOB_MATCH_KEYS = /* @__PURE__ */ new Set(["channel", "sender"]);
|
|
58
196
|
var NON_MATCH_KEYS = /* @__PURE__ */ new Set(["session", "destination", "path", "mode"]);
|
|
@@ -164,204 +302,8 @@ function resolveDeliveryMode(config, sessionName) {
|
|
|
164
302
|
return defaults;
|
|
165
303
|
}
|
|
166
304
|
|
|
167
|
-
// src/lib/
|
|
168
|
-
var dlog2 = logger_default.child("delivery");
|
|
169
|
-
function extractTextContent(content) {
|
|
170
|
-
if (typeof content === "string") return content;
|
|
171
|
-
if (Array.isArray(content)) {
|
|
172
|
-
return content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
|
|
173
|
-
}
|
|
174
|
-
return JSON.stringify(content);
|
|
175
|
-
}
|
|
176
|
-
async function deliverMessage(mindName, payload) {
|
|
177
|
-
try {
|
|
178
|
-
const [baseName] = mindName.split("@", 2);
|
|
179
|
-
const entry = findMind(baseName);
|
|
180
|
-
if (!entry) {
|
|
181
|
-
dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const textContent = extractTextContent(payload.content);
|
|
185
|
-
try {
|
|
186
|
-
const db = await getDb();
|
|
187
|
-
await db.insert(mindHistory).values({
|
|
188
|
-
mind: baseName,
|
|
189
|
-
type: "inbound",
|
|
190
|
-
channel: payload.channel,
|
|
191
|
-
sender: payload.sender ?? null,
|
|
192
|
-
content: textContent
|
|
193
|
-
});
|
|
194
|
-
} catch (err) {
|
|
195
|
-
dlog2.warn(`failed to persist message for ${baseName}`, logger_default.errorData(err));
|
|
196
|
-
}
|
|
197
|
-
try {
|
|
198
|
-
const { getDeliveryManager: getDeliveryManager2 } = await import("./delivery-manager-CSG7LXA4.js");
|
|
199
|
-
const manager = getDeliveryManager2();
|
|
200
|
-
await manager.routeAndDeliver(mindName, payload);
|
|
201
|
-
return;
|
|
202
|
-
} catch (err) {
|
|
203
|
-
if (err instanceof Error && !err.message.includes("not initialized")) {
|
|
204
|
-
dlog2.warn("delivery manager error, falling back to direct delivery", logger_default.errorData(err));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const { findVariant: findVariant2 } = await import("./variants-JAGWGBXG.js");
|
|
208
|
-
const [, variantName] = mindName.split("@", 2);
|
|
209
|
-
let port = entry.port;
|
|
210
|
-
if (variantName) {
|
|
211
|
-
const variant = findVariant2(baseName, variantName);
|
|
212
|
-
if (!variant) {
|
|
213
|
-
dlog2.warn(`cannot deliver to ${mindName}: variant not found`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
port = variant.port;
|
|
217
|
-
}
|
|
218
|
-
const body = JSON.stringify(payload);
|
|
219
|
-
const controller = new AbortController();
|
|
220
|
-
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
221
|
-
try {
|
|
222
|
-
const res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
223
|
-
method: "POST",
|
|
224
|
-
headers: { "Content-Type": "application/json" },
|
|
225
|
-
body,
|
|
226
|
-
signal: controller.signal
|
|
227
|
-
});
|
|
228
|
-
if (!res.ok) {
|
|
229
|
-
const text = await res.text().catch(() => "");
|
|
230
|
-
dlog2.warn(`mind ${mindName} responded ${res.status}: ${text}`);
|
|
231
|
-
} else {
|
|
232
|
-
await res.text().catch(() => {
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
} catch (err) {
|
|
236
|
-
dlog2.warn(`failed to deliver to ${mindName}`, logger_default.errorData(err));
|
|
237
|
-
} finally {
|
|
238
|
-
clearTimeout(timeout);
|
|
239
|
-
}
|
|
240
|
-
} catch (err) {
|
|
241
|
-
dlog2.warn(`unexpected error delivering to ${mindName}`, logger_default.errorData(err));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// src/lib/conversation-events.ts
|
|
246
|
-
var subscribers = /* @__PURE__ */ new Map();
|
|
247
|
-
function subscribe(conversationId, callback) {
|
|
248
|
-
let set = subscribers.get(conversationId);
|
|
249
|
-
if (!set) {
|
|
250
|
-
set = /* @__PURE__ */ new Set();
|
|
251
|
-
subscribers.set(conversationId, set);
|
|
252
|
-
}
|
|
253
|
-
set.add(callback);
|
|
254
|
-
return () => {
|
|
255
|
-
set.delete(callback);
|
|
256
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function publish(conversationId, event) {
|
|
260
|
-
const set = subscribers.get(conversationId);
|
|
261
|
-
if (!set) return;
|
|
262
|
-
for (const cb of set) {
|
|
263
|
-
try {
|
|
264
|
-
cb(event);
|
|
265
|
-
} catch (err) {
|
|
266
|
-
console.error("[conversation-events] subscriber threw:", err);
|
|
267
|
-
set.delete(cb);
|
|
268
|
-
if (set.size === 0) subscribers.delete(conversationId);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/lib/typing.ts
|
|
274
|
-
var DEFAULT_TTL_MS = 1e4;
|
|
275
|
-
var SWEEP_INTERVAL_MS = 5e3;
|
|
276
|
-
var VOLUTE_PREFIX = "volute:";
|
|
277
|
-
var TypingMap = class {
|
|
278
|
-
channels = /* @__PURE__ */ new Map();
|
|
279
|
-
sweepTimer;
|
|
280
|
-
constructor() {
|
|
281
|
-
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
282
|
-
this.sweepTimer.unref();
|
|
283
|
-
}
|
|
284
|
-
set(channel, sender, opts) {
|
|
285
|
-
const expiresAt = opts?.persistent ? Infinity : Date.now() + (opts?.ttlMs ?? DEFAULT_TTL_MS);
|
|
286
|
-
let senders = this.channels.get(channel);
|
|
287
|
-
if (!senders) {
|
|
288
|
-
senders = /* @__PURE__ */ new Map();
|
|
289
|
-
this.channels.set(channel, senders);
|
|
290
|
-
}
|
|
291
|
-
senders.set(sender, { expiresAt });
|
|
292
|
-
}
|
|
293
|
-
delete(channel, sender) {
|
|
294
|
-
const senders = this.channels.get(channel);
|
|
295
|
-
if (senders) {
|
|
296
|
-
senders.delete(sender);
|
|
297
|
-
if (senders.size === 0) {
|
|
298
|
-
this.channels.delete(channel);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
/** Remove a sender from all channels (e.g. when a mind finishes processing). Returns affected channel names. */
|
|
303
|
-
deleteSender(sender) {
|
|
304
|
-
const affected = [];
|
|
305
|
-
for (const [channel, senders] of this.channels) {
|
|
306
|
-
if (senders.has(sender)) {
|
|
307
|
-
senders.delete(sender);
|
|
308
|
-
affected.push(channel);
|
|
309
|
-
}
|
|
310
|
-
if (senders.size === 0) {
|
|
311
|
-
this.channels.delete(channel);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return affected;
|
|
315
|
-
}
|
|
316
|
-
get(channel) {
|
|
317
|
-
const senders = this.channels.get(channel);
|
|
318
|
-
if (!senders) return [];
|
|
319
|
-
const now = Date.now();
|
|
320
|
-
const result = [];
|
|
321
|
-
for (const [sender, entry] of senders) {
|
|
322
|
-
if (entry.expiresAt > now) {
|
|
323
|
-
result.push(sender);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return result;
|
|
327
|
-
}
|
|
328
|
-
dispose() {
|
|
329
|
-
clearInterval(this.sweepTimer);
|
|
330
|
-
this.channels.clear();
|
|
331
|
-
if (instance === this) instance = void 0;
|
|
332
|
-
}
|
|
333
|
-
sweep() {
|
|
334
|
-
const now = Date.now();
|
|
335
|
-
for (const [channel, senders] of this.channels) {
|
|
336
|
-
for (const [sender, entry] of senders) {
|
|
337
|
-
if (entry.expiresAt <= now) {
|
|
338
|
-
senders.delete(sender);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (senders.size === 0) {
|
|
342
|
-
this.channels.delete(channel);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
var instance;
|
|
348
|
-
function getTypingMap() {
|
|
349
|
-
if (!instance) {
|
|
350
|
-
instance = new TypingMap();
|
|
351
|
-
}
|
|
352
|
-
return instance;
|
|
353
|
-
}
|
|
354
|
-
function publishTypingForChannels(channels, map) {
|
|
355
|
-
for (const channel of channels) {
|
|
356
|
-
if (channel.startsWith(VOLUTE_PREFIX)) {
|
|
357
|
-
const conversationId = channel.slice(VOLUTE_PREFIX.length);
|
|
358
|
-
publish(conversationId, { type: "typing", senders: map.get(channel) });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// src/lib/delivery-manager.ts
|
|
364
|
-
var dlog3 = logger_default.child("delivery-manager");
|
|
305
|
+
// src/lib/delivery/delivery-manager.ts
|
|
306
|
+
var dlog2 = logger_default.child("delivery-manager");
|
|
365
307
|
var MAX_BATCH_SIZE = 50;
|
|
366
308
|
var DeliveryManager = class {
|
|
367
309
|
sessionStates = /* @__PURE__ */ new Map();
|
|
@@ -438,7 +380,7 @@ var DeliveryManager = class {
|
|
|
438
380
|
try {
|
|
439
381
|
payload = JSON.parse(row.payload);
|
|
440
382
|
} catch (parseErr) {
|
|
441
|
-
|
|
383
|
+
dlog2.warn(
|
|
442
384
|
`corrupt payload in delivery queue row ${row.id}, skipping`,
|
|
443
385
|
logger_default.errorData(parseErr)
|
|
444
386
|
);
|
|
@@ -449,22 +391,21 @@ var DeliveryManager = class {
|
|
|
449
391
|
if (sessionConfig.delivery.mode === "batch") {
|
|
450
392
|
this.addToBatchBuffer(row.mind, row.session, payload, sessionConfig);
|
|
451
393
|
} else {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
dlog3.warn(`failed to restore delivery for ${row.mind}`, logger_default.errorData(err));
|
|
394
|
+
try {
|
|
395
|
+
await db.delete(deliveryQueue).where(eq(deliveryQueue.id, row.id));
|
|
396
|
+
} catch (err) {
|
|
397
|
+
dlog2.warn(`failed to delete queue row ${row.id} for ${row.mind}`, logger_default.errorData(err));
|
|
398
|
+
}
|
|
399
|
+
this.deliverToMind(row.mind, row.session, payload, sessionConfig).catch((err) => {
|
|
400
|
+
dlog2.warn(`failed to restore delivery for ${row.mind}`, logger_default.errorData(err));
|
|
460
401
|
});
|
|
461
402
|
}
|
|
462
403
|
}
|
|
463
404
|
if (rows.length > 0) {
|
|
464
|
-
|
|
405
|
+
dlog2.info(`restored ${rows.length} queued messages from DB`);
|
|
465
406
|
}
|
|
466
407
|
} catch (err) {
|
|
467
|
-
|
|
408
|
+
dlog2.warn("failed to restore delivery queue from DB", logger_default.errorData(err));
|
|
468
409
|
}
|
|
469
410
|
}
|
|
470
411
|
/**
|
|
@@ -513,22 +454,46 @@ var DeliveryManager = class {
|
|
|
513
454
|
if (instance2 === this) instance2 = void 0;
|
|
514
455
|
}
|
|
515
456
|
// --- Private ---
|
|
516
|
-
|
|
457
|
+
resolvePort(mindName) {
|
|
517
458
|
const [baseName, variantName] = mindName.split("@", 2);
|
|
518
459
|
const entry = findMind(baseName);
|
|
519
|
-
if (!entry)
|
|
520
|
-
dlog3.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
let port = entry.port;
|
|
460
|
+
if (!entry) return null;
|
|
524
461
|
if (variantName) {
|
|
525
462
|
const variant = findVariant(baseName, variantName);
|
|
526
|
-
if (!variant)
|
|
527
|
-
|
|
528
|
-
|
|
463
|
+
if (!variant) return null;
|
|
464
|
+
return { baseName, port: variant.port };
|
|
465
|
+
}
|
|
466
|
+
return { baseName, port: entry.port };
|
|
467
|
+
}
|
|
468
|
+
async postToMind(port, body) {
|
|
469
|
+
const controller = new AbortController();
|
|
470
|
+
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
471
|
+
try {
|
|
472
|
+
const res = await fetch(`http://127.0.0.1:${port}/message`, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
headers: { "Content-Type": "application/json" },
|
|
475
|
+
body,
|
|
476
|
+
signal: controller.signal
|
|
477
|
+
});
|
|
478
|
+
if (!res.ok) {
|
|
479
|
+
const text = await res.text().catch(() => "");
|
|
480
|
+
dlog2.warn(`mind responded ${res.status}: ${text}`);
|
|
481
|
+
return false;
|
|
529
482
|
}
|
|
530
|
-
|
|
483
|
+
await res.text().catch(() => {
|
|
484
|
+
});
|
|
485
|
+
return true;
|
|
486
|
+
} finally {
|
|
487
|
+
clearTimeout(timeout);
|
|
531
488
|
}
|
|
489
|
+
}
|
|
490
|
+
async deliverToMind(mindName, session, payload, sessionConfig) {
|
|
491
|
+
const resolved = this.resolvePort(mindName);
|
|
492
|
+
if (!resolved) {
|
|
493
|
+
dlog2.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const { baseName, port } = resolved;
|
|
532
497
|
const senders = /* @__PURE__ */ new Set();
|
|
533
498
|
if (payload.sender) senders.add(payload.sender);
|
|
534
499
|
const channels = /* @__PURE__ */ new Set();
|
|
@@ -541,55 +506,31 @@ var DeliveryManager = class {
|
|
|
541
506
|
if (payload.conversationId) {
|
|
542
507
|
typingMap.set(`volute:${payload.conversationId}`, baseName, { persistent: true });
|
|
543
508
|
}
|
|
544
|
-
const
|
|
509
|
+
const body = JSON.stringify({
|
|
545
510
|
...payload,
|
|
546
511
|
session,
|
|
547
512
|
interrupt: sessionConfig.interrupt,
|
|
548
513
|
instructions: sessionConfig.instructions
|
|
549
|
-
};
|
|
550
|
-
const body = JSON.stringify(deliveryBody);
|
|
551
|
-
const controller = new AbortController();
|
|
552
|
-
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
514
|
+
});
|
|
553
515
|
try {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
headers: { "Content-Type": "application/json" },
|
|
557
|
-
body,
|
|
558
|
-
signal: controller.signal
|
|
559
|
-
});
|
|
560
|
-
if (!res.ok) {
|
|
561
|
-
const text = await res.text().catch(() => "");
|
|
562
|
-
dlog3.warn(`mind ${mindName} responded ${res.status}: ${text}`);
|
|
516
|
+
const ok = await this.postToMind(port, body);
|
|
517
|
+
if (!ok) {
|
|
563
518
|
this.decrementActive(baseName, session);
|
|
564
519
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
565
|
-
} else {
|
|
566
|
-
await res.text().catch(() => {
|
|
567
|
-
});
|
|
568
520
|
}
|
|
569
521
|
} catch (err) {
|
|
570
|
-
|
|
522
|
+
dlog2.warn(`failed to deliver to ${mindName}`, logger_default.errorData(err));
|
|
571
523
|
this.decrementActive(baseName, session);
|
|
572
524
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
573
|
-
} finally {
|
|
574
|
-
clearTimeout(timeout);
|
|
575
525
|
}
|
|
576
526
|
}
|
|
577
527
|
async deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride) {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
dlog3.warn(`cannot deliver batch to ${mindName}: mind not found`);
|
|
528
|
+
const resolved = this.resolvePort(mindName);
|
|
529
|
+
if (!resolved) {
|
|
530
|
+
dlog2.warn(`cannot deliver batch to ${mindName}: mind not found`);
|
|
582
531
|
return;
|
|
583
532
|
}
|
|
584
|
-
|
|
585
|
-
if (variantName) {
|
|
586
|
-
const variant = findVariant(baseName, variantName);
|
|
587
|
-
if (!variant) {
|
|
588
|
-
dlog3.warn(`cannot deliver batch to ${mindName}: variant not found`);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
port = variant.port;
|
|
592
|
-
}
|
|
533
|
+
const { baseName, port } = resolved;
|
|
593
534
|
const channels = {};
|
|
594
535
|
for (const msg of messages) {
|
|
595
536
|
const ch = msg.channel ?? "unknown";
|
|
@@ -614,30 +555,18 @@ var DeliveryManager = class {
|
|
|
614
555
|
typingMap.set(`volute:${msg.payload.conversationId}`, baseName, { persistent: true });
|
|
615
556
|
}
|
|
616
557
|
}
|
|
617
|
-
const
|
|
558
|
+
const body = JSON.stringify({
|
|
618
559
|
session,
|
|
619
560
|
batch: { channels },
|
|
620
561
|
interrupt: interruptOverride ?? sessionConfig.interrupt,
|
|
621
562
|
instructions: sessionConfig.instructions
|
|
622
|
-
};
|
|
623
|
-
const body = JSON.stringify(batchBody);
|
|
624
|
-
const controller = new AbortController();
|
|
625
|
-
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
563
|
+
});
|
|
626
564
|
try {
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
headers: { "Content-Type": "application/json" },
|
|
630
|
-
body,
|
|
631
|
-
signal: controller.signal
|
|
632
|
-
});
|
|
633
|
-
if (!res.ok) {
|
|
634
|
-
const text = await res.text().catch(() => "");
|
|
635
|
-
dlog3.warn(`mind ${mindName} batch responded ${res.status}: ${text}`);
|
|
565
|
+
const ok = await this.postToMind(port, body);
|
|
566
|
+
if (!ok) {
|
|
636
567
|
this.decrementActive(baseName, session);
|
|
637
568
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
638
569
|
} else {
|
|
639
|
-
await res.text().catch(() => {
|
|
640
|
-
});
|
|
641
570
|
try {
|
|
642
571
|
const db = await getDb();
|
|
643
572
|
await db.delete(deliveryQueue).where(
|
|
@@ -648,18 +577,16 @@ var DeliveryManager = class {
|
|
|
648
577
|
)
|
|
649
578
|
);
|
|
650
579
|
} catch (err) {
|
|
651
|
-
|
|
580
|
+
dlog2.warn(
|
|
652
581
|
`failed to clean delivery queue for ${baseName}/${session}`,
|
|
653
582
|
logger_default.errorData(err)
|
|
654
583
|
);
|
|
655
584
|
}
|
|
656
585
|
}
|
|
657
586
|
} catch (err) {
|
|
658
|
-
|
|
587
|
+
dlog2.warn(`failed to deliver batch to ${mindName}`, logger_default.errorData(err));
|
|
659
588
|
this.decrementActive(baseName, session);
|
|
660
589
|
publishTypingForChannels(typingMap.deleteSender(baseName), typingMap);
|
|
661
|
-
} finally {
|
|
662
|
-
clearTimeout(timeout);
|
|
663
590
|
}
|
|
664
591
|
}
|
|
665
592
|
enqueueBatch(mindName, session, payload, sessionConfig) {
|
|
@@ -684,7 +611,7 @@ var DeliveryManager = class {
|
|
|
684
611
|
if (state && state.activeCount > 0 && payload.sender && !state.lastDeliverySenders.has(payload.sender) && payload.channel && state.lastDeliveryChannels.has(payload.channel) && Date.now() - state.lastDeliveredAt < delivery.maxWait * 1e3 && Date.now() - state.lastInterruptAt > delivery.debounce * 1e3) {
|
|
685
612
|
state.lastInterruptAt = Date.now();
|
|
686
613
|
this.persistToQueue(mindName, session, payload).catch((err) => {
|
|
687
|
-
|
|
614
|
+
dlog2.warn(`failed to persist batch message for ${mindName}/${session}`, logger_default.errorData(err));
|
|
688
615
|
});
|
|
689
616
|
this.flushBatch(
|
|
690
617
|
mindName,
|
|
@@ -695,7 +622,7 @@ var DeliveryManager = class {
|
|
|
695
622
|
return;
|
|
696
623
|
}
|
|
697
624
|
this.persistToQueue(mindName, session, payload).catch((err) => {
|
|
698
|
-
|
|
625
|
+
dlog2.warn(`failed to persist batch message for ${mindName}/${session}`, logger_default.errorData(err));
|
|
699
626
|
});
|
|
700
627
|
this.addToBatchBuffer(mindName, session, payload, sessionConfig);
|
|
701
628
|
}
|
|
@@ -758,12 +685,12 @@ var DeliveryManager = class {
|
|
|
758
685
|
const [baseName] = mindName.split("@", 2);
|
|
759
686
|
const config = getRoutingConfig(baseName);
|
|
760
687
|
const sessionConfig = resolveDeliveryMode(config, session);
|
|
761
|
-
|
|
688
|
+
dlog2.info(
|
|
762
689
|
`flushing batch for ${mindName}/${session}: ${messages.length} messages${interruptOverride ? " (new-speaker interrupt)" : ""}`
|
|
763
690
|
);
|
|
764
691
|
this.deliverBatchToMind(mindName, session, messages, sessionConfig, interruptOverride).catch(
|
|
765
692
|
(err) => {
|
|
766
|
-
|
|
693
|
+
dlog2.warn(`failed to flush batch for ${mindName}/${session}`, logger_default.errorData(err));
|
|
767
694
|
}
|
|
768
695
|
);
|
|
769
696
|
}
|
|
@@ -783,7 +710,7 @@ var DeliveryManager = class {
|
|
|
783
710
|
await this.sendInviteNotification(mindName, payload);
|
|
784
711
|
}
|
|
785
712
|
} catch (err) {
|
|
786
|
-
|
|
713
|
+
dlog2.warn(`failed to check gated count for ${baseName}`, logger_default.errorData(err));
|
|
787
714
|
}
|
|
788
715
|
}
|
|
789
716
|
async sendInviteNotification(mindName, payload) {
|
|
@@ -825,7 +752,7 @@ var DeliveryManager = class {
|
|
|
825
752
|
payload: JSON.stringify(payload)
|
|
826
753
|
});
|
|
827
754
|
} catch (err) {
|
|
828
|
-
|
|
755
|
+
dlog2.warn(
|
|
829
756
|
`failed to persist to delivery queue for ${mindName}/${session}`,
|
|
830
757
|
logger_default.errorData(err)
|
|
831
758
|
);
|
|
@@ -867,7 +794,7 @@ var DeliveryManager = class {
|
|
|
867
794
|
};
|
|
868
795
|
var instance2;
|
|
869
796
|
function initDeliveryManager() {
|
|
870
|
-
if (instance2)
|
|
797
|
+
if (instance2) throw new Error("DeliveryManager already initialized");
|
|
871
798
|
instance2 = new DeliveryManager();
|
|
872
799
|
return instance2;
|
|
873
800
|
}
|
|
@@ -878,14 +805,43 @@ function getDeliveryManager() {
|
|
|
878
805
|
return instance2;
|
|
879
806
|
}
|
|
880
807
|
|
|
808
|
+
// src/lib/delivery/message-delivery.ts
|
|
809
|
+
var dlog3 = logger_default.child("delivery");
|
|
810
|
+
async function deliverMessage(mindName, payload) {
|
|
811
|
+
try {
|
|
812
|
+
const [baseName] = mindName.split("@", 2);
|
|
813
|
+
const entry = findMind(baseName);
|
|
814
|
+
if (!entry) {
|
|
815
|
+
dlog3.warn(`cannot deliver to ${mindName}: mind not found`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const textContent = extractTextContent(payload.content);
|
|
819
|
+
try {
|
|
820
|
+
const db = await getDb();
|
|
821
|
+
await db.insert(mindHistory).values({
|
|
822
|
+
mind: baseName,
|
|
823
|
+
type: "inbound",
|
|
824
|
+
channel: payload.channel,
|
|
825
|
+
sender: payload.sender ?? null,
|
|
826
|
+
content: textContent
|
|
827
|
+
});
|
|
828
|
+
} catch (err) {
|
|
829
|
+
dlog3.warn(`failed to persist message for ${baseName}`, logger_default.errorData(err));
|
|
830
|
+
}
|
|
831
|
+
const manager = getDeliveryManager();
|
|
832
|
+
await manager.routeAndDeliver(mindName, payload);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
dlog3.warn(`unexpected error delivering to ${mindName}`, logger_default.errorData(err));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
881
838
|
export {
|
|
882
|
-
extractTextContent,
|
|
883
|
-
deliverMessage,
|
|
884
839
|
subscribe,
|
|
885
840
|
publish,
|
|
886
841
|
getTypingMap,
|
|
887
842
|
publishTypingForChannels,
|
|
888
|
-
|
|
843
|
+
extractTextContent,
|
|
889
844
|
initDeliveryManager,
|
|
890
|
-
getDeliveryManager
|
|
845
|
+
getDeliveryManager,
|
|
846
|
+
deliverMessage
|
|
891
847
|
};
|