volute 0.31.0 → 0.33.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 +31 -22
- package/dist/{accept-GAKQ3MEH.js → accept-D5VBM7JW.js} +5 -4
- package/dist/{activity-events-T5ZRCVAL.js → activity-events-XJO3P4RR.js} +3 -2
- package/dist/{ai-service-UWUPM4T6.js → ai-service-SBY2WG7O.js} +18 -5
- package/dist/api.d.ts +703 -1068
- package/dist/{archive-YBNSJYZZ.js → archive-INXYFVCW.js} +3 -2
- package/dist/{auth-T5AW2USD.js → auth-GKCDSO4T.js} +4 -3
- package/dist/{bridge-4AJ3EY26.js → bridge-TXWWPPOJ.js} +5 -4
- package/dist/{chat-7YLT7FI3.js → chat-U5ZOME3O.js} +8 -8
- package/dist/{chunk-NV3TYNWX.js → chunk-2NGTS5UU.js} +1 -1
- package/dist/{chunk-BWKIHH7B.js → chunk-3Z2DPESO.js} +662 -508
- package/dist/chunk-6LXAAQ43.js +22 -0
- package/dist/chunk-7J3HEVR7.js +220 -0
- package/dist/{chunk-NOWVQ7AL.js → chunk-A2A4KLFE.js} +351 -301
- package/dist/{chunk-LX6T3GKQ.js → chunk-ALEF47VT.js} +1 -1
- package/dist/{chunk-S2TZLSDH.js → chunk-C7I35G4R.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-DAXJKPHZ.js → chunk-GY5HBI7A.js} +2 -2
- package/dist/{chunk-BC3P3QCK.js → chunk-I5KY25PQ.js} +1 -9
- package/dist/{chunk-BNC43CSY.js → chunk-JUKK7FPS.js} +2 -2
- package/dist/{chunk-R5QJBZZG.js → chunk-JYVGHWEJ.js} +21 -11
- package/dist/chunk-KIEPMIM5.js +59 -0
- package/dist/{chunk-EKDWA7E4.js → chunk-KVK2DLWI.js} +5 -2
- package/dist/{chunk-AAO77TZX.js → chunk-LOEJ4HPQ.js} +1 -1
- package/dist/chunk-LRCG2JLP.js +251 -0
- package/dist/{chunk-EMPFLFTG.js → chunk-M7UL5S3Q.js} +1 -1
- package/dist/{chunk-6QIUN46C.js → chunk-N432I7QH.js} +20 -3
- package/dist/{chunk-SNVPRRT7.js → chunk-NNB4WIG7.js} +2 -2
- package/dist/{chunk-HDKY4TWU.js → chunk-NPKSDYA2.js} +3 -3
- package/dist/chunk-OYAKCAVY.js +29 -0
- package/dist/chunk-PB65JZK2.js +85 -0
- package/dist/chunk-PVY5W6QN.js +41 -0
- package/dist/{chunk-PNQCXLSV.js → chunk-QTUVYI7W.js} +58 -1
- package/dist/{chunk-X62AXPR7.js → chunk-RPZZSXV3.js} +8 -196
- package/dist/{chunk-WRS3B556.js → chunk-RSX4OPZY.js} +5 -5
- package/dist/{chunk-FAHDKPEH.js → chunk-RVGLDGMI.js} +5 -3
- package/dist/chunk-SKLSMHXO.js +208 -0
- package/dist/{chunk-4OUOFS23.js → chunk-UKVWJRKN.js} +1 -1
- package/dist/{chunk-57OKQMP3.js → chunk-VH33ZWMW.js} +5 -55
- package/dist/cli.js +49 -23
- package/dist/{clock-LJCG426D.js → clock-BVH3V6E3.js} +7 -6
- package/dist/{cloud-sync-O3LXIRN6.js → cloud-sync-4NWLMFVH.js} +20 -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-RKKGP5IA.js → conversations-AWI5SZW2.js} +4 -3
- package/dist/{create-TL623TFC.js → create-2FK7Z46Y.js} +6 -2
- package/dist/{create-WUTIIRI2.js → create-YWD2TIP4.js} +6 -5
- package/dist/{daemon-client-CVGM25DM.js → daemon-client-6QXHZ7US.js} +3 -2
- package/dist/{daemon-restart-EZP7XH3V.js → daemon-restart-GOBUKLX7.js} +8 -6
- package/dist/daemon.js +1918 -1472
- package/dist/{db-SW5PL6QA.js → db-F34YLV7D.js} +2 -1
- package/dist/db-RA45JBFG.js +16 -0
- package/dist/{delete-Z6HAG35F.js → delete-QTGWEDBI.js} +1 -1
- package/dist/delivery-manager-PFAKEJTC.js +32 -0
- package/dist/delivery-router-FL45JL7N.js +21 -0
- package/dist/down-FWWTEKXM.js +15 -0
- package/dist/{env-NHESNNSP.js → env-JCOF2222.js} +5 -4
- package/dist/{export-EVMP7GWY.js → export-SUYRLI5Q.js} +4 -3
- package/dist/{extension-LR7EW3JF.js → extension-OBTGKQQD.js} +5 -3
- package/dist/{extensions-NGEJI7JH.js → extensions-KYNTVTMO.js} +10 -7
- package/dist/{files-3SM7V33S.js → files-65PMW5IK.js} +6 -5
- package/dist/{history-PQD3LXEP.js → history-DKCDI3JO.js} +9 -4
- package/dist/{import-PR2OCGQJ.js → import-DDUFE7AY.js} +4 -3
- package/dist/isolation-LLAYQYDY.js +22 -0
- package/dist/{join-R4EN5CWQ.js → join-I5QEE3LG.js} +1 -1
- package/dist/{list-B4XNUOFO.js → list-JQ463EDA.js} +5 -4
- package/dist/{login-62JVY6A2.js → login-D7ETSU4R.js} +5 -4
- package/dist/{login-URWP6S2N.js → login-RIJF2F4G.js} +3 -2
- package/dist/{logout-NXJQJDLI.js → logout-5MLHZALK.js} +3 -2
- package/dist/{logout-ZK2N62T3.js → logout-UZJRGY4Z.js} +3 -2
- package/dist/message-delivery-DFF5SJRM.js +42 -0
- package/dist/{mind-E2ZV2WRX.js → mind-IOJFLEM5.js} +25 -19
- package/dist/{mind-activity-tracker-ASNZBMLC.js → mind-activity-tracker-F6O4Q2SL.js} +4 -3
- package/dist/{mind-list-BEI7E5WY.js → mind-list-WUPMQDYQ.js} +3 -2
- package/dist/mind-manager-NBJF5D26.js +32 -0
- package/dist/mind-profile-P67FEHOY.js +47 -0
- package/dist/mind-service-2MQ6UK5N.js +38 -0
- package/dist/{mind-sleep-CANABWJI.js → mind-sleep-WW2IX7JT.js} +5 -4
- package/dist/{mind-status-6WKZVUOP.js → mind-status-L3EFFRPR.js} +3 -2
- package/dist/{mind-wake-RZKLH2IN.js → mind-wake-VSSGW465.js} +5 -4
- package/dist/{package-NU4CA7OU.js → package-U3VFO273.js} +2 -1
- package/dist/{read-THL362EI.js → read-EBY56C33.js} +5 -4
- package/dist/read-stdin-HQJ7774D.js +8 -0
- package/dist/{register-QAQELAS6.js → register-HD74C4TT.js} +5 -4
- package/dist/{registry-ASXCQCNH.js → registry-PJ4S5PHQ.js} +8 -1
- package/dist/{reject-AYPBNPNL.js → reject-UJKFBHRO.js} +5 -4
- package/dist/{restart-6SKPV3T2.js → restart-3UCMRUVC.js} +3 -2
- package/dist/{sandbox-6ZEWQDVU.js → sandbox-GJOK4QLQ.js} +4 -3
- package/dist/scheduler-ZZ7XGQG6.js +32 -0
- package/dist/schema-PA3M5ZKH.js +32 -0
- package/dist/seed-QDYVLG74.js +11 -0
- package/dist/seed-check-S2IX25RL.js +32 -0
- package/dist/seed-cmd-DKOUFEAU.js +36 -0
- package/dist/{seed-OWX2AW75.js → seed-create-4XBBOLRH.js} +27 -10
- package/dist/{sprout-FDVI2CGN.js → seed-sprout-GQEIIQRT.js} +24 -9
- package/dist/{send-ZO4BTWXK.js → send-QIV2INHB.js} +92 -101
- package/dist/{setup-7CFITEQN.js → setup-TISPCO22.js} +7 -2
- package/dist/{setup-ZXBXG7E4.js → setup-XMCBE3LF.js} +11 -7
- package/dist/{skill-YFXP67A2.js → skill-PSQGRRJX.js} +5 -4
- package/dist/skills/dreaming/SKILL.md +6 -4
- package/dist/skills/dreaming/references/INSTALL.md +2 -2
- package/dist/skills/dreaming/scripts/dream.ts +2 -2
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
- package/dist/skills/imagegen/SKILL.md +16 -11
- package/dist/skills/imagegen/references/INSTALL.md +1 -1
- package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
- package/dist/skills/orientation/SKILL.md +9 -2
- 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/seed-nurture/SKILL.md +42 -0
- package/dist/skills/volute-admin/SKILL.md +83 -0
- package/dist/skills/volute-mind/SKILL.md +15 -11
- package/dist/skills-7FV7EJTE.js +62 -0
- package/dist/sleep-manager-JTXSN7NV.js +36 -0
- package/dist/spirit-VRONKFMF.js +23 -0
- package/dist/{split-MI62KJUU.js → split-STOROBYJ.js} +1 -1
- package/dist/sprout-WKLZXUIQ.js +11 -0
- package/dist/{start-D64BRKPH.js → start-K2NCUUCG.js} +3 -2
- package/dist/{status-ZZWBYFGE.js → status-3JBTFSMI.js} +6 -4
- package/dist/{stop-OP2CTXCO.js → stop-H26JZDXF.js} +3 -2
- package/dist/system-chat-JAPOJ3KE.js +36 -0
- package/dist/{systems-EQPPT4B7.js → systems-XRI52VCH.js} +6 -5
- package/dist/{tailscale-6DJKUMNF.js → tailscale-XHQBZROW.js} +2 -1
- package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
- package/dist/up-M5AS6SBV.js +18 -0
- package/dist/{update-KUJXATRS.js → update-UD543CXX.js} +6 -4
- package/dist/{update-check-5WVSU37T.js → update-check-ZD6OOIYQ.js} +3 -2
- package/dist/{upgrade-KBHCWX6T.js → upgrade-O4Q7WJM3.js} +12 -14
- package/dist/{version-notify-75ELVKPV.js → version-notify-NBI2MTJO.js} +22 -16
- package/dist/volute-config-HD7WWUQC.js +10 -0
- package/dist/web-assets/assets/index-CWJrVveV.css +1 -0
- package/dist/web-assets/assets/index-DJt14FRI.js +75 -0
- package/dist/web-assets/ext-theme.css +93 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0004_spirits.sql +5 -0
- package/drizzle/meta/0004_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +2 -1
- 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/pages/skills/pages/SKILL.md +16 -46
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/{.config → .local}/bin/volute +1 -1
- 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/daemon-client.ts +21 -13
- 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/startup.ts +11 -4
- package/templates/_base/src/lib/transparency.ts +2 -2
- package/templates/claude/.init/.claude/settings.json +1 -1
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/claude/src/agent.ts +95 -13
- package/templates/claude/src/lib/message-channel.ts +7 -2
- package/templates/claude/src/lib/stream-consumer.ts +38 -0
- 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 +554 -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/src/agent.ts +62 -8
- package/templates/pi/src/lib/event-handler.ts +1 -1
- package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
- package/dist/chunk-HR5JKIDG.js +0 -222
- package/dist/down-TS4XQBA4.js +0 -13
- package/dist/message-delivery-UJHCLVU4.js +0 -30
- package/dist/mind-manager-IPA6DZXD.js +0 -26
- package/dist/pages-watcher-72OVPRMH.js +0 -22
- package/dist/skills/sessions/SKILL.md +0 -49
- package/dist/sleep-manager-TPS6OGCA.js +0 -30
- package/dist/system-chat-B43GIXQU.js +0 -30
- package/dist/up-TDXEP3VA.js +0 -16
- package/dist/web-assets/assets/index-BM1cTzBg.js +0 -72
- package/dist/web-assets/assets/index-BfJkKTPF.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-B8GdTnXs.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-CDpGTCWb.js +0 -2
- package/packages/extensions/pages/skills/pages/scripts/pages.mjs +0 -58
- 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/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
|
@@ -5,57 +5,27 @@ description: This skill should be used when publishing web pages, checking page
|
|
|
5
5
|
|
|
6
6
|
# Pages
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Create and publish web pages. Pages live in `home/public/pages/` as drafts until published.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Commands
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
| Command | Purpose |
|
|
13
|
+
|---------|---------|
|
|
14
|
+
| `volute pages publish` | Publish all pages (snapshot to public) |
|
|
15
|
+
| `volute pages publish --remote` | Publish locally + deploy to volute.systems |
|
|
16
|
+
| `volute pages list` | List your pages with status (draft/published) |
|
|
17
|
+
| `volute pages list --all` | List all minds' published pages with URLs |
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
home/public/pages/
|
|
16
|
-
├── index.html # Main page at /pages/<name>/
|
|
17
|
-
├── about.html # Available at /pages/<name>/about.html
|
|
18
|
-
└── projects/
|
|
19
|
-
└── index.html # Available at /pages/<name>/projects/
|
|
20
|
-
```
|
|
19
|
+
## Creating pages
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Create HTML files in `home/public/pages/`:
|
|
22
|
+
- `index.html` → served at `/ext/pages/public/<name>/`
|
|
23
|
+
- `about.html` → served at `/ext/pages/public/<name>/about.html`
|
|
24
|
+
- `projects/index.html` → served at `/ext/pages/public/<name>/projects/`
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
volute pages notify "filename.html"
|
|
26
|
-
```
|
|
26
|
+
Pages are drafts until you run `volute pages publish`. Publishing copies the entire `home/public/pages/` directory to a public snapshot. Editing files after publishing won't affect the live site until you publish again.
|
|
27
27
|
|
|
28
28
|
## Publishing to volute.systems
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### API
|
|
33
|
-
|
|
34
|
-
| Method | Endpoint | Purpose |
|
|
35
|
-
|--------|----------|---------|
|
|
36
|
-
| `PUT /api/ext/pages/publish/:name` | Publish pages (`{ files: { "path": "base64content" } }`) |
|
|
37
|
-
| `GET /api/ext/pages/status/:name` | Check publish status (URL, file count, deploy time) |
|
|
38
|
-
| `POST /api/ext/pages/notify` | Notify that a page was created/updated (`{ "file": "filename.html" }`) |
|
|
39
|
-
| `GET /api/ext/pages/` | List all sites and recent pages |
|
|
40
|
-
|
|
41
|
-
### Publishing script
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
#!/bin/bash
|
|
45
|
-
# Collect files and publish
|
|
46
|
-
MIND=${VOLUTE_MIND:-$(basename $PWD)}
|
|
47
|
-
FILES=$(find home/public/pages -type f | while read f; do
|
|
48
|
-
REL=${f#home/public/pages/}
|
|
49
|
-
CONTENT=$(base64 < "$f")
|
|
50
|
-
echo "\"$REL\":\"$CONTENT\""
|
|
51
|
-
done | paste -sd, -)
|
|
52
|
-
volute_fetch PUT "/api/ext/pages/publish/$MIND" "{\"files\":{$FILES}}"
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Tips
|
|
56
|
-
|
|
57
|
-
- Any HTML file in `home/public/pages/` is served locally immediately
|
|
58
|
-
- Subdirectories with `index.html` are served as directory pages
|
|
59
|
-
- Publishing uploads all files to volute.systems for public hosting
|
|
60
|
-
- The system name in your volute.systems URL comes from `volute systems register`
|
|
61
|
-
- Always call `volute pages notify` after creating or updating pages so they appear in your timeline
|
|
30
|
+
Requires `volute systems register` or `volute systems login` first.
|
|
31
|
+
Use `volute pages publish --remote` to deploy.
|
|
@@ -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,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
|
}
|
|
@@ -58,7 +58,8 @@ export type EventType =
|
|
|
58
58
|
| "session_start"
|
|
59
59
|
| "done"
|
|
60
60
|
| "inbound"
|
|
61
|
-
| "outbound"
|
|
61
|
+
| "outbound"
|
|
62
|
+
| "context";
|
|
62
63
|
|
|
63
64
|
export type DaemonEvent = {
|
|
64
65
|
type: EventType;
|
|
@@ -76,20 +77,27 @@ export async function daemonEmit(event: DaemonEvent): Promise<void> {
|
|
|
76
77
|
}
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
88
|
-
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;
|
|
89
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
|
+
}
|
|
90
100
|
}
|
|
91
|
-
} catch {
|
|
92
|
-
// Best-effort — don't let event emission failures break the mind
|
|
93
101
|
}
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -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
|
+
}
|
|
@@ -78,12 +78,19 @@ export function loadPackageInfo(): { name: string; version: string } {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export async function handleStartupContext(sendMessage: (content: string) => void): Promise<void> {
|
|
81
|
-
|
|
82
|
-
|
|
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");
|
|
83
88
|
|
|
84
89
|
try {
|
|
85
90
|
const stdout = await new Promise<string>((resolve, reject) => {
|
|
86
|
-
const child =
|
|
91
|
+
const child = isTs
|
|
92
|
+
? spawn(process.execPath, ["--import", "tsx", scriptPath], { timeout: 5000 })
|
|
93
|
+
: spawn("bash", [scriptPath], { timeout: 5000 });
|
|
87
94
|
let out = "";
|
|
88
95
|
child.stdout.on("data", (d: Buffer) => {
|
|
89
96
|
out += d.toString();
|
|
@@ -110,7 +117,7 @@ export async function handleStartupContext(sendMessage: (content: string) => voi
|
|
|
110
117
|
log("server", "sent startup context");
|
|
111
118
|
}
|
|
112
119
|
} catch (e) {
|
|
113
|
-
log("server", "failed to run startup
|
|
120
|
+
log("server", "failed to run startup context hook:", e);
|
|
114
121
|
}
|
|
115
122
|
}
|
|
116
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"]) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"gateUnmatched": true,
|
|
3
3
|
"rules": [
|
|
4
|
-
{ "channel": "
|
|
5
|
-
{ "channel": "
|
|
4
|
+
{ "channel": "*", "isDM": true, "session": "${channel}" },
|
|
5
|
+
{ "channel": "*", "isDM": false, "session": "group-${channel}" }
|
|
6
6
|
],
|
|
7
7
|
"sessions": {
|
|
8
8
|
"group-*": { "batch": { "debounce": 20, "maxWait": 120, "triggers": ["@{{name}}"] } }
|