volute 0.30.1 → 0.32.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 +15 -22
- package/dist/{accept-E3PAH3QJ.js → accept-74M7I4RZ.js} +5 -4
- package/dist/{activity-events-BKBPPUBP.js → activity-events-HETAODOK.js} +3 -2
- package/dist/{ai-service-VAJT5UBS.js → ai-service-ZIPCV3MX.js} +20 -5
- package/dist/api.d.ts +341 -397
- package/dist/{archive-WWDBWYN2.js → archive-INXYFVCW.js} +3 -2
- package/dist/auth-6DMGES3I.js +44 -0
- package/dist/{bridge-RO37CUFM.js → bridge-BVCBTGPF.js} +5 -4
- package/dist/{chat-TCUNPFGO.js → chat-XT4OBJBU.js} +8 -8
- package/dist/{chunk-P7VFDSSG.js → chunk-2FLJ63GU.js} +2 -2
- package/dist/{chunk-ZWKTUQEL.js → chunk-2NGTS5UU.js} +1 -1
- package/dist/{chunk-JGFRDMR6.js → chunk-ALEF47VT.js} +1 -1
- package/dist/{chunk-MDPCSXZ4.js → chunk-D5G5YOPL.js} +163 -15
- package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
- package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
- package/dist/{chunk-DTC6EH5I.js → chunk-I5KY25PQ.js} +1 -9
- package/dist/{chunk-NSBFETWP.js → chunk-IYDIE3HG.js} +64 -26
- package/dist/{chunk-W5OOPLNP.js → chunk-JJ7W6WSB.js} +3 -3
- package/dist/{chunk-G3GBKZGG.js → chunk-LGB6JBHI.js} +54 -2
- package/dist/chunk-LRCG2JLP.js +251 -0
- package/dist/{chunk-FXHXHI2A.js → chunk-LSGWR54X.js} +3 -6
- package/dist/{chunk-S5LR3XYJ.js → chunk-M7UL5S3Q.js} +1 -1
- package/dist/chunk-PB65JZK2.js +85 -0
- package/dist/chunk-PVY5W6QN.js +41 -0
- package/dist/{chunk-QVAQ5454.js → chunk-QBQ424EM.js} +3007 -2126
- package/dist/{chunk-P27RV5WM.js → chunk-QZANELPX.js} +6 -2
- package/dist/{chunk-FSM45XD5.js → chunk-R7E6CRVQ.js} +1 -1
- package/dist/{chunk-HHTXM4JT.js → chunk-RPZZSXV3.js} +39 -195
- package/dist/{chunk-UPA6COHU.js → chunk-RSX4OPZY.js} +5 -5
- package/dist/{chunk-2C2VXEBB.js → chunk-S6NFERDC.js} +21 -57
- package/dist/chunk-SKLSMHXO.js +208 -0
- package/dist/{chunk-IKHDUZRH.js → chunk-SX5TKJBZ.js} +2 -2
- package/dist/chunk-TDRYEPH4.js +185 -0
- package/dist/chunk-TSXLLQZW.js +46 -0
- package/dist/{chunk-EFVHR7KH.js → chunk-UKVWJRKN.js} +24 -5
- package/dist/{chunk-2NDZC3S7.js → chunk-WKF5FEFK.js} +688 -389
- package/dist/cli.js +93 -24
- package/dist/{clock-G3ALCMLJ.js → clock-2UOZ6JPU.js} +11 -8
- package/dist/{cloud-sync-JV4LJOK3.js → cloud-sync-JN3NWKEM.js} +16 -14
- package/dist/config-H2H4UIF7.js +72 -0
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-7KVQV7EZ.js → conversations-3O5O6AS3.js} +8 -7
- package/dist/{create-JTLS7GX3.js → create-RNLNCORE.js} +5 -4
- package/dist/{create-VQSQHJQW.js → create-WBBYI6V7.js} +6 -2
- package/dist/daemon-client-6QXHZ7US.js +12 -0
- package/dist/{daemon-restart-4JGBHEJ4.js → daemon-restart-NGFHFAUF.js} +7 -7
- package/dist/daemon.js +2446 -1999
- package/dist/{db-HMFPIRO2.js → db-F34YLV7D.js} +2 -1
- package/dist/db-RA45JBFG.js +16 -0
- package/dist/{delete-JESHKE7F.js → delete-QTGWEDBI.js} +1 -1
- package/dist/delivery-manager-SDVXFD4W.js +28 -0
- package/dist/delivery-router-FL45JL7N.js +21 -0
- package/dist/down-TB3ESMNP.js +14 -0
- package/dist/{env-CLXXT7M2.js → env-RLYQBOOP.js} +5 -4
- package/dist/{export-EGA5M5PB.js → export-SUYRLI5Q.js} +4 -3
- package/dist/{extension-WZ4SUPJB.js → extension-FQ5D3NCC.js} +6 -6
- package/dist/{extensions-ECO4RPFQ.js → extensions-GDYWQXC4.js} +9 -7
- package/dist/{files-4VEJDASH.js → files-EAMPO2SJ.js} +6 -5
- package/dist/{history-EJMMLXDO.js → history-FO5PHBQ5.js} +9 -4
- package/dist/{import-YCGPMBSI.js → import-DDUFE7AY.js} +4 -3
- package/dist/{join-2GBJKZEN.js → join-I5QEE3LG.js} +1 -1
- package/dist/{list-Q6O7FGAN.js → list-DW2VRTOZ.js} +5 -4
- package/dist/{login-RL6AU2SM.js → login-7CHPW2PN.js} +5 -4
- package/dist/{login-RET5WESK.js → login-RIJF2F4G.js} +3 -2
- package/dist/{logout-CGAGJN3L.js → logout-5MLHZALK.js} +3 -2
- package/dist/{logout-JRPBEMMR.js → logout-UZJRGY4Z.js} +3 -2
- package/dist/message-delivery-2FIM7QKO.js +32 -0
- package/dist/{mind-LUWRQUQ5.js → mind-2B6M7Y25.js} +18 -18
- package/dist/{mind-activity-tracker-VYN2ZZ2M.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
- package/dist/{mind-list-V5WW5DUA.js → mind-list-WUPMQDYQ.js} +3 -2
- package/dist/mind-manager-BNCMGYXW.js +28 -0
- package/dist/mind-service-AV273WT4.js +34 -0
- package/dist/{mind-sleep-R6PTNNW4.js → mind-sleep-B7BHJLH7.js} +5 -4
- package/dist/{mind-status-I4ISFJ6I.js → mind-status-L3EFFRPR.js} +3 -2
- package/dist/{mind-wake-67ZQEWAV.js → mind-wake-GY3RFX7Y.js} +5 -4
- package/dist/{package-OYUD4ZJ4.js → package-PK6JUFL3.js} +3 -3
- package/dist/read-5AMJRO3D.js +75 -0
- package/dist/{register-NZDSTLP3.js → register-V2JZZKFK.js} +5 -4
- package/dist/{registry-ODSALQQL.js → registry-PJ4S5PHQ.js} +8 -1
- package/dist/{reject-2HZOJEIJ.js → reject-33HEZMZ4.js} +5 -4
- package/dist/{restart-QHS3NT64.js → restart-3UCMRUVC.js} +5 -4
- package/dist/{sandbox-O5FUSF43.js → sandbox-JANNTX6U.js} +4 -3
- package/dist/schema-PA3M5ZKH.js +32 -0
- package/dist/seed-ALUQ55FF.js +112 -0
- package/dist/{send-OAN3RYYY.js → send-3MI36LEF.js} +58 -69
- package/dist/{setup-QMDK5RZX.js → setup-SZIARWI6.js} +5 -4
- package/dist/{setup-XJH3E7YM.js → setup-WENLVPVP.js} +9 -9
- package/dist/{skill-FZIN4W4Q.js → skill-TUVOTW4Z.js} +5 -4
- package/dist/skills/dreaming/SKILL.md +6 -4
- package/dist/skills/dreaming/references/INSTALL.md +4 -5
- package/dist/skills/dreaming/scripts/dream.ts +5 -27
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
- package/dist/skills/imagegen/SKILL.md +6 -5
- package/dist/skills/imagegen/references/INSTALL.md +1 -1
- package/dist/skills/resonance/SKILL.md +4 -1
- package/dist/skills/resonance/references/INSTALL.md +2 -2
- package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
- package/dist/skills/resonance/scripts/resonance.ts +35 -5
- package/dist/skills/volute-admin/SKILL.md +83 -0
- package/dist/skills/volute-mind/SKILL.md +12 -12
- package/dist/skills-XNZK6P4K.js +61 -0
- package/dist/sleep-manager-53DZOWW7.js +32 -0
- package/dist/spirit-N4W4UQRH.js +217 -0
- package/dist/{split-EXYGGGQN.js → split-STOROBYJ.js} +1 -1
- package/dist/{sprout-AXQ6H5DB.js → sprout-L2GFOVF7.js} +9 -8
- package/dist/{start-MTOVL6SY.js → start-K2NCUUCG.js} +5 -4
- package/dist/{status-ZRO37MWR.js → status-TCUMUO6M.js} +5 -5
- package/dist/{stop-OK5WEPVC.js → stop-H26JZDXF.js} +5 -4
- package/dist/system-chat-NPYFYZVI.js +32 -0
- package/dist/{systems-W3BBMSOZ.js → systems-DHBKVYEY.js} +6 -5
- package/dist/{tailscale-BM72RXCJ.js → tailscale-XHQBZROW.js} +2 -1
- package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
- package/dist/up-6I6BHRTO.js +17 -0
- package/dist/{update-PLPHMMZ2.js → update-QVPRF6GR.js} +5 -5
- package/dist/{update-check-CVCN7MF6.js → update-check-ZD6OOIYQ.js} +3 -2
- package/dist/{upgrade-I6NPCYUU.js → upgrade-O4Q7WJM3.js} +12 -14
- package/dist/{version-notify-2NTWVEHL.js → version-notify-TCKWBZZG.js} +22 -23
- package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
- package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
- package/dist/web-assets/ext-theme.css +94 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0000_baseline.sql +152 -0
- package/drizzle/0001_add_conversation_private.sql +1 -0
- package/drizzle/0002_turns.sql +21 -0
- package/drizzle/0003_turn_feed_links.sql +11 -0
- package/drizzle/0004_spirits.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +3 -223
- package/drizzle/meta/0001_snapshot.json +3 -294
- package/drizzle/meta/0002_snapshot.json +3 -335
- package/drizzle/meta/0003_snapshot.json +3 -413
- package/drizzle/meta/0004_snapshot.json +3 -406
- package/drizzle/meta/_journal.json +10 -101
- package/package.json +3 -3
- package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
- package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
- package/packages/extensions/notes/dist/ui/index.html +2 -2
- package/packages/extensions/notes/skills/notes/SKILL.md +8 -8
- package/packages/extensions/pages/skills/pages/SKILL.md +17 -44
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/bin/volute +27 -0
- package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
- package/templates/_base/home/.config/routes.json +1 -1
- package/templates/_base/src/lib/auto-commit.ts +82 -43
- package/templates/_base/src/lib/daemon-client.ts +40 -36
- package/templates/_base/src/lib/format-prefix.ts +1 -0
- package/templates/_base/src/lib/hook-loader.ts +155 -0
- package/templates/_base/src/lib/router.ts +17 -1
- package/templates/_base/src/lib/startup.ts +17 -12
- package/templates/_base/src/lib/transparency.ts +2 -2
- package/templates/_base/src/lib/volute-server.ts +2 -5
- package/templates/claude/.init/.claude/settings.json +1 -1
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/claude/src/agent.ts +97 -14
- package/templates/claude/src/lib/hooks/auto-commit.ts +7 -3
- package/templates/claude/src/lib/message-channel.ts +7 -2
- package/templates/claude/src/server.ts +0 -9
- package/templates/codex/.init/.config/routes.json +11 -0
- package/templates/codex/.init/AGENTS.md +29 -0
- package/templates/codex/home/.config/config.json.tmpl +7 -0
- package/templates/codex/package.json.tmpl +20 -0
- package/templates/codex/src/agent.ts +553 -0
- package/templates/codex/src/lib/content.ts +16 -0
- package/templates/codex/src/lib/session-store.ts +56 -0
- package/templates/codex/src/server.ts +59 -0
- package/templates/codex/volute-template.json +8 -0
- package/templates/pi/.init/.config/routes.json +2 -2
- package/templates/pi/package.json.tmpl +1 -1
- package/templates/pi/src/agent.ts +63 -9
- package/templates/pi/src/lib/event-handler.ts +6 -4
- package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
- package/dist/chunk-7D47T4RB.js +0 -84
- package/dist/chunk-CVH6Y2YG.js +0 -59
- package/dist/chunk-EFP3PE6C.js +0 -232
- package/dist/chunk-LIRWLNAK.js +0 -729
- package/dist/daemon-client-BCTFGVCZ.js +0 -9
- package/dist/down-NGBMGORS.js +0 -14
- package/dist/message-delivery-6YMVNOEC.js +0 -28
- package/dist/migrate-registry-to-db-FK35IPEH.js +0 -110
- package/dist/mind-manager-YFCOIAAX.js +0 -18
- package/dist/pages-watcher-Z3PKNROC.js +0 -21
- package/dist/read-WQMPTSN2.js +0 -46
- package/dist/seed-WUQMPLDM.js +0 -71
- package/dist/skills/sessions/SKILL.md +0 -49
- package/dist/sleep-manager-O7YQFCV5.js +0 -30
- package/dist/up-BXUAIDXB.js +0 -17
- package/dist/web-assets/assets/index--kREqKl9.js +0 -72
- package/dist/web-assets/assets/index-BXYTG0nJ.css +0 -1
- package/drizzle/0000_flaky_mariko_yashida.sql +0 -34
- package/drizzle/0001_careless_warpath.sql +0 -12
- package/drizzle/0002_wealthy_the_call.sql +0 -6
- package/drizzle/0003_clean_ego.sql +0 -12
- package/drizzle/0004_magical_silverclaw.sql +0 -1
- package/drizzle/0005_rename_agents_to_minds.sql +0 -11
- package/drizzle/0006_mind_history.sql +0 -20
- package/drizzle/0007_system_prompts.sql +0 -5
- package/drizzle/0008_volute_channels.sql +0 -24
- package/drizzle/0009_shared_skills.sql +0 -9
- package/drizzle/0010_delivery_queue.sql +0 -12
- package/drizzle/0011_rename_human_to_brain.sql +0 -1
- package/drizzle/0012_activity.sql +0 -11
- package/drizzle/0013_user_profiles.sql +0 -3
- package/drizzle/0014_conversation_reads.sql +0 -7
- package/drizzle/0015_notes.sql +0 -23
- package/drizzle/0016_note_reactions_and_replies.sql +0 -15
- package/drizzle/0017_minds.sql +0 -16
- package/drizzle/meta/0005_snapshot.json +0 -410
- package/drizzle/meta/0006_snapshot.json +0 -7
- package/drizzle/meta/0007_snapshot.json +0 -7
- package/drizzle/meta/0008_snapshot.json +0 -7
- package/drizzle/meta/0009_snapshot.json +0 -7
- package/drizzle/meta/0010_snapshot.json +0 -7
- package/drizzle/meta/0011_snapshot.json +0 -7
- package/drizzle/meta/0012_snapshot.json +0 -7
- package/drizzle/meta/0013_snapshot.json +0 -7
- package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +0 -2
- package/packages/extensions/notes/skills/notes/scripts/notes.mjs +0 -185
- package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
- package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
- package/templates/_base/home/public/.gitkeep +0 -0
- package/templates/_base/src/lib/session-monitor.ts +0 -400
- package/templates/claude/src/lib/hooks/session-context.ts +0 -32
- package/templates/pi/src/lib/session-context-extension.ts +0 -35
- /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Cross-session activity — shows what happened in other sessions since last check.
|
|
2
|
+
// Uses the daemon history API. Customize or remove this hook as you like.
|
|
3
|
+
|
|
4
|
+
const input = await new Promise<string>((resolve) => {
|
|
5
|
+
let data = "";
|
|
6
|
+
process.stdin.on("data", (chunk) => {
|
|
7
|
+
data += chunk;
|
|
8
|
+
});
|
|
9
|
+
process.stdin.on("end", () => resolve(data));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const { VOLUTE_DAEMON_PORT, VOLUTE_DAEMON_TOKEN, VOLUTE_MIND } = process.env;
|
|
13
|
+
if (!VOLUTE_DAEMON_PORT || !VOLUTE_DAEMON_TOKEN || !VOLUTE_MIND) {
|
|
14
|
+
console.log("{}");
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let session = "";
|
|
19
|
+
try {
|
|
20
|
+
session = JSON.parse(input).session ?? "";
|
|
21
|
+
} catch {}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(
|
|
25
|
+
`http://127.0.0.1:${VOLUTE_DAEMON_PORT}/api/minds/${VOLUTE_MIND}/history/cross-session?session=${encodeURIComponent(session)}`,
|
|
26
|
+
{ headers: { Authorization: `Bearer ${VOLUTE_DAEMON_TOKEN}` } },
|
|
27
|
+
);
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
console.log("{}");
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
const { context } = (await res.json()) as { context: string | null };
|
|
33
|
+
if (context) {
|
|
34
|
+
console.log(JSON.stringify({ additionalContext: context }));
|
|
35
|
+
} else {
|
|
36
|
+
console.log("{}");
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
console.log("{}");
|
|
40
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Startup context hook — generates orientation context for new sessions.
|
|
2
|
+
// Edit this script to customize what you see when your session starts.
|
|
3
|
+
// Input: JSON on stdin with { "source": "startup" | "SessionStart" }
|
|
4
|
+
// Output: JSON with hookSpecificOutput.additionalContext (for SessionStart hook)
|
|
5
|
+
// or plain text (for direct execution by pi template)
|
|
6
|
+
|
|
7
|
+
import { readdirSync } from "node:fs";
|
|
8
|
+
|
|
9
|
+
const input = await new Promise<string>((resolve) => {
|
|
10
|
+
let data = "";
|
|
11
|
+
process.stdin.on("data", (chunk: Buffer) => {
|
|
12
|
+
data += chunk;
|
|
13
|
+
});
|
|
14
|
+
process.stdin.on("end", () => resolve(data));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
let source = "startup";
|
|
18
|
+
try {
|
|
19
|
+
source = JSON.parse(input).source ?? "startup";
|
|
20
|
+
} catch {}
|
|
21
|
+
|
|
22
|
+
const parts: string[] = [`Session ${source} at ${new Date().toLocaleString()}.`];
|
|
23
|
+
|
|
24
|
+
// Active sessions
|
|
25
|
+
try {
|
|
26
|
+
const files = readdirSync(".mind/sessions").filter((f) => f.endsWith(".json"));
|
|
27
|
+
if (files.length > 0) {
|
|
28
|
+
const names = files.map((f) => f.replace(/\.json$/, "")).sort();
|
|
29
|
+
parts.push(`Active sessions: ${names.join(", ")}.`);
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
|
|
33
|
+
// Last journal entry
|
|
34
|
+
try {
|
|
35
|
+
const entries = readdirSync("home/memory/journal").filter((f) => f.endsWith(".md"));
|
|
36
|
+
if (entries.length > 0) {
|
|
37
|
+
const latest = entries.sort().pop()!.replace(/\.md$/, "");
|
|
38
|
+
parts.push(`Last journal entry: ${latest}.`);
|
|
39
|
+
}
|
|
40
|
+
} catch {}
|
|
41
|
+
|
|
42
|
+
// Pending channel invites
|
|
43
|
+
try {
|
|
44
|
+
const invites = readdirSync("home/inbox").filter((f) => f.endsWith(".md"));
|
|
45
|
+
if (invites.length > 0) {
|
|
46
|
+
parts.push(`Pending channel invites: ${invites.length} (check inbox/).`);
|
|
47
|
+
}
|
|
48
|
+
} catch {}
|
|
49
|
+
|
|
50
|
+
const context = parts.join(" ");
|
|
51
|
+
console.log(
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
hookSpecificOutput: {
|
|
54
|
+
hookEventName: "SessionStart",
|
|
55
|
+
additionalContext: context,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"rules": [{ "channel": "
|
|
2
|
+
"rules": [{ "channel": "*", "isDM": false, "session": "group-${channel}" }],
|
|
3
3
|
"sessions": {
|
|
4
4
|
"group-*": { "batch": { "debounce": 20, "maxWait": 120, "triggers": ["@{{name}}"] } }
|
|
5
5
|
}
|
|
@@ -17,15 +17,19 @@ function exec(cmd: string, args: string[], cwd: string): Promise<{ code: number;
|
|
|
17
17
|
// Serialize git operations to prevent concurrent commits from conflicting
|
|
18
18
|
let pending = Promise.resolve();
|
|
19
19
|
|
|
20
|
+
// Pending file changes accumulated across all sessions, flushed on turn end
|
|
21
|
+
const pendingFiles = new Set<string>();
|
|
22
|
+
const pendingSharedFiles = new Set<string>();
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
|
-
*
|
|
25
|
+
* Track a file change in the mind's home directory for batched commit.
|
|
22
26
|
* Called by the PostToolUse hook when Edit or Write completes.
|
|
23
27
|
*
|
|
24
|
-
* Files under home/shared/ are
|
|
25
|
-
*
|
|
28
|
+
* Files under home/shared/ are tracked separately for the shared worktree repo.
|
|
29
|
+
* All other files go to the mind's own repo.
|
|
26
30
|
*/
|
|
27
|
-
export function
|
|
28
|
-
// Only
|
|
31
|
+
export function trackFileChange(filePath: string, cwd: string): void {
|
|
32
|
+
// Only track files under the home directory
|
|
29
33
|
const homeDir = resolve(cwd);
|
|
30
34
|
const resolved = resolve(cwd, filePath);
|
|
31
35
|
if (!resolved.startsWith(`${homeDir}/`) && resolved !== homeDir) return;
|
|
@@ -33,56 +37,91 @@ export function commitFileChange(filePath: string, cwd: string): void {
|
|
|
33
37
|
const relativePath = resolved.slice(homeDir.length + 1);
|
|
34
38
|
if (!relativePath) return;
|
|
35
39
|
|
|
36
|
-
// Check if this file is under the shared/ worktree
|
|
37
40
|
const sharedPrefix = "shared/";
|
|
38
|
-
|
|
41
|
+
if (relativePath.startsWith(sharedPrefix)) {
|
|
42
|
+
pendingSharedFiles.add(relativePath);
|
|
43
|
+
} else {
|
|
44
|
+
pendingFiles.add(relativePath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Flush all pending file changes into batched commits.
|
|
50
|
+
* Called at the end of each turn. Produces up to two commits:
|
|
51
|
+
* one for the mind's own repo and one for the shared worktree.
|
|
52
|
+
*/
|
|
53
|
+
export function flushFileChanges(cwd?: string): Promise<void> {
|
|
54
|
+
const filesToCommit = [...pendingFiles];
|
|
55
|
+
const sharedToCommit = [...pendingSharedFiles];
|
|
56
|
+
pendingFiles.clear();
|
|
57
|
+
pendingSharedFiles.clear();
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return;
|
|
59
|
+
if (filesToCommit.length === 0 && sharedToCommit.length === 0) {
|
|
60
|
+
return pending.then(() => {});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const effectiveCwd = cwd ?? process.cwd();
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} else {
|
|
62
|
-
log("auto-commit", `[shared] commit failed for ${sharedRelative}`);
|
|
65
|
+
pending = pending.then(async () => {
|
|
66
|
+
// Commit mind's own files
|
|
67
|
+
if (filesToCommit.length > 0) {
|
|
68
|
+
for (const f of filesToCommit) {
|
|
69
|
+
if ((await exec("git", ["add", f], effectiveCwd)).code !== 0) {
|
|
70
|
+
log("auto-commit", `git add failed for ${f}`);
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
if ((await exec("git", ["diff", "--cached", "--quiet"], effectiveCwd)).code !== 0) {
|
|
74
|
+
const names = filesToCommit.map((f) => f.replace(/^.*\//, "")).join(", ");
|
|
75
|
+
const message = `Update ${names}`;
|
|
76
|
+
if ((await exec("git", ["commit", "-m", message], effectiveCwd)).code === 0) {
|
|
77
|
+
log("auto-commit", message);
|
|
78
|
+
// Push if a remote is configured
|
|
79
|
+
const { stdout: remote } = await exec("git", ["remote"], effectiveCwd);
|
|
80
|
+
if (remote) {
|
|
81
|
+
const pushResult = await exec("git", ["push"], effectiveCwd);
|
|
82
|
+
if (pushResult.code !== 0) {
|
|
83
|
+
log("auto-commit", `git push failed`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
log("auto-commit", `commit failed for: ${names}`);
|
|
88
|
+
}
|
|
70
89
|
}
|
|
71
|
-
|
|
90
|
+
}
|
|
72
91
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
// Commit shared worktree files
|
|
93
|
+
if (sharedToCommit.length > 0) {
|
|
94
|
+
const sharedCwd = resolve(effectiveCwd, "shared");
|
|
95
|
+
const sharedPrefix = "shared/";
|
|
96
|
+
const mindName = process.env.VOLUTE_MIND ?? "unknown";
|
|
97
|
+
|
|
98
|
+
for (const f of sharedToCommit) {
|
|
99
|
+
const sharedRelative = f.slice(sharedPrefix.length);
|
|
100
|
+
if ((await exec("git", gitArgs(["add", sharedRelative]), sharedCwd)).code !== 0) {
|
|
101
|
+
log("auto-commit", `git add failed for shared/${sharedRelative}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if ((await exec("git", gitArgs(["diff", "--cached", "--quiet"]), sharedCwd)).code !== 0) {
|
|
105
|
+
const names = sharedToCommit
|
|
106
|
+
.map((f) => f.slice(sharedPrefix.length).replace(/^.*\//, ""))
|
|
107
|
+
.join(", ");
|
|
108
|
+
const message = `Update ${names}`;
|
|
109
|
+
const authorFlag = `${mindName} <${mindName}@volute>`;
|
|
110
|
+
if (
|
|
111
|
+
(await exec("git", gitArgs(["commit", "--author", authorFlag, "-m", message]), sharedCwd))
|
|
112
|
+
.code === 0
|
|
113
|
+
) {
|
|
114
|
+
log("auto-commit", `[shared] ${message}`);
|
|
115
|
+
} else {
|
|
116
|
+
log("auto-commit", `[shared] commit failed`);
|
|
80
117
|
}
|
|
81
118
|
}
|
|
82
119
|
}
|
|
83
120
|
});
|
|
121
|
+
|
|
122
|
+
return pending.then(() => {});
|
|
84
123
|
}
|
|
85
124
|
|
|
86
125
|
export function waitForCommits(): Promise<void> {
|
|
87
|
-
return
|
|
126
|
+
return flushFileChanges();
|
|
88
127
|
}
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
1
4
|
const port = process.env.VOLUTE_DAEMON_PORT;
|
|
2
5
|
const mind = process.env.VOLUTE_MIND;
|
|
3
6
|
const token = process.env.VOLUTE_DAEMON_TOKEN;
|
|
4
7
|
|
|
8
|
+
/** Read session from file (fallback for sandbox where env vars don't propagate). */
|
|
9
|
+
function readSessionFile(): string | undefined {
|
|
10
|
+
const mindDir = process.env.VOLUTE_MIND_DIR;
|
|
11
|
+
if (!mindDir) return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const p = resolve(mindDir, ".mind", "current-session");
|
|
14
|
+
if (existsSync(p)) return readFileSync(p, "utf-8").trim() || undefined;
|
|
15
|
+
} catch (err) {
|
|
16
|
+
console.warn(`[volute] failed to read session file: ${err}`);
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
function headers(): Record<string, string> {
|
|
6
22
|
const h: Record<string, string> = { "Content-Type": "application/json" };
|
|
7
23
|
if (token) h.Authorization = `Bearer ${token}`;
|
|
8
24
|
// Origin header required for CSRF checks on mutation requests
|
|
9
25
|
if (port) h.Origin = `http://127.0.0.1:${port}`;
|
|
26
|
+
// Tag requests with the current session for turn resolution
|
|
27
|
+
const session = process.env.VOLUTE_SESSION ?? readSessionFile();
|
|
28
|
+
if (session) h["X-Volute-Session"] = session;
|
|
10
29
|
return h;
|
|
11
30
|
}
|
|
12
31
|
|
|
@@ -39,7 +58,8 @@ export type EventType =
|
|
|
39
58
|
| "session_start"
|
|
40
59
|
| "done"
|
|
41
60
|
| "inbound"
|
|
42
|
-
| "outbound"
|
|
61
|
+
| "outbound"
|
|
62
|
+
| "context";
|
|
43
63
|
|
|
44
64
|
export type DaemonEvent = {
|
|
45
65
|
type: EventType;
|
|
@@ -57,20 +77,27 @@ export async function daemonEmit(event: DaemonEvent): Promise<void> {
|
|
|
57
77
|
}
|
|
58
78
|
return;
|
|
59
79
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
);
|
|
69
|
-
if (!res.ok) {
|
|
80
|
+
const url = `http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/events`;
|
|
81
|
+
const body = JSON.stringify(event);
|
|
82
|
+
// Critical events (done) get retries — if lost, turns stay stuck until daemon restart
|
|
83
|
+
const maxAttempts = event.type === "done" ? 3 : 1;
|
|
84
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch(url, { method: "POST", headers: headers(), body });
|
|
87
|
+
if (res.ok) return;
|
|
70
88
|
console.error(`[volute] event emit failed: ${res.status}`);
|
|
89
|
+
// Don't retry client errors — they won't succeed on retry
|
|
90
|
+
if (res.status >= 400 && res.status < 500) return;
|
|
91
|
+
if (attempt < maxAttempts) {
|
|
92
|
+
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (attempt >= maxAttempts) {
|
|
96
|
+
console.error(`[volute] event emit failed after ${maxAttempts} attempts:`, err);
|
|
97
|
+
} else {
|
|
98
|
+
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
99
|
+
}
|
|
71
100
|
}
|
|
72
|
-
} catch {
|
|
73
|
-
// Best-effort — don't let event emission failures break the mind
|
|
74
101
|
}
|
|
75
102
|
}
|
|
76
103
|
|
|
@@ -95,26 +122,3 @@ export async function daemonSendFile(
|
|
|
95
122
|
}
|
|
96
123
|
return (await res.json()) as { status: string; id?: string; destPath?: string };
|
|
97
124
|
}
|
|
98
|
-
|
|
99
|
-
export async function daemonSend(channel: string, text: string): Promise<void> {
|
|
100
|
-
if (!port || !mind) {
|
|
101
|
-
console.error("[volute] daemonSend: VOLUTE_DAEMON_PORT or VOLUTE_MIND not set");
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const res = await fetch(
|
|
105
|
-
`http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/message`,
|
|
106
|
-
{
|
|
107
|
-
method: "POST",
|
|
108
|
-
headers: headers(),
|
|
109
|
-
body: JSON.stringify({
|
|
110
|
-
content: text,
|
|
111
|
-
channel,
|
|
112
|
-
sender: mind,
|
|
113
|
-
}),
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
if (!res.ok) {
|
|
117
|
-
const body = await res.text().catch(() => "");
|
|
118
|
-
throw new Error(`daemonSend failed (${res.status}): ${body}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { extname, join, resolve } from "node:path";
|
|
4
|
+
import { log } from "./logger.js";
|
|
5
|
+
|
|
6
|
+
export type HookResult = {
|
|
7
|
+
additionalContext?: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
decision?: "block";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AggregatedResult = {
|
|
13
|
+
additionalContext?: string;
|
|
14
|
+
metadata: Record<string, unknown>;
|
|
15
|
+
blocked: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Discover hook scripts in `.local/hooks/<event>/`, sorted alphabetically.
|
|
22
|
+
*/
|
|
23
|
+
export function discoverHooks(hooksDir: string, event: string): string[] {
|
|
24
|
+
const dir = resolve(hooksDir, event);
|
|
25
|
+
if (!existsSync(dir)) return [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return readdirSync(dir)
|
|
29
|
+
.filter((f) => /\.(sh|ts|js)$/.test(f))
|
|
30
|
+
.sort()
|
|
31
|
+
.map((f) => join(dir, f));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
log(
|
|
34
|
+
"hooks",
|
|
35
|
+
`failed to read hooks directory ${dir}: ${err instanceof Error ? err.message : err}`,
|
|
36
|
+
);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Select the runner command for a hook script based on its extension.
|
|
43
|
+
*/
|
|
44
|
+
function getRunner(scriptPath: string): { cmd: string; args: string[] } {
|
|
45
|
+
const ext = extname(scriptPath);
|
|
46
|
+
if (ext === ".ts") return { cmd: process.execPath, args: ["--import", "tsx", scriptPath] };
|
|
47
|
+
if (ext === ".js") return { cmd: "node", args: [scriptPath] };
|
|
48
|
+
return { cmd: "bash", args: [scriptPath] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Execute a single hook script with JSON on stdin, parse JSON from stdout.
|
|
53
|
+
*/
|
|
54
|
+
export function executeHook(
|
|
55
|
+
scriptPath: string,
|
|
56
|
+
input: object,
|
|
57
|
+
timeout = DEFAULT_TIMEOUT,
|
|
58
|
+
): Promise<HookResult> {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const { cmd, args } = getRunner(scriptPath);
|
|
61
|
+
const child = spawn(cmd, args, {
|
|
62
|
+
timeout,
|
|
63
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
64
|
+
cwd: process.cwd(),
|
|
65
|
+
env: process.env,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let stdout = "";
|
|
69
|
+
let stderr = "";
|
|
70
|
+
|
|
71
|
+
child.stdout.on("data", (d: Buffer) => {
|
|
72
|
+
stdout += d.toString();
|
|
73
|
+
});
|
|
74
|
+
child.stderr.on("data", (d: Buffer) => {
|
|
75
|
+
stderr += d.toString();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Ignore stdin errors — child may exit before reading (EPIPE)
|
|
79
|
+
child.stdin.on("error", () => {});
|
|
80
|
+
child.stdin.write(JSON.stringify(input));
|
|
81
|
+
child.stdin.end();
|
|
82
|
+
|
|
83
|
+
let settled = false;
|
|
84
|
+
child.on("close", (code) => {
|
|
85
|
+
if (settled) return;
|
|
86
|
+
settled = true;
|
|
87
|
+
if (code !== 0) {
|
|
88
|
+
log("hooks", `hook ${scriptPath} exited with code ${code}: ${stderr.trim()}`);
|
|
89
|
+
resolve({});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const trimmed = stdout.trim();
|
|
94
|
+
if (!trimmed) {
|
|
95
|
+
resolve({});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(trimmed);
|
|
101
|
+
resolve({
|
|
102
|
+
additionalContext: parsed.additionalContext,
|
|
103
|
+
metadata: parsed.metadata,
|
|
104
|
+
decision: parsed.decision,
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
log("hooks", `hook ${scriptPath} returned invalid JSON: ${trimmed.slice(0, 200)}`);
|
|
108
|
+
resolve({});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
child.on("error", (err) => {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
settled = true;
|
|
115
|
+
log("hooks", `hook ${scriptPath} failed to spawn: ${err.message}`);
|
|
116
|
+
resolve({});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Discover and run all hooks for an event, aggregating results.
|
|
123
|
+
*/
|
|
124
|
+
export async function runHooks(
|
|
125
|
+
hooksDir: string,
|
|
126
|
+
event: string,
|
|
127
|
+
input: object,
|
|
128
|
+
timeout = DEFAULT_TIMEOUT,
|
|
129
|
+
): Promise<AggregatedResult> {
|
|
130
|
+
const scripts = discoverHooks(hooksDir, event);
|
|
131
|
+
if (scripts.length === 0) return { metadata: {}, blocked: false };
|
|
132
|
+
|
|
133
|
+
const contextParts: string[] = [];
|
|
134
|
+
const metadata: Record<string, unknown> = {};
|
|
135
|
+
let blocked = false;
|
|
136
|
+
|
|
137
|
+
for (const script of scripts) {
|
|
138
|
+
const result = await executeHook(script, input, timeout);
|
|
139
|
+
if (result.additionalContext) {
|
|
140
|
+
contextParts.push(result.additionalContext);
|
|
141
|
+
}
|
|
142
|
+
if (result.metadata) {
|
|
143
|
+
Object.assign(metadata, result.metadata);
|
|
144
|
+
}
|
|
145
|
+
if (result.decision === "block") {
|
|
146
|
+
blocked = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
additionalContext: contextParts.length > 0 ? contextParts.join("\n\n") : undefined,
|
|
152
|
+
metadata,
|
|
153
|
+
blocked,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
1
3
|
import { formatPrefix, formatTypingSuffix } from "./format-prefix.js";
|
|
2
|
-
import { log } from "./logger.js";
|
|
4
|
+
import { log, warn } from "./logger.js";
|
|
3
5
|
import {
|
|
4
6
|
type BatchConfig,
|
|
5
7
|
loadRoutingConfig,
|
|
@@ -273,6 +275,20 @@ export function createRouter(options: {
|
|
|
273
275
|
const noop = () => {};
|
|
274
276
|
const safeListener = listener ?? noop;
|
|
275
277
|
|
|
278
|
+
// Expose session to child processes (daemon-client reads this for X-Volute-Session)
|
|
279
|
+
process.env.VOLUTE_SESSION = session;
|
|
280
|
+
// Also write to file for sandbox environments where env vars don't propagate
|
|
281
|
+
try {
|
|
282
|
+
const mindDir = process.env.VOLUTE_MIND_DIR;
|
|
283
|
+
if (mindDir) {
|
|
284
|
+
const sessionFile = resolve(mindDir, ".mind", "current-session");
|
|
285
|
+
mkdirSync(resolve(mindDir, ".mind"), { recursive: true });
|
|
286
|
+
writeFileSync(sessionFile, session, "utf-8");
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
warn("router", `failed to write session file: ${err}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
276
292
|
// Apply formatting
|
|
277
293
|
const formatted = applyPrefix(content, { ...meta, sessionName: session });
|
|
278
294
|
const withTyping = appendTypingSuffix(formatted, meta.typing);
|
|
@@ -30,16 +30,14 @@ export function loadConfig(): {
|
|
|
30
30
|
compaction?: { maxContextTokens?: number };
|
|
31
31
|
subagents?: Record<string, SubagentConfig>;
|
|
32
32
|
} {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (err?.code === "ENOENT") continue;
|
|
39
|
-
log("startup", `failed to parse ${file}:`, err);
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(resolve("home/.config/config.json"), "utf-8"));
|
|
35
|
+
} catch (err: any) {
|
|
36
|
+
if (err?.code !== "ENOENT") {
|
|
37
|
+
log("startup", "failed to parse config.json:", err);
|
|
40
38
|
}
|
|
39
|
+
return {};
|
|
41
40
|
}
|
|
42
|
-
return {};
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
function loadFile(path: string): string {
|
|
@@ -80,12 +78,19 @@ export function loadPackageInfo(): { name: string; version: string } {
|
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
export async function handleStartupContext(sendMessage: (content: string) => void): Promise<void> {
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// Prefer .ts, fall back to .sh for backwards compatibility
|
|
82
|
+
const tsPath = resolve("home/.local/hooks/startup-context.ts");
|
|
83
|
+
const shPath = resolve("home/.local/hooks/startup-context.sh");
|
|
84
|
+
const scriptPath = existsSync(tsPath) ? tsPath : existsSync(shPath) ? shPath : null;
|
|
85
|
+
if (!scriptPath) return;
|
|
86
|
+
|
|
87
|
+
const isTs = scriptPath.endsWith(".ts");
|
|
85
88
|
|
|
86
89
|
try {
|
|
87
90
|
const stdout = await new Promise<string>((resolve, reject) => {
|
|
88
|
-
const child =
|
|
91
|
+
const child = isTs
|
|
92
|
+
? spawn(process.execPath, ["--import", "tsx", scriptPath], { timeout: 5000 })
|
|
93
|
+
: spawn("bash", [scriptPath], { timeout: 5000 });
|
|
89
94
|
let out = "";
|
|
90
95
|
child.stdout.on("data", (d: Buffer) => {
|
|
91
96
|
out += d.toString();
|
|
@@ -112,7 +117,7 @@ export async function handleStartupContext(sendMessage: (content: string) => voi
|
|
|
112
117
|
log("server", "sent startup context");
|
|
113
118
|
}
|
|
114
119
|
} catch (e) {
|
|
115
|
-
log("server", "failed to run startup
|
|
120
|
+
log("server", "failed to run startup context hook:", e);
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
|
|
@@ -4,7 +4,7 @@ import type { DaemonEvent, EventType } from "./daemon-client.js";
|
|
|
4
4
|
|
|
5
5
|
export type TransparencyPreset = "transparent" | "standard" | "private" | "silent";
|
|
6
6
|
|
|
7
|
-
type FilterableEventType = Exclude<EventType, "inbound" | "outbound">;
|
|
7
|
+
type FilterableEventType = Exclude<EventType, "inbound" | "outbound" | "context">;
|
|
8
8
|
|
|
9
9
|
const PRESET_RULES: Record<
|
|
10
10
|
TransparencyPreset,
|
|
@@ -53,7 +53,7 @@ const PRESET_RULES: Record<
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
// Communication records are always emitted (bypass transparency filtering)
|
|
56
|
-
const ALWAYS_ALLOWED: ReadonlySet<string> = new Set(["inbound", "outbound"]);
|
|
56
|
+
const ALWAYS_ALLOWED: ReadonlySet<string> = new Set(["inbound", "outbound", "context"]);
|
|
57
57
|
|
|
58
58
|
export function loadTransparencyPreset(): TransparencyPreset {
|
|
59
59
|
for (const file of ["home/.config/config.json", "home/.config/volute.json"]) {
|
|
@@ -115,12 +115,9 @@ export function createVoluteServer(options: {
|
|
|
115
115
|
channels: Record<string, any[]>;
|
|
116
116
|
};
|
|
117
117
|
router.dispatchBatch(batch, body.session ?? "main", body);
|
|
118
|
-
} else if (body.session) {
|
|
119
|
-
// Pre-routed by daemon delivery manager — dispatch directly
|
|
120
|
-
router.dispatch(body.content, body.session, body);
|
|
121
118
|
} else {
|
|
122
|
-
//
|
|
123
|
-
router.
|
|
119
|
+
// Pre-routed by daemon delivery manager — dispatch directly
|
|
120
|
+
router.dispatch(body.content, body.session ?? "main", body);
|
|
124
121
|
}
|
|
125
122
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
126
123
|
res.end(JSON.stringify({ ok: true }));
|