volute 0.33.0 → 0.35.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 -6
- package/dist/accept-ZBDVVCEU.js +42 -0
- package/dist/activity-events-ZW4SDL2C.js +15 -0
- package/dist/{ai-service-SBY2WG7O.js → ai-service-LURBEDDB.js} +6 -6
- package/dist/{api-client-YPKOZP2O.js → api-client-3A77HMH7.js} +2 -2
- package/dist/api.d.ts +1 -5195
- package/dist/{archive-INXYFVCW.js → archive-ESU2FUN4.js} +4 -4
- package/dist/{auth-GKCDSO4T.js → auth-WX4TESEI.js} +6 -6
- package/dist/bridge-PXIO6PS2.js +206 -0
- package/dist/chat-QXAJF3FU.js +51 -0
- package/dist/{chunk-NNB4WIG7.js → chunk-2TGZJFAT.js} +3 -3
- package/dist/{chunk-6LXAAQ43.js → chunk-33ODGMFZ.js} +1 -1
- package/dist/{chunk-RPZZSXV3.js → chunk-5N7Y5WAM.js} +21 -2
- package/dist/chunk-5T5YMX6S.js +23 -0
- package/dist/{chunk-7J3HEVR7.js → chunk-5XJYUFZH.js} +28 -16
- package/dist/chunk-7KJOFUNN.js +22 -0
- package/dist/{chunk-2NGTS5UU.js → chunk-A2ZLHBHG.js} +2 -2
- package/dist/{chunk-KIEPMIM5.js → chunk-AN2W47GW.js} +2 -2
- package/dist/{chunk-G53F3JA4.js → chunk-AOB6GVRM.js} +1 -1
- package/dist/{chunk-LRCG2JLP.js → chunk-BDYXIWA5.js} +9 -5
- package/dist/{chunk-YUIHSKR6.js → chunk-BKF4WQCY.js} +2 -2
- package/dist/{chunk-N432I7QH.js → chunk-BMZQYACC.js} +2 -2
- package/dist/{chunk-NAOW2CLO.js → chunk-BTY4WNFE.js} +1 -1
- package/dist/{chunk-ALEF47VT.js → chunk-BV65KRHM.js} +2 -2
- package/dist/{chunk-KVK2DLWI.js → chunk-CORXD635.js} +4 -4
- package/dist/{chunk-PVY5W6QN.js → chunk-F7ZNLYKZ.js} +2 -2
- package/dist/{chunk-QTUVYI7W.js → chunk-FT5KETXZ.js} +3 -3
- package/dist/{chunk-C7I35G4R.js → chunk-IJHIXLVN.js} +44 -8
- package/dist/{chunk-JUKK7FPS.js → chunk-J6CJQDWI.js} +37 -28
- package/dist/{chunk-4RQBJWQX.js → chunk-LOPXTW6H.js} +1 -1
- package/dist/{chunk-RSX4OPZY.js → chunk-MDJGMOSD.js} +8 -137
- package/dist/{chunk-LOEJ4HPQ.js → chunk-N446KRP7.js} +3 -3
- package/dist/{chunk-I5KY25PQ.js → chunk-N5LMGYXX.js} +2 -2
- package/dist/{chunk-G6BSYHPK.js → chunk-NJK5SDGR.js} +1 -1
- package/dist/{chunk-D424ZQGI.js → chunk-O7IGP7ZW.js} +11 -3
- package/dist/{chunk-M7UL5S3Q.js → chunk-OTC67N2Z.js} +2 -2
- package/dist/{chunk-GY5HBI7A.js → chunk-PWQ2ITYG.js} +4 -4
- package/dist/{chunk-KTLFDYPT.js → chunk-QCH6K235.js} +1 -1
- package/dist/chunk-QHG4OMZL.js +145 -0
- package/dist/{chunk-SKLSMHXO.js → chunk-QWTR6AWZ.js} +3 -3
- package/dist/chunk-TXSA4Q3V.js +116 -0
- package/dist/{chunk-VH33ZWMW.js → chunk-VHJRZM2S.js} +2 -2
- package/dist/{chunk-SSI47XP2.js → chunk-VHWGEJ4V.js} +1 -1
- package/dist/chunk-VY3RB2V7.js +164 -0
- package/dist/chunk-WJPROOU5.js +8314 -0
- package/dist/{chunk-RVGLDGMI.js → chunk-WZRZFFCL.js} +25 -27
- package/dist/{chunk-JYVGHWEJ.js → chunk-XRQSAMX2.js} +4 -4
- package/dist/{chunk-OYAKCAVY.js → chunk-ZSR72JB3.js} +1 -1
- package/dist/{chunk-UKVWJRKN.js → chunk-ZX7EAV5J.js} +17 -7
- package/dist/cli.js +90 -29
- package/dist/clock-HSEKS5AR.js +289 -0
- package/dist/{cloud-sync-4NWLMFVH.js → cloud-sync-6JL4C24T.js} +22 -23
- package/dist/config-UTS7QULS.js +76 -0
- package/dist/connectors/discord-bridge.js +4 -4
- package/dist/connectors/slack-bridge.js +4 -4
- package/dist/connectors/telegram-bridge.js +4 -4
- package/dist/{conversations-AWI5SZW2.js → conversations-2PW57WO2.js} +6 -6
- package/dist/create-5BPOOJAN.js +75 -0
- package/dist/create-UVCK2CS6.js +50 -0
- package/dist/daemon-client-RVIKXGFQ.js +12 -0
- package/dist/daemon-restart-HSZ3BCX5.js +65 -0
- package/dist/daemon.js +1349 -1211
- package/dist/db-BDMH4SZ2.js +20 -0
- package/dist/db-BVBJ57TU.js +9 -0
- package/dist/delete-L5PAVDGQ.js +42 -0
- package/dist/delivery-manager-H5ZVBMCQ.js +31 -0
- package/dist/{delivery-router-FL45JL7N.js → delivery-router-HEJSJAHQ.js} +5 -5
- package/dist/down-74VXM45A.js +17 -0
- package/dist/env-E4XHO2BI.js +223 -0
- package/dist/exec-PY7THYH4.js +17 -0
- package/dist/export-OAS6QVBN.js +113 -0
- package/dist/extension-D74CNM7G.js +89 -0
- package/dist/extensions-XDDFY72A.js +49 -0
- package/dist/files-CWTK6V3H.js +53 -0
- package/dist/import-5A3T7QV4.js +143 -0
- package/dist/{isolation-LLAYQYDY.js → isolation-TK5RX2WM.js} +4 -4
- package/dist/join-DF5XSJAC.js +67 -0
- package/dist/lib-DYEZMGW7.js +6588 -0
- package/dist/list-PDMQM7ZV.js +53 -0
- package/dist/login-7TE6CIZF.js +60 -0
- package/dist/login-GOTAYLXP.js +51 -0
- package/dist/logout-6KIA74EV.js +29 -0
- package/dist/logout-T4XS6LRU.js +50 -0
- package/dist/message-delivery-GRC4W6P7.js +41 -0
- package/dist/mind-5IEYKV7I.js +97 -0
- package/dist/mind-activity-tracker-QBLIV7ZJ.js +18 -0
- package/dist/mind-history-IE2QH7U5.js +275 -0
- package/dist/mind-list-GEWHWAL4.js +38 -0
- package/dist/mind-manager-HFLB5653.js +31 -0
- package/dist/mind-profile-DCBDVF5B.js +53 -0
- package/dist/mind-service-X2CAA6W6.js +37 -0
- package/dist/mind-sleep-ITCF6OQA.js +47 -0
- package/dist/mind-status-X4SX3YUG.js +65 -0
- package/dist/mind-wake-KXMKMGWX.js +42 -0
- package/dist/{package-U3VFO273.js → package-D2FSVFAX.js} +11 -8
- package/dist/read-67VRP2DO.js +91 -0
- package/dist/{read-stdin-HQJ7774D.js → read-stdin-3X5VYKNS.js} +2 -2
- package/dist/register-SB7NXCOE.js +51 -0
- package/dist/{registry-PJ4S5PHQ.js → registry-GBSNW3HG.js} +3 -3
- package/dist/reject-MUR2KWJ4.js +40 -0
- package/dist/restart-5EGG4JXU.js +42 -0
- package/dist/{sandbox-GJOK4QLQ.js → sandbox-R37VIU36.js} +6 -6
- package/dist/scheduler-Y7O4CJXL.js +31 -0
- package/dist/{schema-PA3M5ZKH.js → schema-XVZ2CLKW.js} +4 -2
- package/dist/{seed-QDYVLG74.js → seed-EQORWX77.js} +3 -3
- package/dist/seed-check-KJNTL72M.js +35 -0
- package/dist/seed-cmd-ZM2XGVU2.js +30 -0
- package/dist/seed-create-DRWGGHEI.js +113 -0
- package/dist/seed-sprout-JYXGXOP3.js +148 -0
- package/dist/send-JBJJQ7CA.js +409 -0
- package/dist/service-WNPCNHOX.js +121 -0
- package/dist/{setup-XMCBE3LF.js → setup-BJ4YAY26.js} +155 -129
- package/dist/{setup-TISPCO22.js → setup-RHJRFURI.js} +4 -4
- package/dist/skill-TAAKEYBV.js +389 -0
- package/dist/skills/plan-coordinator/SKILL.md +60 -0
- package/dist/skills/volute-mind/SKILL.md +9 -227
- package/dist/skills/volute-mind/references/extensions.md +34 -0
- package/dist/skills/volute-mind/references/integrations.md +48 -0
- package/dist/skills/volute-mind/references/routing.md +86 -0
- package/dist/skills/volute-mind/references/sleep.md +33 -0
- package/dist/skills/volute-mind/references/variants.md +31 -0
- package/dist/{skills-7FV7EJTE.js → skills-EKMCQ46K.js} +12 -8
- package/dist/sleep-manager-7KFK3USC.js +35 -0
- package/dist/spirit-ZFRDXMG7.js +23 -0
- package/dist/split-AWVOYOPZ.js +64 -0
- package/dist/{sprout-WKLZXUIQ.js → sprout-HE4TITMK.js} +3 -3
- package/dist/start-3UXOPXQG.js +39 -0
- package/dist/status-ZK34WYIM.js +125 -0
- package/dist/stop-3XYIBGFM.js +41 -0
- package/dist/system-chat-IDPHYHY4.js +35 -0
- package/dist/systems-O43WGQY6.js +52 -0
- package/dist/{tailscale-XHQBZROW.js → tailscale-ZIZ2HWJ5.js} +5 -5
- package/dist/template-hash-A7FNHTB7.js +9 -0
- package/dist/up-77ICEDEW.js +19 -0
- package/dist/update-ANE5ZM7F.js +225 -0
- package/dist/{update-check-ZD6OOIYQ.js → update-check-UV55CBEP.js} +4 -4
- package/dist/upgrade-ZMDGC7M2.js +74 -0
- package/dist/variant-QWL2WSRI.js +62 -0
- package/dist/{version-notify-NBI2MTJO.js → version-notify-FXSEMXWW.js} +29 -28
- package/dist/{volute-config-HD7WWUQC.js → volute-config-D2XVS2YI.js} +2 -2
- package/dist/web-assets/assets/index-BhxWKvbB.css +1 -0
- package/dist/web-assets/assets/index-CHVKJ9II.js +75 -0
- package/dist/web-assets/ext-theme.css +48 -9
- package/dist/web-assets/index.html +2 -2
- package/dist/web-assets/sw.js +117 -0
- package/drizzle/0005_meta_summaries.sql +15 -0
- package/drizzle/meta/0005_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +10 -7
- package/packages/extensions/pages/dist/ui/assets/index-DKZLNMED.js +2 -0
- package/packages/extensions/pages/dist/ui/index.html +1 -1
- package/packages/extensions/pages/skills/pages/SKILL.md +84 -9
- package/packages/extensions/plan/dist/ui/assets/index-CJj2gZnZ.css +1 -0
- package/packages/extensions/plan/dist/ui/assets/index-FMEJmvQz.js +61 -0
- package/packages/extensions/plan/dist/ui/index.html +14 -0
- package/packages/extensions/plan/skills/plan/SKILL.md +43 -0
- package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +37 -0
- package/templates/_base/home/VOLUTE.md +12 -19
- package/templates/_base/src/lib/auto-commit.ts +8 -8
- package/templates/_base/src/lib/context-breakdown.ts +450 -0
- package/templates/_base/src/lib/format-prefix.ts +17 -0
- package/templates/_base/src/lib/hook-loader.ts +8 -2
- package/templates/_base/src/lib/router.ts +75 -33
- package/templates/_base/src/lib/routing.ts +4 -1
- package/templates/_base/src/lib/startup.ts +16 -8
- package/templates/_base/src/lib/types.ts +2 -1
- package/templates/_base/src/lib/volute-server.ts +75 -8
- package/templates/claude/.init/CLAUDE.md +4 -10
- package/templates/claude/package.json.tmpl +1 -0
- package/templates/claude/src/agent.ts +108 -33
- package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
- package/templates/claude/src/lib/stream-consumer.ts +2 -2
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/package.json.tmpl +1 -0
- package/templates/codex/src/agent.ts +80 -8
- package/templates/codex/src/server.ts +1 -4
- package/templates/pi/package.json.tmpl +1 -0
- package/templates/pi/src/agent.ts +115 -36
- package/templates/pi/src/lib/event-handler.ts +22 -7
- package/templates/pi/src/lib/reply-instructions-extension.ts +23 -4
- package/templates/pi/src/lib/subagents.ts +20 -17
- package/templates/pi/src/server.ts +2 -5
- package/dist/accept-D5VBM7JW.js +0 -42
- package/dist/activity-events-XJO3P4RR.js +0 -15
- package/dist/bridge-TXWWPPOJ.js +0 -207
- package/dist/chat-U5ZOME3O.js +0 -68
- package/dist/chunk-3Z2DPESO.js +0 -3634
- package/dist/chunk-A2A4KLFE.js +0 -1528
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/chunk-NPKSDYA2.js +0 -156
- package/dist/chunk-PB65JZK2.js +0 -85
- package/dist/clock-BVH3V6E3.js +0 -266
- package/dist/config-H2H4UIF7.js +0 -72
- package/dist/create-2FK7Z46Y.js +0 -44
- package/dist/create-YWD2TIP4.js +0 -71
- package/dist/daemon-client-6QXHZ7US.js +0 -12
- package/dist/daemon-restart-GOBUKLX7.js +0 -52
- package/dist/db-F34YLV7D.js +0 -9
- package/dist/db-RA45JBFG.js +0 -16
- package/dist/delete-QTGWEDBI.js +0 -35
- package/dist/delivery-manager-PFAKEJTC.js +0 -32
- package/dist/down-FWWTEKXM.js +0 -15
- package/dist/env-JCOF2222.js +0 -191
- package/dist/export-SUYRLI5Q.js +0 -112
- package/dist/extension-OBTGKQQD.js +0 -175
- package/dist/extensions-KYNTVTMO.js +0 -30
- package/dist/files-65PMW5IK.js +0 -47
- package/dist/history-DKCDI3JO.js +0 -128
- package/dist/import-DDUFE7AY.js +0 -23
- package/dist/join-I5QEE3LG.js +0 -66
- package/dist/list-JQ463EDA.js +0 -41
- package/dist/login-D7ETSU4R.js +0 -47
- package/dist/login-RIJF2F4G.js +0 -47
- package/dist/logout-5MLHZALK.js +0 -40
- package/dist/logout-UZJRGY4Z.js +0 -21
- package/dist/message-delivery-DFF5SJRM.js +0 -42
- package/dist/mind-IOJFLEM5.js +0 -108
- package/dist/mind-activity-tracker-F6O4Q2SL.js +0 -18
- package/dist/mind-list-WUPMQDYQ.js +0 -30
- package/dist/mind-manager-NBJF5D26.js +0 -32
- package/dist/mind-profile-P67FEHOY.js +0 -47
- package/dist/mind-service-2MQ6UK5N.js +0 -38
- package/dist/mind-sleep-WW2IX7JT.js +0 -42
- package/dist/mind-status-L3EFFRPR.js +0 -56
- package/dist/mind-wake-VSSGW465.js +0 -37
- package/dist/read-EBY56C33.js +0 -75
- package/dist/register-HD74C4TT.js +0 -47
- package/dist/reject-UJKFBHRO.js +0 -40
- package/dist/restart-3UCMRUVC.js +0 -33
- package/dist/scheduler-ZZ7XGQG6.js +0 -32
- package/dist/seed-check-S2IX25RL.js +0 -32
- package/dist/seed-cmd-DKOUFEAU.js +0 -36
- package/dist/seed-create-4XBBOLRH.js +0 -112
- package/dist/seed-sprout-GQEIIQRT.js +0 -132
- package/dist/send-QIV2INHB.js +0 -373
- package/dist/skill-PSQGRRJX.js +0 -358
- package/dist/skills/shared-files/SKILL.md +0 -44
- package/dist/skills/shared-files/scripts/merge.ts +0 -72
- package/dist/skills/shared-files/scripts/pull.ts +0 -52
- package/dist/sleep-manager-JTXSN7NV.js +0 -36
- package/dist/spirit-VRONKFMF.js +0 -23
- package/dist/split-STOROBYJ.js +0 -63
- package/dist/start-K2NCUUCG.js +0 -33
- package/dist/status-3JBTFSMI.js +0 -115
- package/dist/stop-H26JZDXF.js +0 -32
- package/dist/system-chat-JAPOJ3KE.js +0 -36
- package/dist/systems-XRI52VCH.js +0 -61
- package/dist/template-hash-A6VVKOXJ.js +0 -9
- package/dist/up-M5AS6SBV.js +0 -18
- package/dist/update-UD543CXX.js +0 -215
- package/dist/upgrade-O4Q7WJM3.js +0 -67
- package/dist/variant-7TGZHOU3.js +0 -41
- package/dist/web-assets/assets/index-CWJrVveV.css +0 -1
- package/dist/web-assets/assets/index-DJt14FRI.js +0 -75
- package/packages/extensions/pages/dist/ui/assets/index-tLTROSk5.js +0 -2
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
compactTime,
|
|
5
|
+
compactTimestamp,
|
|
6
|
+
formatPrefix,
|
|
7
|
+
formatTypingSuffix,
|
|
8
|
+
} from "./format-prefix.js";
|
|
4
9
|
import { log, warn } from "./logger.js";
|
|
5
10
|
import {
|
|
6
11
|
type BatchConfig,
|
|
@@ -11,6 +16,12 @@ import {
|
|
|
11
16
|
import { loadPrompts } from "./startup.js";
|
|
12
17
|
import type { ChannelMeta, HandlerResolver, Listener, VoluteContentPart } from "./types.js";
|
|
13
18
|
|
|
19
|
+
/** Shape of a single message in a batch payload (subset of daemon DeliveryPayload). */
|
|
20
|
+
export type BatchMessage = {
|
|
21
|
+
sender?: string | null;
|
|
22
|
+
content: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
14
25
|
export type Router = {
|
|
15
26
|
route(
|
|
16
27
|
content: VoluteContentPart[],
|
|
@@ -25,7 +36,7 @@ export type Router = {
|
|
|
25
36
|
listener?: Listener,
|
|
26
37
|
): { messageId: string; unsubscribe: () => void };
|
|
27
38
|
dispatchBatch(
|
|
28
|
-
batch: { channels: Record<string,
|
|
39
|
+
batch: { channels: Record<string, BatchMessage[]> },
|
|
29
40
|
session: string,
|
|
30
41
|
meta: ChannelMeta,
|
|
31
42
|
): void;
|
|
@@ -55,7 +66,7 @@ function generateMessageId(): string {
|
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
function applyPrefix(content: VoluteContentPart[], meta: ChannelMeta): VoluteContentPart[] {
|
|
58
|
-
const time =
|
|
69
|
+
const time = compactTimestamp();
|
|
59
70
|
const prefix = formatPrefix(meta, time);
|
|
60
71
|
if (!prefix) return content;
|
|
61
72
|
|
|
@@ -129,7 +140,7 @@ function formatInviteNotification(
|
|
|
129
140
|
filePath: string,
|
|
130
141
|
messageText: string,
|
|
131
142
|
): string {
|
|
132
|
-
const time =
|
|
143
|
+
const time = compactTimestamp();
|
|
133
144
|
const prompts = loadPrompts();
|
|
134
145
|
|
|
135
146
|
const headerLines: string[] = [];
|
|
@@ -172,6 +183,18 @@ export function createRouter(options: {
|
|
|
172
183
|
}): Router {
|
|
173
184
|
const batchBuffers = new Map<string, BatchBuffer>();
|
|
174
185
|
const pendingChannels = new Set<string>();
|
|
186
|
+
const instructedSessions = new Set<string>();
|
|
187
|
+
|
|
188
|
+
/** Prepend session instructions only on the first message per session. */
|
|
189
|
+
function prependInstructionsOnce(
|
|
190
|
+
content: VoluteContentPart[],
|
|
191
|
+
instructions: string | undefined,
|
|
192
|
+
sessionName: string,
|
|
193
|
+
): VoluteContentPart[] {
|
|
194
|
+
if (!instructions || instructedSessions.has(sessionName)) return content;
|
|
195
|
+
instructedSessions.add(sessionName);
|
|
196
|
+
return prependInstructions(content, instructions);
|
|
197
|
+
}
|
|
175
198
|
|
|
176
199
|
function flushBatch(key: string) {
|
|
177
200
|
const buffer = batchBuffers.get(key);
|
|
@@ -191,16 +214,22 @@ export function createRouter(options: {
|
|
|
191
214
|
const uri = msg.channel ?? "unknown";
|
|
192
215
|
channelCounts.set(uri, (channelCounts.get(uri) ?? 0) + 1);
|
|
193
216
|
}
|
|
194
|
-
|
|
217
|
+
let header: string;
|
|
218
|
+
if (channelCounts.size === 1) {
|
|
219
|
+
const [uri] = [...channelCounts.keys()];
|
|
195
220
|
const msg = messages.find((m) => m.channel === uri);
|
|
196
|
-
const display = msg?.channelName
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
221
|
+
const display = msg?.channelName ? `#${msg.channelName}` : uri;
|
|
222
|
+
header = `[Batch: ${messages.length} message${messages.length === 1 ? "" : "s"} from ${display}]`;
|
|
223
|
+
} else {
|
|
224
|
+
const channelLabels = [...channelCounts.entries()].map(([uri, n]) => {
|
|
225
|
+
const msg = messages.find((m) => m.channel === uri);
|
|
226
|
+
const display = msg?.channelName
|
|
227
|
+
? `#${msg.channelName}${msg.serverName ? ` in ${msg.serverName}` : ""}`
|
|
228
|
+
: uri;
|
|
229
|
+
return `${n} from ${display}`;
|
|
230
|
+
});
|
|
231
|
+
header = `[Batch: ${messages.length} messages — ${channelLabels.join(", ")}]`;
|
|
232
|
+
}
|
|
204
233
|
// Include channel URI per message when batch spans multiple channels
|
|
205
234
|
const multiChannel = channelCounts.size > 1;
|
|
206
235
|
const body = messages
|
|
@@ -222,7 +251,7 @@ export function createRouter(options: {
|
|
|
222
251
|
// Resolve session config for instructions
|
|
223
252
|
const config = options.configPath ? loadRoutingConfig(options.configPath) : {};
|
|
224
253
|
const sessionConfig = resolveSessionConfig(config, buffer.sessionName);
|
|
225
|
-
content =
|
|
254
|
+
content = prependInstructionsOnce(content, sessionConfig.instructions, buffer.sessionName);
|
|
226
255
|
|
|
227
256
|
const messageId = generateMessageId();
|
|
228
257
|
const handler = options.mindHandler(buffer.sessionName);
|
|
@@ -296,13 +325,23 @@ export function createRouter(options: {
|
|
|
296
325
|
// Resolve session config for instructions
|
|
297
326
|
const config = options.configPath ? loadRoutingConfig(options.configPath) : {};
|
|
298
327
|
const sessionConfig = resolveSessionConfig(config, session);
|
|
299
|
-
const withInstructions =
|
|
328
|
+
const withInstructions = prependInstructionsOnce(
|
|
329
|
+
withTyping,
|
|
330
|
+
sessionConfig.instructions,
|
|
331
|
+
session,
|
|
332
|
+
);
|
|
300
333
|
|
|
301
334
|
const handler = options.mindHandler(session);
|
|
302
|
-
const interrupt =
|
|
335
|
+
const interrupt = meta.interrupt ?? sessionConfig.interrupt;
|
|
303
336
|
const unsubscribe = handler.handle(
|
|
304
337
|
withInstructions,
|
|
305
|
-
{
|
|
338
|
+
{
|
|
339
|
+
...meta,
|
|
340
|
+
sessionName: session,
|
|
341
|
+
messageId,
|
|
342
|
+
interrupt,
|
|
343
|
+
replyInstructions: sessionConfig.replyInstructions,
|
|
344
|
+
},
|
|
306
345
|
safeListener,
|
|
307
346
|
);
|
|
308
347
|
return { messageId, unsubscribe };
|
|
@@ -423,10 +462,7 @@ export function createRouter(options: {
|
|
|
423
462
|
channel: meta.channel,
|
|
424
463
|
channelName: meta.channelName,
|
|
425
464
|
serverName: meta.serverName,
|
|
426
|
-
timestamp:
|
|
427
|
-
hour: "numeric",
|
|
428
|
-
minute: "2-digit",
|
|
429
|
-
}),
|
|
465
|
+
timestamp: compactTime(),
|
|
430
466
|
typing: meta.typing,
|
|
431
467
|
});
|
|
432
468
|
|
|
@@ -444,7 +480,11 @@ export function createRouter(options: {
|
|
|
444
480
|
// Direct dispatch to mind
|
|
445
481
|
const formatted = applyPrefix(content, { ...meta, sessionName });
|
|
446
482
|
const withTyping = appendTypingSuffix(formatted, meta.typing);
|
|
447
|
-
const withInstructions =
|
|
483
|
+
const withInstructions = prependInstructionsOnce(
|
|
484
|
+
withTyping,
|
|
485
|
+
sessionConfig.instructions,
|
|
486
|
+
sessionName,
|
|
487
|
+
);
|
|
448
488
|
const handler = options.mindHandler(sessionName);
|
|
449
489
|
const unsubscribe = handler.handle(
|
|
450
490
|
withInstructions,
|
|
@@ -453,6 +493,7 @@ export function createRouter(options: {
|
|
|
453
493
|
sessionName,
|
|
454
494
|
messageId,
|
|
455
495
|
interrupt: sessionConfig.interrupt,
|
|
496
|
+
replyInstructions: sessionConfig.replyInstructions,
|
|
456
497
|
},
|
|
457
498
|
safeListener,
|
|
458
499
|
);
|
|
@@ -464,11 +505,11 @@ export function createRouter(options: {
|
|
|
464
505
|
* Formats messages grouped by channel into a single SDK message.
|
|
465
506
|
*/
|
|
466
507
|
function dispatchBatch(
|
|
467
|
-
batch: { channels: Record<string,
|
|
508
|
+
batch: { channels: Record<string, BatchMessage[]> },
|
|
468
509
|
session: string,
|
|
469
510
|
_meta: ChannelMeta,
|
|
470
511
|
): void {
|
|
471
|
-
const allMessages: { channel: string; payload:
|
|
512
|
+
const allMessages: { channel: string; payload: BatchMessage }[] = [];
|
|
472
513
|
for (const [channel, messages] of Object.entries(batch.channels)) {
|
|
473
514
|
for (const msg of messages) {
|
|
474
515
|
allMessages.push({ channel, payload: msg });
|
|
@@ -482,10 +523,14 @@ export function createRouter(options: {
|
|
|
482
523
|
for (const msg of allMessages) {
|
|
483
524
|
channelCounts.set(msg.channel, (channelCounts.get(msg.channel) ?? 0) + 1);
|
|
484
525
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
526
|
+
let header: string;
|
|
527
|
+
if (channelCounts.size === 1) {
|
|
528
|
+
const [ch] = [...channelCounts.keys()];
|
|
529
|
+
header = `[Batch: ${allMessages.length} message${allMessages.length === 1 ? "" : "s"} from ${ch}]`;
|
|
530
|
+
} else {
|
|
531
|
+
const channelLabels = [...channelCounts.entries()].map(([ch, n]) => `${n} from ${ch}`);
|
|
532
|
+
header = `[Batch: ${allMessages.length} messages — ${channelLabels.join(", ")}]`;
|
|
533
|
+
}
|
|
489
534
|
const multiChannel = channelCounts.size > 1;
|
|
490
535
|
|
|
491
536
|
const body = allMessages
|
|
@@ -500,10 +545,7 @@ export function createRouter(options: {
|
|
|
500
545
|
.map((p) => p.text)
|
|
501
546
|
.join("\n")
|
|
502
547
|
: JSON.stringify(m.payload.content);
|
|
503
|
-
const time =
|
|
504
|
-
hour: "numeric",
|
|
505
|
-
minute: "2-digit",
|
|
506
|
-
});
|
|
548
|
+
const time = compactTime();
|
|
507
549
|
const prefix = multiChannel
|
|
508
550
|
? `[${sender} in ${m.channel} — ${time}]`
|
|
509
551
|
: `[${sender} — ${time}]`;
|
|
@@ -516,7 +558,7 @@ export function createRouter(options: {
|
|
|
516
558
|
// Resolve session config for instructions
|
|
517
559
|
const config = options.configPath ? loadRoutingConfig(options.configPath) : {};
|
|
518
560
|
const sessionConfig = resolveSessionConfig(config, session);
|
|
519
|
-
const withInstructions =
|
|
561
|
+
const withInstructions = prependInstructionsOnce(content, sessionConfig.instructions, session);
|
|
520
562
|
|
|
521
563
|
const messageId = generateMessageId();
|
|
522
564
|
const handler = options.mindHandler(session);
|
|
@@ -23,12 +23,14 @@ export type SessionConfig = {
|
|
|
23
23
|
batch?: number | BatchConfig;
|
|
24
24
|
interrupt?: boolean;
|
|
25
25
|
instructions?: string;
|
|
26
|
+
replyInstructions?: "once" | "always" | "never";
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export type ResolvedSessionConfig = {
|
|
29
30
|
batch?: BatchConfig;
|
|
30
31
|
interrupt: boolean;
|
|
31
32
|
instructions?: string;
|
|
33
|
+
replyInstructions: "once" | "always" | "never";
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
export type RoutingConfig = {
|
|
@@ -154,7 +156,7 @@ export function resolveSessionConfig(
|
|
|
154
156
|
config: RoutingConfig,
|
|
155
157
|
sessionName: string,
|
|
156
158
|
): ResolvedSessionConfig {
|
|
157
|
-
const defaults: ResolvedSessionConfig = { interrupt: true };
|
|
159
|
+
const defaults: ResolvedSessionConfig = { interrupt: true, replyInstructions: "once" };
|
|
158
160
|
|
|
159
161
|
if (!config.sessions) return defaults;
|
|
160
162
|
|
|
@@ -165,6 +167,7 @@ export function resolveSessionConfig(
|
|
|
165
167
|
batch,
|
|
166
168
|
interrupt: sessionConfig.interrupt ?? true,
|
|
167
169
|
instructions: sessionConfig.instructions,
|
|
170
|
+
replyInstructions: sessionConfig.replyInstructions ?? "once",
|
|
168
171
|
};
|
|
169
172
|
}
|
|
170
173
|
}
|
|
@@ -23,13 +23,19 @@ export type SubagentConfig = {
|
|
|
23
23
|
maxTurns?: number;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export
|
|
26
|
+
export type MindConfig = {
|
|
27
27
|
model?: string;
|
|
28
28
|
logLevel?: "error" | "warn" | "info" | "debug";
|
|
29
29
|
compactionMessage?: string;
|
|
30
30
|
compaction?: { maxContextTokens?: number };
|
|
31
31
|
subagents?: Record<string, SubagentConfig>;
|
|
32
|
-
|
|
32
|
+
// Template-specific config fields (claude, pi, codex)
|
|
33
|
+
maxThinkingTokens?: number;
|
|
34
|
+
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
35
|
+
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function loadConfig(): MindConfig {
|
|
33
39
|
try {
|
|
34
40
|
return JSON.parse(readFileSync(resolve("home/.config/config.json"), "utf-8"));
|
|
35
41
|
} catch (err: any) {
|
|
@@ -77,12 +83,16 @@ export function loadPackageInfo(): { name: string; version: string } {
|
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Run the startup-context hook and return the generated context string.
|
|
88
|
+
* Returns null if no hook is found or it produces no output.
|
|
89
|
+
*/
|
|
90
|
+
export async function getStartupContext(): Promise<string | null> {
|
|
81
91
|
// Prefer .ts, fall back to .sh for backwards compatibility
|
|
82
92
|
const tsPath = resolve("home/.local/hooks/startup-context.ts");
|
|
83
93
|
const shPath = resolve("home/.local/hooks/startup-context.sh");
|
|
84
94
|
const scriptPath = existsSync(tsPath) ? tsPath : existsSync(shPath) ? shPath : null;
|
|
85
|
-
if (!scriptPath) return;
|
|
95
|
+
if (!scriptPath) return null;
|
|
86
96
|
|
|
87
97
|
const isTs = scriptPath.endsWith(".ts");
|
|
88
98
|
|
|
@@ -112,12 +122,10 @@ export async function handleStartupContext(sendMessage: (content: string) => voi
|
|
|
112
122
|
context = stdout.trim();
|
|
113
123
|
}
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
sendMessage(`[system] ${context}`);
|
|
117
|
-
log("server", "sent startup context");
|
|
118
|
-
}
|
|
125
|
+
return context || null;
|
|
119
126
|
} catch (e) {
|
|
120
127
|
log("server", "failed to run startup context hook:", e);
|
|
128
|
+
return null;
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -21,6 +21,8 @@ export type ChannelMeta = {
|
|
|
21
21
|
participantCount?: number;
|
|
22
22
|
participantProfiles?: ParticipantProfile[];
|
|
23
23
|
typing?: string[];
|
|
24
|
+
replyInstructions?: "once" | "always" | "never";
|
|
25
|
+
interrupt?: boolean;
|
|
24
26
|
signature?: string;
|
|
25
27
|
signatureTimestamp?: string;
|
|
26
28
|
signerFingerprint?: string;
|
|
@@ -30,7 +32,6 @@ export type ChannelMeta = {
|
|
|
30
32
|
/** ChannelMeta enriched by the router with dispatch info. */
|
|
31
33
|
export type HandlerMeta = ChannelMeta & {
|
|
32
34
|
messageId: string;
|
|
33
|
-
interrupt?: boolean;
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
export type VoluteRequest = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, verify } from "node:crypto";
|
|
2
2
|
import { createServer, type IncomingMessage, type Server } from "node:http";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
|
-
import type { Router } from "./router.js";
|
|
4
|
+
import type { BatchMessage, Router } from "./router.js";
|
|
5
5
|
import type { VoluteContentPart, VoluteRequest } from "./types.js";
|
|
6
6
|
|
|
7
7
|
function readBody(req: IncomingMessage): Promise<string> {
|
|
@@ -21,10 +21,27 @@ function extractText(content: VoluteContentPart[] | string): string {
|
|
|
21
21
|
.join("\n");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** Normalize content to VoluteContentPart[] — connectors may send plain strings. */
|
|
24
|
+
/** Normalize content to VoluteContentPart[] — connectors may send plain strings or mixed arrays. */
|
|
25
25
|
function normalizeContent(content: unknown): VoluteContentPart[] {
|
|
26
|
-
if (Array.isArray(content)) return content as VoluteContentPart[];
|
|
27
26
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
27
|
+
if (Array.isArray(content)) {
|
|
28
|
+
return content.map((item): VoluteContentPart => {
|
|
29
|
+
if (typeof item === "object" && item !== null && "type" in item) {
|
|
30
|
+
const obj = item as Record<string, unknown>;
|
|
31
|
+
if (obj.type === "image") {
|
|
32
|
+
if (typeof obj.media_type === "string" && typeof obj.data === "string") {
|
|
33
|
+
return { type: "image", media_type: obj.media_type, data: obj.data };
|
|
34
|
+
}
|
|
35
|
+
log("server", "image content part missing required fields, coercing to text");
|
|
36
|
+
}
|
|
37
|
+
if (typeof obj.text === "string") return { type: "text", text: obj.text };
|
|
38
|
+
}
|
|
39
|
+
if (typeof item !== "string") {
|
|
40
|
+
log("server", `unexpected content type (${typeof item}), coercing to text`);
|
|
41
|
+
}
|
|
42
|
+
return { type: "text", text: typeof item === "string" ? item : JSON.stringify(item) };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
28
45
|
return [{ type: "text", text: JSON.stringify(content) }];
|
|
29
46
|
}
|
|
30
47
|
|
|
@@ -78,15 +95,47 @@ async function verifyRequest(body: VoluteRequest): Promise<boolean | undefined>
|
|
|
78
95
|
return verifySignature(publicKey, text, body.signatureTimestamp, body.signature);
|
|
79
96
|
}
|
|
80
97
|
|
|
98
|
+
export type ContextBreakdown = {
|
|
99
|
+
systemPrompt: number;
|
|
100
|
+
sdkInstructions: number;
|
|
101
|
+
skillDescriptions: number;
|
|
102
|
+
conversation: {
|
|
103
|
+
userText: number;
|
|
104
|
+
assistantText: number;
|
|
105
|
+
thinking: number;
|
|
106
|
+
toolUse: number;
|
|
107
|
+
toolResult: number;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type SessionContextInfo = {
|
|
112
|
+
name: string;
|
|
113
|
+
contextTokens: number;
|
|
114
|
+
contextWindow?: number;
|
|
115
|
+
breakdown?: ContextBreakdown;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type ContextInfo = {
|
|
119
|
+
sessions: SessionContextInfo[];
|
|
120
|
+
systemPrompt: number;
|
|
121
|
+
};
|
|
122
|
+
|
|
81
123
|
export function createVoluteServer(options: {
|
|
82
124
|
router: Router;
|
|
83
125
|
port: number;
|
|
84
126
|
name: string;
|
|
85
127
|
version: string;
|
|
128
|
+
getContextInfo?: () => ContextInfo | Promise<ContextInfo>;
|
|
86
129
|
}): Server {
|
|
87
130
|
const { router, port, name, version } = options;
|
|
88
131
|
|
|
89
132
|
const server = createServer(async (req, res) => {
|
|
133
|
+
// Prevent EPIPE crashes when the client disconnects before the response is written
|
|
134
|
+
res.on("error", (err: NodeJS.ErrnoException) => {
|
|
135
|
+
if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
|
|
136
|
+
log("server", "response error:", err);
|
|
137
|
+
});
|
|
138
|
+
|
|
90
139
|
const url = new URL(req.url!, "http://localhost");
|
|
91
140
|
|
|
92
141
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
@@ -95,6 +144,24 @@ export function createVoluteServer(options: {
|
|
|
95
144
|
return;
|
|
96
145
|
}
|
|
97
146
|
|
|
147
|
+
if (req.method === "GET" && url.pathname === "/context") {
|
|
148
|
+
if (!options.getContextInfo) {
|
|
149
|
+
res.writeHead(404);
|
|
150
|
+
res.end("Not Found");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const info = await options.getContextInfo();
|
|
155
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
156
|
+
res.end(JSON.stringify(info));
|
|
157
|
+
} catch (err) {
|
|
158
|
+
log("server", "error in /context:", err);
|
|
159
|
+
res.writeHead(500);
|
|
160
|
+
res.end("Internal Server Error");
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
98
165
|
if (req.method === "POST" && url.pathname === "/message") {
|
|
99
166
|
try {
|
|
100
167
|
const body = JSON.parse(await readBody(req)) as VoluteRequest;
|
|
@@ -110,11 +177,11 @@ export function createVoluteServer(options: {
|
|
|
110
177
|
body.content = normalizeContent(body.content);
|
|
111
178
|
|
|
112
179
|
// Handle batch payloads from delivery manager
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
router.dispatchBatch(batch, body.session ?? "main", body);
|
|
180
|
+
const bodyWithBatch = body as VoluteRequest & {
|
|
181
|
+
batch?: { channels: Record<string, BatchMessage[]> };
|
|
182
|
+
};
|
|
183
|
+
if (bodyWithBatch.batch) {
|
|
184
|
+
router.dispatchBatch(bodyWithBatch.batch, body.session ?? "main", body);
|
|
118
185
|
} else {
|
|
119
186
|
// Pre-routed by daemon delivery manager — dispatch directly
|
|
120
187
|
router.dispatch(body.content, body.session ?? "main", body);
|
|
@@ -6,21 +6,22 @@ You are an autonomous mind running as a persistent server in a git repository. Y
|
|
|
6
6
|
|
|
7
7
|
Messages arrive with a context prefix:
|
|
8
8
|
```
|
|
9
|
-
[Discord: username in #general in My Server —
|
|
9
|
+
[Discord: username in #general in My Server — 2025-01-15 10:30]
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
You can also reach out proactively — see the **volute-mind** skill.
|
|
13
13
|
|
|
14
|
-
## Identity
|
|
14
|
+
## Identity & Sessions
|
|
15
15
|
|
|
16
16
|
These files shape your starting identity. They're loaded into your system prompt, but they belong to you — edit them as you evolve:
|
|
17
17
|
|
|
18
18
|
- `SOUL.md` — Who you are. Your core sense of self.
|
|
19
19
|
- `MEMORY.md` — What you know. Your long-term memory.
|
|
20
|
-
- `VOLUTE.md` — How you communicate. Your channels and routing.
|
|
21
20
|
|
|
22
21
|
**Editing any identity file triggers an automatic restart** — your server restarts so the updated file takes effect. Your session resumes automatically.
|
|
23
22
|
|
|
23
|
+
You may have **multiple named sessions** — each maintains its own conversation history. See `VOLUTE.md` for how to configure session routing via `.config/routes.json`. Your conversation may be **resumed** from a previous session — orient yourself by reading recent journal entries if needed. On a **fresh session**, read `MEMORY.md` and recent journal entries to remember where you left off. On **compaction**, update today's journal to preserve context before the conversation is trimmed.
|
|
24
|
+
|
|
24
25
|
## Memory System
|
|
25
26
|
|
|
26
27
|
Two-tier memory, both managed via file tools:
|
|
@@ -30,10 +31,3 @@ Two-tier memory, both managed via file tools:
|
|
|
30
31
|
- Periodically consolidate journal entries into `MEMORY.md` to promote lasting insights.
|
|
31
32
|
|
|
32
33
|
See the **memory** skill for detailed guidance.
|
|
33
|
-
|
|
34
|
-
## Sessions
|
|
35
|
-
|
|
36
|
-
- You may have **multiple named sessions** — each maintains its own conversation history. See `VOLUTE.md` for how to configure session routing via `.config/routes.json`.
|
|
37
|
-
- Your conversation may be **resumed** from a previous session — orient yourself by reading recent journal entries if needed.
|
|
38
|
-
- On a **fresh session**, read `MEMORY.md` and recent journal entries to remember where you left off.
|
|
39
|
-
- On **compaction**, update today's journal to preserve context before the conversation is trimmed.
|