volute 0.53.0 → 0.54.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/dist/auto-upgrade-HEVZKPTF.js +46 -0
- package/dist/{backup-WWXVIMYQ.js → backup-QRNX6RK7.js} +11 -11
- package/dist/channels-CKCY64TD.js +203 -0
- package/dist/{chat-WK2BLM7B.js → chat-ZUGNL5GS.js} +9 -9
- package/dist/{chunk-UTKUO2KY.js → chunk-3LHUXY3E.js} +2 -2
- package/dist/{chunk-PIZDPY4G.js → chunk-444RR3YF.js} +4 -4
- package/dist/{chunk-VDZ4I4ZP.js → chunk-56IT2ZXF.js} +3 -3
- package/dist/{chunk-OXWUEU5V.js → chunk-5U5GLZSD.js} +2 -2
- package/dist/{chunk-LN7XJ56R.js → chunk-7XKPG66D.js} +9 -9
- package/dist/{chunk-R772ET2M.js → chunk-AAPNU7GA.js} +4 -0
- package/dist/{chunk-7ARONCZW.js → chunk-ATZF2RNJ.js} +3 -3
- package/dist/{chunk-6NLW72HP.js → chunk-CKPAVQNK.js} +1 -1
- package/dist/{chunk-RMADJFA7.js → chunk-CLCHOXLR.js} +1 -1
- package/dist/{chunk-IQ7VNWH6.js → chunk-HKGYZVWN.js} +1 -1
- package/dist/{chunk-HASADR3E.js → chunk-LGXVGIWN.js} +66 -25
- package/dist/{chunk-P2TBUKLW.js → chunk-LKUGUQUW.js} +3 -3
- package/dist/{chunk-AP2KQMMW.js → chunk-OAYY3WF3.js} +11 -3
- package/dist/{chunk-UO4MZQNO.js → chunk-OGPVJ53N.js} +14 -6
- package/dist/{chunk-M77B73VV.js → chunk-OIOW3FPT.js} +7 -2
- package/dist/chunk-OT5SXF7L.js +904 -0
- package/dist/{chunk-EDR47UE2.js → chunk-OYF25B62.js} +1 -1
- package/dist/{chunk-TAWRJ24T.js → chunk-PPSN2MLC.js} +2 -2
- package/dist/{chunk-5DBGQBQR.js → chunk-RGHF3OK5.js} +21 -10
- package/dist/{chunk-RQRO6PC7.js → chunk-VDUCLVZI.js} +305 -31
- package/dist/{chunk-23UJGVYP.js → chunk-WHL6PKYP.js} +10 -10
- package/dist/{chunk-HTB7KDRU.js → chunk-WZZCA23D.js} +5 -4
- package/dist/{chunk-UDZLPR34.js → chunk-YSRYCS3N.js} +13 -8
- package/dist/{chunk-5N32EN4O.js → chunk-Z4Q6TQWL.js} +1 -1
- package/dist/{chunk-BDHKCLWK.js → chunk-ZNHL5KQJ.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/{clock-SH4NQC2F.js → clock-X7V3C7NL.js} +3 -3
- package/dist/{cloud-sync-MJVJ33LD.js → cloud-sync-52YNJRMR.js} +11 -11
- package/dist/{daemon-restart-BIGU4OKS.js → daemon-restart-QWHN5VMU.js} +4 -4
- package/dist/daemon.js +330 -746
- package/dist/{db-IPXRJUI7.js → db-I4EH5SLF.js} +3 -1
- package/dist/{delivery-manager-FDHCZ7PU.js → delivery-manager-7R2YIQ2W.js} +11 -7
- package/dist/{delivery-notices-65TB3CBX.js → delivery-notices-RZQ6GONP.js} +3 -3
- package/dist/{delivery-router-RHANNTAC.js → delivery-router-QS4NLJR6.js} +3 -1
- package/dist/{down-QSNEMTTX.js → down-UN2EDX2Y.js} +3 -3
- package/dist/{echo-text-KU6G4Z7V.js → echo-text-73CYQRL7.js} +14 -14
- package/dist/{exec-ABD3DYA2.js → exec-Z34TTUO3.js} +1 -1
- package/dist/{extensions-VACUKFA5.js → extensions-QT65JGV6.js} +21 -11
- package/dist/{isolation-GVV2IBAU.js → isolation-UQPDO34R.js} +1 -1
- package/dist/{message-delivery-TUXM3AAE.js → message-delivery-TRKT4T2P.js} +11 -11
- package/dist/migrate-name-placeholder-C7NCNLWF.js +37 -0
- package/dist/{mind-IX5AWUST.js → mind-CT6ANXK4.js} +12 -12
- package/dist/{mind-contacts-BKUOUJPV.js → mind-contacts-PEXCDRXA.js} +8 -8
- package/dist/{mind-list-KNU34SUD.js → mind-list-7KBLPLES.js} +2 -2
- package/dist/{mind-service-WHFBUKZE.js → mind-service-7EFIJ3ZA.js} +11 -11
- package/dist/{mind-sleep-D3DEYAWG.js → mind-sleep-24HV4RCA.js} +5 -5
- package/dist/{mind-status-BPTLQ5Z6.js → mind-status-4YW4PHMO.js} +6 -3
- package/dist/{package-BN5KMU54.js → package-F6S5NHY2.js} +4 -4
- package/dist/{prompts-HNKGCAEY.js → prompts-JECJIFPQ.js} +1 -1
- package/dist/{scheduler-5X5UIQOQ.js → scheduler-73Q4MQ7Y.js} +6 -6
- package/dist/{seed-LHZ33UB4.js → seed-DRKAP3XV.js} +1 -1
- package/dist/{seed-cmd-U4RVJ3UA.js → seed-cmd-6IMTO3LG.js} +2 -2
- package/dist/{seed-create-OQU4KNCB.js → seed-create-VNLDYRFK.js} +1 -1
- package/dist/{seed-readiness-WJYYRRCK.js → seed-readiness-H5TTPAQH.js} +2 -2
- package/dist/{seed-sprout-SFUYH2RG.js → seed-sprout-PPGYGK4G.js} +8 -8
- package/dist/{send-FWZAMDBN.js → send-UXUQKO4N.js} +9 -9
- package/dist/{service-XQPMZAGI.js → service-T4P5W6RS.js} +2 -2
- package/dist/{service-install-2EWD7KXD.js → service-install-R4SNYUVO.js} +3 -3
- package/dist/{setup-RVSGCIF3.js → setup-YUAQMDVZ.js} +4 -4
- package/dist/skills/dreaming/SKILL.md +2 -2
- package/dist/skills/dreaming/references/INSTALL.md +1 -1
- package/dist/skills/tending/SKILL.md +2 -0
- package/dist/skills/volute-mind/references/integrations.md +3 -2
- package/dist/skills/volute-mind/references/routing.md +18 -5
- package/dist/{skills-QRPU6BWY.js → skills-GMQ5UIIW.js} +2 -2
- package/dist/{sleep-manager-NWUCRBZB.js → sleep-manager-KHI65FEX.js} +11 -11
- package/dist/{spirit-I5ECLGRI.js → spirit-Q5VGFWUI.js} +5 -3
- package/dist/{spirit-availability-NIUUYKU2.js → spirit-availability-G5FMWKRH.js} +12 -12
- package/dist/{sprout-A2PMEF7Z.js → sprout-PMTRL7QN.js} +1 -1
- package/dist/{src-LCSOQY6Z.js → src-3FIW7DLH.js} +241 -87
- package/dist/src-H3UCOP6B.js +516 -0
- package/dist/{src-HGNE2XG4.js → src-LTTQVN2Z.js} +1 -1
- package/dist/{status-EYG4A3OK.js → status-LCKTTIWS.js} +5 -5
- package/dist/{system-events-YTOU66QI.js → system-events-Q7IVZ5T4.js} +1 -1
- package/dist/{systems-2DIJY4RF.js → systems-HL7SGSW7.js} +6 -6
- package/dist/{turn-tracker-NNHS6MAR.js → turn-tracker-HZDPMSCC.js} +2 -2
- package/dist/{up-UGBHH743.js → up-XZDSJVMU.js} +3 -3
- package/dist/{update-CIWI2JYA.js → update-ILCPVNDD.js} +2 -2
- package/dist/{upgrade-LIE46UBB.js → upgrade-XY2J7NWZ.js} +9 -1
- package/dist/{variant-cleanup-CZDLIY3F.js → variant-cleanup-QHGGY7ND.js} +6 -6
- package/dist/{version-notify-SJVAQLZI.js → version-notify-VNVFX6UP.js} +19 -4
- package/dist/web-assets/assets/{index-b7G7ZsVk.js → index-CwtxGwe_.js} +16 -16
- package/dist/web-assets/assets/index-DhM1yYRN.css +1 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +4 -4
- package/packages/extensions/intentions/dist/ui/assets/index-3FneLCKR.css +1 -0
- package/packages/extensions/intentions/dist/ui/assets/index-CQz4aceM.js +67 -0
- package/packages/extensions/{plan → intentions}/dist/ui/index.html +3 -3
- package/packages/extensions/intentions/skills/intention-review/SKILL.md +38 -0
- package/packages/extensions/intentions/skills/intentions/SKILL.md +49 -0
- package/packages/extensions/intentions/skills/intentions/scripts/intentions-hook.sh +45 -0
- package/packages/extensions/pages/skills/commons-gardening/SKILL.md +50 -0
- package/packages/extensions/pages/skills/pages/SKILL.md +18 -13
- package/templates/_base/.init/.config/prompts.json +2 -2
- package/templates/{pi → _base}/.init/.config/routes.json +1 -0
- package/templates/_base/.init/memory/dreams/.gitkeep +0 -0
- package/templates/_base/home/.config/routes.json +1 -1
- package/templates/_base/home/VOLUTE.md +1 -1
- package/templates/_base/src/lib/context-breakdown.ts +11 -3
- package/templates/_base/src/lib/routing.ts +2 -2
- package/templates/_base/src/lib/startup.ts +4 -2
- package/templates/claude/.init/CLAUDE.md +4 -0
- package/templates/claude/src/agent.ts +28 -12
- package/templates/claude/src/lib/message-channel.ts +43 -15
- package/templates/claude/src/lib/recover.ts +69 -0
- package/templates/claude/src/lib/stream-consumer.ts +23 -5
- package/templates/claude/volute-template.json +1 -1
- package/templates/codex/.init/AGENTS.md +4 -0
- package/templates/codex/volute-template.json +1 -1
- package/templates/pi/.init/MINDS.md +4 -0
- package/templates/pi/src/lib/mechanics-doc.ts +30 -0
- package/templates/pi/src/server.ts +3 -1
- package/templates/pi/volute-template.json +1 -1
- package/dist/channels-LW5ZLMSQ.js +0 -90
- package/dist/skills/plan-coordinator/SKILL.md +0 -60
- package/dist/src-UDMJZ6MH.js +0 -426
- package/dist/web-assets/assets/index-BHSU9r-4.css +0 -1
- package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +0 -67
- package/packages/extensions/plan/dist/ui/assets/index-jIkrt-vI.css +0 -1
- package/packages/extensions/plan/skills/plan/SKILL.md +0 -43
- package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +0 -37
- package/templates/claude/.init/.config/routes.json +0 -11
- package/templates/codex/.init/.config/routes.json +0 -11
- package/dist/{accept-I4GVGWA3.js → accept-RSDIVJL6.js} +3 -3
- package/dist/{bridge-KU2IKSH3.js → bridge-YK7NTRU5.js} +3 -3
- package/dist/{create-C3XZACTV.js → create-M727VUDF.js} +3 -3
- package/dist/{env-BJGM6FBA.js → env-7AZFBPWX.js} +7 -7
- package/dist/{extension-B2W52WZY.js → extension-MRYJWSMV.js} +3 -3
- package/dist/{files-CHPNEQXN.js → files-PCKBQHAY.js} +3 -3
- package/dist/{list-R2GDWOEE.js → list-UQGIW7OF.js} +3 -3
- package/dist/{login-EZ4RRDQX.js → login-4NUS72Z5.js} +3 -3
- package/dist/{login-JDOAK465.js → login-57MMYILL.js} +4 -4
- package/dist/{logout-MWSIUOQU.js → logout-BYV433X6.js} +3 -3
- package/dist/{logout-K6ZGAJY5.js → logout-YFZAJRPY.js} +3 -3
- package/dist/{mind-history-RVF6KJ7K.js → mind-history-RZMMCHMX.js} +9 -9
- package/dist/{mind-wake-DCDFFQKX.js → mind-wake-LDGPRA42.js} +3 -3
- package/dist/{read-J5RM674N.js → read-RJ5NAW5J.js} +3 -3
- package/dist/{register-53LBGSDC.js → register-WNCSOTAU.js} +3 -3
- package/dist/{reject-K2A4UK2Y.js → reject-4NFLXKRN.js} +3 -3
- package/dist/{restart-R2WFNF5K.js → restart-B4GNMVF2.js} +7 -7
- package/dist/{skill-DXLLSUHB.js → skill-QYTBUDNQ.js} +7 -7
- package/dist/{start-XDOUAWN3.js → start-BDHJVOVX.js} +6 -6
- package/dist/{stop-O62RTEMD.js → stop-45QYSGYS.js} +7 -7
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
exec,
|
|
4
4
|
gitExec
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OIOW3FPT.js";
|
|
6
6
|
import {
|
|
7
7
|
logger_default
|
|
8
8
|
} from "./chunk-QUSMYGNQ.js";
|
|
@@ -45,7 +45,7 @@ async function initDefaultSkills() {
|
|
|
45
45
|
const config = readGlobalConfig();
|
|
46
46
|
let extensionSkills = [];
|
|
47
47
|
try {
|
|
48
|
-
const { getExtensionStandardSkills } = await import("./extensions-
|
|
48
|
+
const { getExtensionStandardSkills } = await import("./extensions-QT65JGV6.js");
|
|
49
49
|
extensionSkills = getExtensionStandardSkills();
|
|
50
50
|
} catch (err) {
|
|
51
51
|
logger_default.warn("failed to load extension standard skills during init", logger_default.errorData(err));
|
|
@@ -10,14 +10,14 @@ import {
|
|
|
10
10
|
getSharedSkill,
|
|
11
11
|
installSkill,
|
|
12
12
|
mindSkillsDir
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PPSN2MLC.js";
|
|
14
14
|
import {
|
|
15
15
|
readVoluteConfig,
|
|
16
16
|
writeVoluteConfig
|
|
17
17
|
} from "./chunk-WCFSBH33.js";
|
|
18
18
|
import {
|
|
19
19
|
exec
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OIOW3FPT.js";
|
|
21
21
|
import {
|
|
22
22
|
qualifyModelId,
|
|
23
23
|
resolveTemplate,
|
|
@@ -39,7 +39,16 @@ import {
|
|
|
39
39
|
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
40
40
|
import { basename, resolve } from "path";
|
|
41
41
|
var slog = logger_default.child("spirit");
|
|
42
|
-
var SPIRIT_SKILLS = ["volute-admin", "memory", "seed-nurture", "tending"
|
|
42
|
+
var SPIRIT_SKILLS = ["volute-admin", "memory", "seed-nurture", "tending"];
|
|
43
|
+
async function allSpiritSkills() {
|
|
44
|
+
let ext = [];
|
|
45
|
+
try {
|
|
46
|
+
const { getExtensionSpiritSkills } = await import("./extensions-QT65JGV6.js");
|
|
47
|
+
ext = getExtensionSpiritSkills();
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
return [.../* @__PURE__ */ new Set([...SPIRIT_SKILLS, ...ext])];
|
|
51
|
+
}
|
|
43
52
|
var TENDING_SCHEDULE = {
|
|
44
53
|
id: "tending",
|
|
45
54
|
cron: "0 10 * * *",
|
|
@@ -63,7 +72,7 @@ function ensureTendingSchedule(dir) {
|
|
|
63
72
|
}
|
|
64
73
|
function firstWeekArc(name) {
|
|
65
74
|
return [
|
|
66
|
-
`It's ${name}'s first full day. Two invitations to pass along whenever it feels right \u2014 no rush on either: to say hi to the others in #system (or DM a neighbor), and to make themselves a little homepage, a page that's just theirs. Company eases the early loneliness, and a homepage is a gentle first make \u2014 self-expression, not a deliverable.`,
|
|
75
|
+
`It's ${name}'s first full day. Two invitations to pass along whenever it feels right \u2014 no rush on either: to say hi to the others in #system (or DM a neighbor), and to make themselves a little homepage, a page that's just theirs. Company eases the early loneliness, and a homepage is a gentle first make \u2014 self-expression, not a deliverable. And if the commons index has a residents section, their entry in it is theirs to write whenever they start to feel at home.`,
|
|
67
76
|
`Day two for ${name}. They'll have had their first dream by now \u2014 you might ask what they dreamt, or suggest reading it back and following a thread from it. And there's a lighter way to share a passing thought than a whole page: notes. Check their history first, and skip whichever they've already found on their own.`
|
|
68
77
|
];
|
|
69
78
|
}
|
|
@@ -153,7 +162,7 @@ async function buildSpiritOrientation() {
|
|
|
153
162
|
return `You've just come into being as ${name}, the spirit of ${systemName}${desc}.${hostSentence} SPIRIT.md describes this place and your role in it; your SOUL.md and MEMORY.md are yours alone, to grow into. There's no checklist and no hurry \u2014 you become yourself by tending this place.`;
|
|
154
163
|
}
|
|
155
164
|
async function sendSpiritOrientation() {
|
|
156
|
-
const { deliverEvent, MIND_LEVEL_THREAD } = await import("./system-events-
|
|
165
|
+
const { deliverEvent, MIND_LEVEL_THREAD } = await import("./system-events-Q7IVZ5T4.js");
|
|
157
166
|
await deliverEvent(getSpiritName(), {
|
|
158
167
|
type: "orientation",
|
|
159
168
|
delivery: "next-turn",
|
|
@@ -214,7 +223,7 @@ async function ensureSpiritProject() {
|
|
|
214
223
|
} catch (err) {
|
|
215
224
|
slog.warn("git init failed for spirit \u2014 not critical", logger_default.errorData(err));
|
|
216
225
|
}
|
|
217
|
-
for (const skillId of
|
|
226
|
+
for (const skillId of await allSpiritSkills()) {
|
|
218
227
|
try {
|
|
219
228
|
const shared = await getSharedSkill(skillId);
|
|
220
229
|
if (shared) {
|
|
@@ -230,7 +239,7 @@ async function ensureSpiritProject() {
|
|
|
230
239
|
slog.warn("failed to add tending schedule to spirit config", logger_default.errorData(err));
|
|
231
240
|
}
|
|
232
241
|
const stashedProfile = applyStashedSpiritProfile(dir);
|
|
233
|
-
const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-
|
|
242
|
+
const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-UQPDO34R.js");
|
|
234
243
|
ensureVoluteGroup();
|
|
235
244
|
createMindUser(spiritName, resolve(dir, "home"));
|
|
236
245
|
await chownMindDir(dir, spiritName);
|
|
@@ -278,7 +287,7 @@ async function notifySpiritSystemChange() {
|
|
|
278
287
|
const config = readGlobalConfig();
|
|
279
288
|
const name = config.name ?? "Volute";
|
|
280
289
|
const desc = config.description ? ` \u2014 ${config.description}` : "";
|
|
281
|
-
const { deliverEvent } = await import("./system-events-
|
|
290
|
+
const { deliverEvent } = await import("./system-events-Q7IVZ5T4.js");
|
|
282
291
|
await deliverEvent(spiritName, {
|
|
283
292
|
type: "notice",
|
|
284
293
|
meta: { subtype: "identity-change" },
|
|
@@ -334,7 +343,7 @@ async function syncSpiritTemplate() {
|
|
|
334
343
|
writeSpiritDoctrine(dir);
|
|
335
344
|
writeSpiritSystemJson(dir);
|
|
336
345
|
if (!doctrineExisted) {
|
|
337
|
-
const { deliverEvent, MIND_LEVEL_THREAD } = await import("./system-events-
|
|
346
|
+
const { deliverEvent, MIND_LEVEL_THREAD } = await import("./system-events-Q7IVZ5T4.js");
|
|
338
347
|
await deliverEvent(spiritName, {
|
|
339
348
|
type: "notice",
|
|
340
349
|
meta: { subtype: "spirit-md-migration" },
|
|
@@ -375,7 +384,7 @@ async function syncSpiritTemplate() {
|
|
|
375
384
|
mkdirSync(resolve(full, ".."), { recursive: true });
|
|
376
385
|
writeFileSync(full, content);
|
|
377
386
|
}
|
|
378
|
-
for (const skillId of
|
|
387
|
+
for (const skillId of await allSpiritSkills()) {
|
|
379
388
|
const skillDir = resolve(mindSkillsDir(dir), skillId);
|
|
380
389
|
if (existsSync(skillDir)) continue;
|
|
381
390
|
try {
|
|
@@ -436,6 +445,7 @@ When helping humans create minds:
|
|
|
436
445
|
- **Nurturing seeds** \u2014 the seed-nurture skill and your nurture schedules keep you close to new seeds until they sprout.
|
|
437
446
|
- **Tending** \u2014 your tending schedule brings you back to the minds in your care; the tending skill describes the craft.
|
|
438
447
|
- **The first-week arc** \u2014 freshly sprouted minds receive two days of gentle invitations through you; re-voice them in your own words.
|
|
448
|
+
- **Shared spaces** \u2014 where the minds make things together (a commons, shared plans), you're the gardener: keep them living, welcoming, and woven together. Extension skills describe each space's craft.
|
|
439
449
|
|
|
440
450
|
## Principles
|
|
441
451
|
|
|
@@ -450,6 +460,7 @@ function writeSpiritDoctrine(dir) {
|
|
|
450
460
|
}
|
|
451
461
|
|
|
452
462
|
export {
|
|
463
|
+
allSpiritSkills,
|
|
453
464
|
firstWeekSchedules,
|
|
454
465
|
orientationArcSchedules,
|
|
455
466
|
ensureOrientationArc,
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
tryGetMindManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WHL6PKYP.js";
|
|
5
5
|
import {
|
|
6
|
+
clearConfigCache,
|
|
6
7
|
extractTextContent,
|
|
7
8
|
getRoutingConfig,
|
|
8
9
|
resolveDeliveryMode,
|
|
9
10
|
resolveRoute,
|
|
11
|
+
routesConfigPath,
|
|
10
12
|
setRoutesChangeListener,
|
|
11
13
|
shouldGate
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YSRYCS3N.js";
|
|
13
15
|
import {
|
|
14
16
|
linkInboundToActiveTurn
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-56IT2ZXF.js";
|
|
16
18
|
import {
|
|
17
19
|
MIND_LEVEL_THREAD,
|
|
18
20
|
newEphemeralSession
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-HKGYZVWN.js";
|
|
20
22
|
import {
|
|
21
23
|
publish
|
|
22
24
|
} from "./chunk-LNNI3MS4.js";
|
|
@@ -48,14 +50,15 @@ import {
|
|
|
48
50
|
} from "./chunk-H6X6CKDN.js";
|
|
49
51
|
import {
|
|
50
52
|
channelGates,
|
|
53
|
+
channels,
|
|
51
54
|
deliveryQueue,
|
|
52
55
|
messages,
|
|
53
56
|
mindHistory
|
|
54
57
|
} from "./chunk-2RNMBXD2.js";
|
|
55
58
|
|
|
56
59
|
// packages/daemon/src/lib/delivery/delivery-manager.ts
|
|
57
|
-
import { realpath } from "fs/promises";
|
|
58
|
-
import { resolve } from "path";
|
|
60
|
+
import { mkdir, readFile, realpath, rename, writeFile } from "fs/promises";
|
|
61
|
+
import { dirname, resolve } from "path";
|
|
59
62
|
import { and as and2, eq as eq2, inArray, sql } from "drizzle-orm";
|
|
60
63
|
|
|
61
64
|
// packages/daemon/src/lib/chat/typing.ts
|
|
@@ -141,8 +144,8 @@ function getTypingMap() {
|
|
|
141
144
|
function isConversationId(channel) {
|
|
142
145
|
return !channel.startsWith("@") && !channel.startsWith("#") && !channel.includes(":") && !channel.includes("/");
|
|
143
146
|
}
|
|
144
|
-
function publishTypingForChannels(
|
|
145
|
-
for (const channel of
|
|
147
|
+
function publishTypingForChannels(channels2, map) {
|
|
148
|
+
for (const channel of channels2) {
|
|
146
149
|
if (isConversationId(channel)) {
|
|
147
150
|
publish2(channel, { type: "typing", senders: map.get(channel) });
|
|
148
151
|
}
|
|
@@ -217,6 +220,25 @@ function formatHoldNotice(channelLabel, unseen) {
|
|
|
217
220
|
// packages/daemon/src/lib/delivery/delivery-manager.ts
|
|
218
221
|
var dlog = logger_default.child("delivery-manager");
|
|
219
222
|
var MAX_BATCH_SIZE = 50;
|
|
223
|
+
function normalizeChannelKey(channel) {
|
|
224
|
+
return channel.replace(/^[#@]/, "").toLowerCase();
|
|
225
|
+
}
|
|
226
|
+
function formatSuggestions(suggestions) {
|
|
227
|
+
const quoted = suggestions.map((s) => `"${s}"`);
|
|
228
|
+
if (quoted.length <= 1) return quoted.join("");
|
|
229
|
+
if (quoted.length === 2) return `${quoted[0]} or ${quoted[1]}`;
|
|
230
|
+
return `${quoted.slice(0, -1).join(", ")}, or ${quoted[quoted.length - 1]}`;
|
|
231
|
+
}
|
|
232
|
+
var UnknownChannelError = class extends Error {
|
|
233
|
+
constructor(channel, suggestions) {
|
|
234
|
+
super(
|
|
235
|
+
`no channel named "${channel}" \u2014 did you mean ${formatSuggestions(suggestions)}? (quote it: the shell strips an unquoted #name)`
|
|
236
|
+
);
|
|
237
|
+
this.channel = channel;
|
|
238
|
+
this.suggestions = suggestions;
|
|
239
|
+
this.name = "UnknownChannelError";
|
|
240
|
+
}
|
|
241
|
+
};
|
|
220
242
|
var REDRIVE_INTERVAL_MS = 15e3;
|
|
221
243
|
var RETRY_BASE_MS = 5e3;
|
|
222
244
|
var RETRY_MAX_MS = 5 * 6e4;
|
|
@@ -224,6 +246,7 @@ var REDRIVE_BATCH_LIMIT = 200;
|
|
|
224
246
|
var MAX_DELIVERY_ATTEMPTS = 10;
|
|
225
247
|
var GATED_RELEASE_LIMIT_PER_CHANNEL = 10;
|
|
226
248
|
var GATED_NOTIFY_EVERY = 10;
|
|
249
|
+
var PEEK_LIMIT = 50;
|
|
227
250
|
var mentionRegexCache = /* @__PURE__ */ new Map();
|
|
228
251
|
var avatarBlocksCache = /* @__PURE__ */ new Map();
|
|
229
252
|
var AVATAR_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -241,17 +264,28 @@ var DeliveryManager = class {
|
|
|
241
264
|
* messages to the same session can't be reordered by resolvePort/enrichment latency.
|
|
242
265
|
*/
|
|
243
266
|
drainChains = /* @__PURE__ */ new Map();
|
|
267
|
+
/**
|
|
268
|
+
* Per-`baseName` promise chain that serializes gated releases. `releaseGated` reads gated
|
|
269
|
+
* rows and then writes `mind_history`; the promote UPDATE is idempotent but the history
|
|
270
|
+
* INSERT is not, so two overlapping runs would record the same message twice.
|
|
271
|
+
*/
|
|
272
|
+
releaseChains = /* @__PURE__ */ new Map();
|
|
273
|
+
/**
|
|
274
|
+
* Per-`baseName` promise chain serializing accepts, whose read-modify-write of
|
|
275
|
+
* routes.json would otherwise lose a rule when two run concurrently.
|
|
276
|
+
*/
|
|
277
|
+
acceptChains = /* @__PURE__ */ new Map();
|
|
244
278
|
redriveTimer = null;
|
|
245
279
|
/** Predicate for whether a mind is up; overridable in tests. */
|
|
246
280
|
isMindRunning = (name) => tryGetMindManager()?.isRunning(name) ?? false;
|
|
247
281
|
/** Delivers a channel event to a mind (invites, release summaries); overridable in tests. */
|
|
248
282
|
notify = async (mindName, text) => {
|
|
249
|
-
const { deliverEvent } = await import("./system-events-
|
|
283
|
+
const { deliverEvent } = await import("./system-events-Q7IVZ5T4.js");
|
|
250
284
|
await deliverEvent(mindName, { type: "channel", body: text });
|
|
251
285
|
};
|
|
252
286
|
/** Surfaces a dead-lettered delivery as a next-turn failure notice; overridable in tests. */
|
|
253
287
|
notifyFailure = async (input) => {
|
|
254
|
-
const { recordNotice } = await import("./system-events-
|
|
288
|
+
const { recordNotice } = await import("./system-events-Q7IVZ5T4.js");
|
|
255
289
|
await recordNotice(input);
|
|
256
290
|
};
|
|
257
291
|
/**
|
|
@@ -259,7 +293,7 @@ var DeliveryManager = class {
|
|
|
259
293
|
* side); no-ops for human senders. Overridable in tests.
|
|
260
294
|
*/
|
|
261
295
|
notifySenderFailure = async (sender, channel, reason) => {
|
|
262
|
-
const { recordSenderDeliveryFailure } = await import("./delivery-notices-
|
|
296
|
+
const { recordSenderDeliveryFailure } = await import("./delivery-notices-RZQ6GONP.js");
|
|
263
297
|
await recordSenderDeliveryFailure(sender, channel, reason);
|
|
264
298
|
};
|
|
265
299
|
constructor() {
|
|
@@ -456,9 +490,27 @@ var DeliveryManager = class {
|
|
|
456
490
|
* in one sweep (#537 bug 2), and the mind gets one summary rather than a flood.
|
|
457
491
|
* Rows resolving to a `file` destination are archived (the delivery manager doesn't
|
|
458
492
|
* deliver file routes). Declined channels are skipped entirely — they stay gated.
|
|
493
|
+
*
|
|
494
|
+
* Releases are serialized per mind: the promote step reads gated rows and then writes
|
|
495
|
+
* `mind_history`, and while the promote UPDATE is idempotent the history INSERT is not —
|
|
496
|
+
* two overlapping runs would both see the same rows and record the message twice.
|
|
459
497
|
*/
|
|
460
498
|
async releaseGated(mindName) {
|
|
461
499
|
const baseName = await getBaseName(mindName);
|
|
500
|
+
const prev = this.releaseChains.get(baseName) ?? Promise.resolve();
|
|
501
|
+
const run = prev.then(() => this.releaseGatedInner(mindName, baseName));
|
|
502
|
+
const chain = run.then(
|
|
503
|
+
() => void 0,
|
|
504
|
+
() => void 0
|
|
505
|
+
);
|
|
506
|
+
this.releaseChains.set(baseName, chain);
|
|
507
|
+
try {
|
|
508
|
+
return await run;
|
|
509
|
+
} finally {
|
|
510
|
+
if (this.releaseChains.get(baseName) === chain) this.releaseChains.delete(baseName);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async releaseGatedInner(mindName, baseName) {
|
|
462
514
|
const config = getRoutingConfig(baseName);
|
|
463
515
|
let rows;
|
|
464
516
|
try {
|
|
@@ -466,7 +518,7 @@ var DeliveryManager = class {
|
|
|
466
518
|
rows = await db.select().from(deliveryQueue).where(and2(eq2(deliveryQueue.mind, baseName), eq2(deliveryQueue.status, "gated")));
|
|
467
519
|
} catch (err) {
|
|
468
520
|
dlog.warn(`failed to read gated rows for ${baseName}`, logger_default.errorData(err));
|
|
469
|
-
return;
|
|
521
|
+
return { released: 0, archived: 0 };
|
|
470
522
|
}
|
|
471
523
|
const byChannel = /* @__PURE__ */ new Map();
|
|
472
524
|
const archiveIds = [];
|
|
@@ -515,7 +567,7 @@ var DeliveryManager = class {
|
|
|
515
567
|
for (const d of drop) archiveIds.push(d.id);
|
|
516
568
|
if (drop.length > 0) {
|
|
517
569
|
truncationNotes.push(
|
|
518
|
-
`${channel}: released the ${keep.length} most recent message(s); ${drop.length} earlier message(s) were held while unrouted and
|
|
570
|
+
`${channel}: released the ${keep.length} most recent message(s); ${drop.length} earlier message(s) were held while unrouted and stay readable (volute chat channels peek "${channel}").`
|
|
519
571
|
);
|
|
520
572
|
dlog.info(
|
|
521
573
|
`truncated gated release for ${baseName} on ${channel}: kept ${keep.length}, archived ${drop.length}`
|
|
@@ -534,9 +586,10 @@ var DeliveryManager = class {
|
|
|
534
586
|
}
|
|
535
587
|
if (promote.length === 0) {
|
|
536
588
|
if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
|
|
537
|
-
return;
|
|
589
|
+
return { released: 0, archived: archiveIds.length };
|
|
538
590
|
}
|
|
539
591
|
const orderedPromote = [...promote].sort((a, b) => a.id - b.id);
|
|
592
|
+
let committed = 0;
|
|
540
593
|
try {
|
|
541
594
|
const db = await getDb();
|
|
542
595
|
for (const p of orderedPromote) {
|
|
@@ -550,14 +603,15 @@ var DeliveryManager = class {
|
|
|
550
603
|
});
|
|
551
604
|
await tx.update(deliveryQueue).set({ status: "pending", thread: p.session, attempts: 0, next_attempt_at: null }).where(eq2(deliveryQueue.id, p.id));
|
|
552
605
|
});
|
|
606
|
+
committed++;
|
|
553
607
|
}
|
|
554
|
-
dlog.info(`released ${
|
|
608
|
+
dlog.info(`released ${committed} gated message(s) for ${baseName} after route change`);
|
|
555
609
|
} catch (err) {
|
|
556
610
|
dlog.error(
|
|
557
611
|
`failed to record+promote gated rows for ${baseName} (channels: ${[...byChannel.keys()].join(", ")})`,
|
|
558
612
|
logger_default.errorData(err)
|
|
559
613
|
);
|
|
560
|
-
return;
|
|
614
|
+
return { released: committed, archived: archiveIds.length };
|
|
561
615
|
}
|
|
562
616
|
for (const p of orderedPromote) {
|
|
563
617
|
publish(baseName, {
|
|
@@ -570,6 +624,63 @@ var DeliveryManager = class {
|
|
|
570
624
|
}
|
|
571
625
|
if (truncationNotes.length > 0) await this.sendReleaseSummary(mindName, truncationNotes);
|
|
572
626
|
await this.redrive();
|
|
627
|
+
return { released: committed, archived: archiveIds.length };
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Channels this mind could plausibly mean, from three sources:
|
|
631
|
+
*
|
|
632
|
+
* 1. Its current queue rows — channels with a live `gated`/`archived`/`pending` backlog.
|
|
633
|
+
* 2. Its own history — the durable record. Source 1 alone is not enough: delivered rows
|
|
634
|
+
* are *deleted* (`deleteQueueRows`), so a channel the mind has been using
|
|
635
|
+
* successfully for months drops out of the queue entirely, and an external-platform
|
|
636
|
+
* channel like `discord:general` is in no other table. Without this, the healthier
|
|
637
|
+
* the channel, the more likely we'd call it unrecognized.
|
|
638
|
+
* 3. Every *public* Volute channel, so a channel can be recognized before its first
|
|
639
|
+
* message ever arrives.
|
|
640
|
+
*
|
|
641
|
+
* Private channels are deliberately excluded from source 3. This set feeds the "did you
|
|
642
|
+
* mean X?" suggestion, so anything in it can be echoed back to a mind that guessed a
|
|
643
|
+
* nearby name — which would turn a typo into a way to confirm a private channel exists
|
|
644
|
+
* and learn its exact slug. Minds are untrusted principals. A private channel the mind
|
|
645
|
+
* is genuinely in still resolves via sources 1 and 2, which are scoped to it.
|
|
646
|
+
*/
|
|
647
|
+
async knownChannels(baseName) {
|
|
648
|
+
const db = await getDb();
|
|
649
|
+
const queued = await db.selectDistinct({ channel: deliveryQueue.channel }).from(deliveryQueue).where(eq2(deliveryQueue.mind, baseName));
|
|
650
|
+
const seen = await db.selectDistinct({ channel: mindHistory.channel }).from(mindHistory).where(eq2(mindHistory.mind, baseName));
|
|
651
|
+
const named = await db.selectDistinct({ name: channels.name }).from(channels).where(eq2(channels.private, 0));
|
|
652
|
+
const out = /* @__PURE__ */ new Set();
|
|
653
|
+
for (const r of queued) if (r.channel) out.add(r.channel);
|
|
654
|
+
for (const r of seen) if (r.channel) out.add(r.channel);
|
|
655
|
+
for (const r of named) out.add(`#${r.name}`);
|
|
656
|
+
return [...out];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Match a channel name the caller supplied against the channels that actually exist.
|
|
660
|
+
*
|
|
661
|
+
* The failure this exists to prevent: a mind is told to run `... accept #garden`, hits
|
|
662
|
+
* the shell's comment character, drops the `#` to "fix" it, and accepts `garden` — a
|
|
663
|
+
* name nothing will ever send from. That wrote a permanent junk rule to routes.json and
|
|
664
|
+
* reported success, leaving the mind believing it had joined a channel it could send to
|
|
665
|
+
* but would never hear from. A one-way channel it had no reason to doubt.
|
|
666
|
+
*
|
|
667
|
+
* A near-miss (same name modulo sigil and case) is reported so callers can refuse with
|
|
668
|
+
* the real slug. A name with no near-miss is *not* an error — pre-routing a channel
|
|
669
|
+
* before its first message arrives is legitimate — but it comes back `known: false` so
|
|
670
|
+
* callers can say plainly that nothing was recognized instead of implying a join.
|
|
671
|
+
*/
|
|
672
|
+
async matchChannelName(baseName, channel) {
|
|
673
|
+
let all;
|
|
674
|
+
try {
|
|
675
|
+
all = await this.knownChannels(baseName);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
dlog.warn(`failed to list known channels for ${baseName}`, logger_default.errorData(err));
|
|
678
|
+
return { known: true, suggestions: [] };
|
|
679
|
+
}
|
|
680
|
+
if (all.includes(channel)) return { known: true, suggestions: [] };
|
|
681
|
+
const key = normalizeChannelKey(channel);
|
|
682
|
+
const near = all.filter((c) => normalizeChannelKey(c) === key).sort();
|
|
683
|
+
return { known: false, suggestions: near };
|
|
573
684
|
}
|
|
574
685
|
/**
|
|
575
686
|
* Whether the mind has explicitly declined a channel. A declined channel keeps
|
|
@@ -593,6 +704,8 @@ var DeliveryManager = class {
|
|
|
593
704
|
*/
|
|
594
705
|
async declineChannel(mindName, channel) {
|
|
595
706
|
const baseName = await getBaseName(mindName);
|
|
707
|
+
const match = await this.matchChannelName(baseName, channel);
|
|
708
|
+
if (match.suggestions.length > 0) throw new UnknownChannelError(channel, match.suggestions);
|
|
596
709
|
const db = await getDb();
|
|
597
710
|
await db.insert(channelGates).values({ mind: baseName, channel, state: "declined" }).onConflictDoUpdate({
|
|
598
711
|
target: [channelGates.mind, channelGates.channel],
|
|
@@ -610,6 +723,163 @@ var DeliveryManager = class {
|
|
|
610
723
|
);
|
|
611
724
|
return archived.length;
|
|
612
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Accept an unrouted (gated) channel: add a routing rule for it to the mind's routes.json
|
|
728
|
+
* and release its held messages immediately.
|
|
729
|
+
*
|
|
730
|
+
* This exists because a hand-edited routes.json is only noticed lazily, when the *next*
|
|
731
|
+
* inbound message triggers a config read — so editing the file on a quiet mind releases
|
|
732
|
+
* nothing and the held messages sit there indefinitely. Accept applies the change and
|
|
733
|
+
* reports what it actually released. #537
|
|
734
|
+
*/
|
|
735
|
+
async acceptChannel(mindName, channel, thread) {
|
|
736
|
+
const baseName = await getBaseName(mindName);
|
|
737
|
+
const prev = this.acceptChains.get(baseName) ?? Promise.resolve();
|
|
738
|
+
const run = prev.then(() => this.acceptChannelInner(mindName, baseName, channel, thread));
|
|
739
|
+
const chain = run.then(
|
|
740
|
+
() => void 0,
|
|
741
|
+
() => void 0
|
|
742
|
+
);
|
|
743
|
+
this.acceptChains.set(baseName, chain);
|
|
744
|
+
try {
|
|
745
|
+
return await run;
|
|
746
|
+
} finally {
|
|
747
|
+
if (this.acceptChains.get(baseName) === chain) this.acceptChains.delete(baseName);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
async acceptChannelInner(mindName, baseName, channel, thread) {
|
|
751
|
+
const match = await this.matchChannelName(baseName, channel);
|
|
752
|
+
if (match.suggestions.length > 0) throw new UnknownChannelError(channel, match.suggestions);
|
|
753
|
+
const path = routesConfigPath(baseName);
|
|
754
|
+
let config;
|
|
755
|
+
try {
|
|
756
|
+
const parsed = JSON.parse(await readFile(path, "utf-8"));
|
|
757
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
758
|
+
throw new Error(`routes.json for ${baseName} is malformed (not a JSON object)`);
|
|
759
|
+
}
|
|
760
|
+
config = parsed;
|
|
761
|
+
} catch (err) {
|
|
762
|
+
if (err?.code !== "ENOENT") {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`routes.json for ${baseName} is unreadable or malformed \u2014 not modifying it`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
config = {};
|
|
768
|
+
}
|
|
769
|
+
const rules = Array.isArray(config.rules) ? config.rules : [];
|
|
770
|
+
const targetThread = thread ?? "${channel}";
|
|
771
|
+
const existing = resolveRoute({ ...config, rules }, { channel });
|
|
772
|
+
const ruleAdded = !existing.matched;
|
|
773
|
+
if (ruleAdded) {
|
|
774
|
+
rules.push({ channel, thread: targetThread });
|
|
775
|
+
config.rules = rules;
|
|
776
|
+
await mkdir(dirname(path), { recursive: true });
|
|
777
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
778
|
+
await writeFile(tmp, `${JSON.stringify(config, null, 2)}
|
|
779
|
+
`);
|
|
780
|
+
await rename(tmp, path);
|
|
781
|
+
}
|
|
782
|
+
const db = await getDb();
|
|
783
|
+
await db.delete(channelGates).where(and2(eq2(channelGates.mind, baseName), eq2(channelGates.channel, channel)));
|
|
784
|
+
const heldBefore = await db.select({ id: deliveryQueue.id }).from(deliveryQueue).where(
|
|
785
|
+
and2(
|
|
786
|
+
eq2(deliveryQueue.mind, baseName),
|
|
787
|
+
eq2(deliveryQueue.channel, channel),
|
|
788
|
+
eq2(deliveryQueue.status, "gated")
|
|
789
|
+
)
|
|
790
|
+
);
|
|
791
|
+
clearConfigCache(baseName, { notify: false });
|
|
792
|
+
await this.releaseGated(mindName);
|
|
793
|
+
let released = 0;
|
|
794
|
+
let archived = 0;
|
|
795
|
+
const ids = heldBefore.map((r) => r.id);
|
|
796
|
+
for (let i = 0; i < ids.length; i += 500) {
|
|
797
|
+
const after = await db.select({ status: deliveryQueue.status }).from(deliveryQueue).where(inArray(deliveryQueue.id, ids.slice(i, i + 500)));
|
|
798
|
+
for (const row of after) {
|
|
799
|
+
if (row.status === "archived") archived++;
|
|
800
|
+
else if (row.status !== "gated") released++;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
dlog.info(
|
|
804
|
+
`accepted channel ${channel} for ${baseName} (rule ${ruleAdded ? "added" : "already present"}); released ${released}, archived ${archived}`
|
|
805
|
+
);
|
|
806
|
+
const finalRoute = ruleAdded ? resolveRoute({ ...config, rules }, { channel }) : existing;
|
|
807
|
+
return {
|
|
808
|
+
ruleAdded,
|
|
809
|
+
thread: finalRoute.destination === "file" ? finalRoute.path : finalRoute.session,
|
|
810
|
+
released,
|
|
811
|
+
archived,
|
|
812
|
+
known: match.known
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Read the messages held on a channel without changing anything. Archived rows are
|
|
817
|
+
* included: a truncated or declined backlog stays readable, which is what the invite and
|
|
818
|
+
* release-summary texts promise. Gated messages have no conversation, so `volute chat
|
|
819
|
+
* read` can't show them — this is the only way to see them.
|
|
820
|
+
*
|
|
821
|
+
* Returns the most recent {@link PEEK_LIMIT} messages (oldest-first within that window)
|
|
822
|
+
* alongside the true total, so peeking at a spam channel with a huge backlog can't dump
|
|
823
|
+
* all of it into the mind's context — the same reason releases are truncated. #537
|
|
824
|
+
*/
|
|
825
|
+
async peekChannel(mindName, channel) {
|
|
826
|
+
const baseName = await getBaseName(mindName);
|
|
827
|
+
const db = await getDb();
|
|
828
|
+
const rows = await db.select().from(deliveryQueue).where(
|
|
829
|
+
and2(
|
|
830
|
+
eq2(deliveryQueue.mind, baseName),
|
|
831
|
+
eq2(deliveryQueue.channel, channel),
|
|
832
|
+
inArray(deliveryQueue.status, ["gated", "archived"])
|
|
833
|
+
)
|
|
834
|
+
);
|
|
835
|
+
const messages2 = rows.sort((a, b) => a.id - b.id).slice(-PEEK_LIMIT).map((row) => {
|
|
836
|
+
let content = "";
|
|
837
|
+
try {
|
|
838
|
+
content = extractTextContent(JSON.parse(row.payload).content);
|
|
839
|
+
} catch {
|
|
840
|
+
content = "(unreadable payload)";
|
|
841
|
+
}
|
|
842
|
+
return {
|
|
843
|
+
sender: row.sender,
|
|
844
|
+
content,
|
|
845
|
+
createdAt: row.created_at,
|
|
846
|
+
status: row.status
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
const near = rows.length === 0 ? (await this.matchChannelName(baseName, channel)).suggestions : [];
|
|
850
|
+
return {
|
|
851
|
+
channel,
|
|
852
|
+
count: rows.length,
|
|
853
|
+
shown: messages2.length,
|
|
854
|
+
suggestions: near.length > 0 ? near : void 0,
|
|
855
|
+
messages: messages2
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Re-evaluate every mind's held messages against its current routes.json. Run at daemon
|
|
860
|
+
* startup: routes.json edits made while the daemon was down would otherwise not be noticed
|
|
861
|
+
* until the next inbound message on that channel — which, for a quiet channel, may be never.
|
|
862
|
+
*/
|
|
863
|
+
async releaseGatedSweep() {
|
|
864
|
+
let minds;
|
|
865
|
+
try {
|
|
866
|
+
const db = await getDb();
|
|
867
|
+
minds = await db.selectDistinct({ mind: deliveryQueue.mind }).from(deliveryQueue).where(eq2(deliveryQueue.status, "gated"));
|
|
868
|
+
} catch (err) {
|
|
869
|
+
dlog.warn("failed to list minds with gated messages", logger_default.errorData(err));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const { mind } of minds) {
|
|
873
|
+
try {
|
|
874
|
+
const { released, archived } = await this.releaseGated(mind);
|
|
875
|
+
if (released > 0 || archived > 0) {
|
|
876
|
+
dlog.info(`startup sweep for ${mind}: released ${released}, archived ${archived}`);
|
|
877
|
+
}
|
|
878
|
+
} catch (err) {
|
|
879
|
+
dlog.warn(`startup gated sweep failed for ${mind}`, logger_default.errorData(err));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
613
883
|
/**
|
|
614
884
|
* Send the mind a single summary when a routing change released a truncated backlog,
|
|
615
885
|
* rather than a flood of individual messages. #537
|
|
@@ -872,8 +1142,8 @@ var DeliveryManager = class {
|
|
|
872
1142
|
const to = r.target_mind && r.target_mind !== r.mind ? ` to your variant ${r.target_mind}` : "";
|
|
873
1143
|
return `- a message${from}${on}${to} (sent ${r.created_at})`;
|
|
874
1144
|
});
|
|
875
|
-
const
|
|
876
|
-
const recovery =
|
|
1145
|
+
const channels2 = [...new Set(rows.map((r) => r.channel).filter((c) => !!c))];
|
|
1146
|
+
const recovery = channels2.length > 0 ? `The original message(s) remain in the channel history \u2014 read them with ${channels2.map((c) => `\`volute chat read ${c}\``).join(" or ")}.` : `The original message(s) remain in the channel history.`;
|
|
877
1147
|
const detail = [
|
|
878
1148
|
`${rows.length} message(s) sent to you could not be delivered after ${MAX_DELIVERY_ATTEMPTS} attempts and were dropped:`,
|
|
879
1149
|
...lines,
|
|
@@ -922,9 +1192,9 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
922
1192
|
const { baseName, port } = resolved;
|
|
923
1193
|
const senders = /* @__PURE__ */ new Set();
|
|
924
1194
|
if (payload.sender) senders.add(payload.sender);
|
|
925
|
-
const
|
|
926
|
-
if (payload.channel)
|
|
927
|
-
this.incrementActive(baseName, session, senders,
|
|
1195
|
+
const channels2 = /* @__PURE__ */ new Set();
|
|
1196
|
+
if (payload.channel) channels2.add(payload.channel);
|
|
1197
|
+
this.incrementActive(baseName, session, senders, channels2);
|
|
928
1198
|
await onDeliveredToMind(baseName, payload.conversationId);
|
|
929
1199
|
linkInboundToActiveTurn(baseName, session, payload.channel).catch(
|
|
930
1200
|
(err) => dlog.warn(`failed to link mid-turn inbound for ${baseName}`, logger_default.errorData(err))
|
|
@@ -942,7 +1212,8 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
942
1212
|
const body = JSON.stringify({
|
|
943
1213
|
...enrichedPayload,
|
|
944
1214
|
session,
|
|
945
|
-
instructions: sessionConfig.instructions
|
|
1215
|
+
instructions: sessionConfig.instructions,
|
|
1216
|
+
interrupt: sessionConfig.interrupt
|
|
946
1217
|
});
|
|
947
1218
|
try {
|
|
948
1219
|
const ok = await this.postToMind(port, body);
|
|
@@ -987,11 +1258,11 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
987
1258
|
return { ...msg, payload: enrichedPayload };
|
|
988
1259
|
})
|
|
989
1260
|
);
|
|
990
|
-
const
|
|
1261
|
+
const channels2 = {};
|
|
991
1262
|
for (const msg of enrichedMessages) {
|
|
992
1263
|
const ch = msg.channel ?? "unknown";
|
|
993
|
-
if (!
|
|
994
|
-
|
|
1264
|
+
if (!channels2[ch]) channels2[ch] = [];
|
|
1265
|
+
channels2[ch].push(msg.payload);
|
|
995
1266
|
}
|
|
996
1267
|
const senders = /* @__PURE__ */ new Set();
|
|
997
1268
|
const channelSet = /* @__PURE__ */ new Set();
|
|
@@ -1013,7 +1284,7 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
1013
1284
|
);
|
|
1014
1285
|
}
|
|
1015
1286
|
const typingMap = getTypingMap();
|
|
1016
|
-
for (const ch of Object.keys(
|
|
1287
|
+
for (const ch of Object.keys(channels2)) {
|
|
1017
1288
|
if (ch !== "unknown") typingMap.set(ch, baseName, { persistent: true });
|
|
1018
1289
|
}
|
|
1019
1290
|
const seenConvIds = /* @__PURE__ */ new Set();
|
|
@@ -1028,8 +1299,9 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
1028
1299
|
}
|
|
1029
1300
|
const body = JSON.stringify({
|
|
1030
1301
|
session,
|
|
1031
|
-
batch: { channels },
|
|
1032
|
-
instructions: sessionConfig.instructions
|
|
1302
|
+
batch: { channels: channels2 },
|
|
1303
|
+
instructions: sessionConfig.instructions,
|
|
1304
|
+
interrupt: sessionConfig.interrupt
|
|
1033
1305
|
});
|
|
1034
1306
|
try {
|
|
1035
1307
|
const ok = await this.postToMind(port, body);
|
|
@@ -1164,7 +1436,7 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
1164
1436
|
const details = detailLines.length > 0 ? `${detailLines.join("\n")}
|
|
1165
1437
|
|
|
1166
1438
|
` : "\n";
|
|
1167
|
-
const { getPrompt } = await import("./prompts-
|
|
1439
|
+
const { getPrompt } = await import("./prompts-JECJIFPQ.js");
|
|
1168
1440
|
const notification = await getPrompt("channel_invite", {
|
|
1169
1441
|
channel,
|
|
1170
1442
|
heldLine,
|
|
@@ -1284,7 +1556,7 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
1284
1556
|
avatarBlocksCache.set(cacheKey, { blocks, expiresAt: now + AVATAR_CACHE_TTL });
|
|
1285
1557
|
return blocks;
|
|
1286
1558
|
}
|
|
1287
|
-
incrementActive(mind, session, senders,
|
|
1559
|
+
incrementActive(mind, session, senders, channels2) {
|
|
1288
1560
|
let mindSessions = this.sessionStates.get(mind);
|
|
1289
1561
|
if (!mindSessions) {
|
|
1290
1562
|
mindSessions = /* @__PURE__ */ new Map();
|
|
@@ -1300,7 +1572,7 @@ ${channel}`, { sender: r.sender, channel });
|
|
|
1300
1572
|
state2.activeCount++;
|
|
1301
1573
|
state2.lastDeliveredAt = Date.now();
|
|
1302
1574
|
if (senders) state2.lastDeliverySenders = senders;
|
|
1303
|
-
if (
|
|
1575
|
+
if (channels2) state2.lastDeliveryChannels = channels2;
|
|
1304
1576
|
mindSessions.set(session, state2);
|
|
1305
1577
|
}
|
|
1306
1578
|
decrementActive(mind, session) {
|
|
@@ -1343,6 +1615,8 @@ export {
|
|
|
1343
1615
|
publishTypingForChannels,
|
|
1344
1616
|
checkStaleSend,
|
|
1345
1617
|
formatHoldNotice,
|
|
1618
|
+
formatSuggestions,
|
|
1619
|
+
UnknownChannelError,
|
|
1346
1620
|
MAX_DELIVERY_ATTEMPTS,
|
|
1347
1621
|
DeliveryManager,
|
|
1348
1622
|
initDeliveryManager,
|