volute 0.25.0 → 0.27.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 +28 -33
- package/dist/{activity-events-4O37J7PD.js → activity-events-BBIEA2F4.js} +2 -3
- package/dist/api.d.ts +886 -220
- package/dist/{archive-4ZQYK5MN.js → archive-UA4BDFXQ.js} +2 -2
- package/dist/{auth-HM2RSPY7.js → auth-D3OT2ARB.js} +3 -3
- package/dist/bridge-FQHZL3MC.js +206 -0
- package/dist/chat-MHJ3L6JQ.js +58 -0
- package/dist/{chunk-PHU4DEAJ.js → chunk-2WPW7OT6.js} +3 -3
- package/dist/{chunk-BOTQ25QT.js → chunk-2YP2TVDT.js} +138 -56
- package/dist/{chunk-DG7TO7EE.js → chunk-4WXYUOAK.js} +5 -7
- package/dist/{chunk-JTDFJWI2.js → chunk-AW7PFDVN.js} +5 -5
- package/dist/{chunk-2767L2RZ.js → chunk-EHYDTZTF.js} +6 -6
- package/dist/{chunk-ZSH4G2P5.js → chunk-GIE6CSN5.js} +17 -17
- package/dist/chunk-H7OZRFJB.js +432 -0
- package/dist/{chunk-ON3FF5JA.js → chunk-HDN7MNGD.js} +3 -3
- package/dist/chunk-IAYBDWVG.js +477 -0
- package/dist/chunk-IKRVFPWU.js +83 -0
- package/dist/{chunk-TRQEV3CD.js → chunk-JGFVMROS.js} +32 -6
- package/dist/{chunk-PHHKNGA3.js → chunk-JKOWNZ4P.js} +3 -3
- package/dist/{chunk-E7GOKNOT.js → chunk-K5NAC55T.js} +1 -1
- package/dist/{chunk-HFCBO2GL.js → chunk-KDGS53OS.js} +4 -4
- package/dist/chunk-KTLFDYPT.js +61 -0
- package/dist/{chunk-3AIBT4TW.js → chunk-LAC664WU.js} +30 -4
- package/dist/{chunk-PMX4EIJK.js → chunk-OQZH4PBB.js} +467 -1054
- package/dist/{chunk-SHSWYG2J.js → chunk-PHSAT7YL.js} +71 -58
- package/dist/chunk-RKQEHRBB.js +177 -0
- package/dist/{chunk-RVKR2R7F.js → chunk-SSI47XP2.js} +10 -2
- package/dist/chunk-T6HKBWXZ.js +23 -0
- package/dist/chunk-USUXRNVD.js +113 -0
- package/dist/{chunk-BFK6SOEJ.js → chunk-VIVMW2H2.js} +4 -4
- package/dist/{chunk-KTJGZ7M7.js → chunk-XBLSAVJF.js} +1 -1
- package/dist/chunk-ZYGKG6VC.js +22 -0
- package/dist/cli.js +51 -32
- package/dist/{cloud-sync-PPBBJDY6.js → cloud-sync-T7M3ESC3.js} +15 -12
- package/dist/connectors/discord-bridge.js +158 -0
- package/dist/connectors/slack-bridge.js +119 -0
- package/dist/connectors/telegram-bridge.js +133 -0
- package/dist/conversations-M2K4253F.js +55 -0
- package/dist/create-D7J73A6H.js +45 -0
- package/dist/{create-VDQJER52.js → create-QWV73WXD.js} +1 -1
- package/dist/{daemon-client-JOVQZ52X.js → daemon-client-I42FK2BF.js} +2 -2
- package/dist/{daemon-restart-FDNOZEAD.js → daemon-restart-M2QTYMEG.js} +7 -6
- package/dist/daemon.js +2247 -1085
- package/dist/db-IC4J52XQ.js +8 -0
- package/dist/{delete-2MRR4JX5.js → delete-4JYGD4VN.js} +1 -1
- package/dist/down-LVBXEULC.js +14 -0
- package/dist/{env-2FPOZK37.js → env-YJMUMFIY.js} +5 -5
- package/dist/{export-IKFAPRAO.js → export-BOJQWBMA.js} +4 -4
- package/dist/{file-KT3UIQM3.js → file-CR36YUPD.js} +4 -4
- package/dist/{history-46WZN5CN.js → history-XKRTAFS2.js} +7 -7
- package/dist/{import-TH26J76F.js → import-SRTQXBGH.js} +4 -4
- package/dist/join-J4QU42DL.js +66 -0
- package/dist/list-R73GENNL.js +40 -0
- package/dist/{log-6SGSSR3D.js → log-ABYNVYJ3.js} +4 -4
- package/dist/login-3QZNR2DF.js +46 -0
- package/dist/{login-UO6AOVEA.js → login-XX37I52P.js} +3 -3
- package/dist/logout-T53VKCPU.js +39 -0
- package/dist/{logout-UKD5LA37.js → logout-W4KOOBIT.js} +2 -2
- package/dist/{logs-HRBONI5I.js → logs-U35JR2KE.js} +7 -7
- package/dist/{merge-KSFJKX6T.js → merge-LNSMSAOF.js} +4 -4
- package/dist/message-delivery-LDXLGERA.js +25 -0
- package/dist/migrate-registry-to-db-XC7T5B7P.js +110 -0
- package/dist/{mind-YVWAHL2A.js → mind-DI33C74K.js} +25 -25
- package/dist/{mind-activity-tracker-NMDDEV3K.js → mind-activity-tracker-EN6XNXPF.js} +3 -4
- package/dist/{mind-manager-4NDNAYAB.js → mind-manager-M6EMUW5I.js} +6 -5
- package/dist/{mind-sleep-GHPTSAYN.js → mind-sleep-BTSWQNAC.js} +4 -4
- package/dist/{mind-wake-BJDJFMDF.js → mind-wake-SBAKIDVP.js} +4 -4
- package/dist/notes-XCER3I7M.js +220 -0
- package/dist/{package-3HF5MXU2.js → package-7WY6VKU3.js} +2 -1
- package/dist/{pages-Y6DRWUOJ.js → pages-6EBS6CBR.js} +2 -2
- package/dist/{publish-EEKTZBHW.js → publish-66UB2ZFY.js} +5 -5
- package/dist/{pull-D32SPFVU.js → pull-XCHJTM5M.js} +4 -4
- package/dist/read-36UFXN3G.js +46 -0
- package/dist/{register-U2UO6TC4.js → register-6B2CXTYM.js} +3 -3
- package/dist/{registry-D2BSQ2X5.js → registry-NDNOOYG4.js} +15 -9
- package/dist/{restart-5BMNV7KU.js → restart-6ESL3NBO.js} +6 -6
- package/dist/sandbox-TGBX22DS.js +19 -0
- package/dist/{schedule-YEFDLVMJ.js → schedule-QTJMFATP.js} +7 -7
- package/dist/{seed-6FEKB3YC.js → seed-SSUCYYDF.js} +2 -2
- package/dist/{send-IISDYFCL.js → send-ZNCJDSRP.js} +28 -36
- package/dist/service-6LIN3F3K.js +122 -0
- package/dist/setup-JG4QAEBV.js +371 -0
- package/dist/setup-JHL5ZEST.js +17 -0
- package/dist/{shared-LWMNTTZN.js → shared-ML5I4Q2A.js} +4 -4
- package/dist/{skill-T3EMR6IR.js → skill-AUAQTSP5.js} +7 -7
- package/dist/skills/dreaming/SKILL.md +68 -0
- package/dist/skills/dreaming/references/INSTALL.md +56 -0
- package/dist/skills/dreaming/scripts/dream.ts +289 -0
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +30 -0
- package/dist/skills/notes/SKILL.md +34 -0
- package/dist/skills/orientation/SKILL.md +3 -3
- package/dist/skills/volute-mind/SKILL.md +32 -30
- package/dist/sleep-manager-MWYHM5HV.js +29 -0
- package/dist/split-TKJ5OT3P.js +63 -0
- package/dist/{sprout-QJVGJDSH.js → sprout-IJVVKSJ2.js} +6 -7
- package/dist/{start-C7XITZ5O.js → start-EUJSS5R4.js} +4 -4
- package/dist/{status-SIRPLEZC.js → status-77YEPHMW.js} +5 -5
- package/dist/{status-LYS4NUOZ.js → status-7GA4SM4Y.js} +4 -4
- package/dist/{status-LV34BG6G.js → status-THLOBLWG.js} +2 -2
- package/dist/{stop-CVKBSLXY.js → stop-3XAITBBF.js} +6 -6
- package/dist/{tailscale-AJ4VL5XK.js → tailscale-NY5MUMY3.js} +1 -1
- package/dist/up-NKSMXBWR.js +17 -0
- package/dist/{update-7XCZMYBT.js → update-PTSH22AZ.js} +11 -11
- package/dist/{update-check-F5Z3ALXX.js → update-check-64FWC4Y2.js} +2 -2
- package/dist/{upgrade-7RUIXGOO.js → upgrade-HA47CS4C.js} +12 -5
- package/dist/variant-7TGZHOU3.js +41 -0
- package/dist/{version-notify-AZQMC32A.js → version-notify-5Z4MNR6M.js} +26 -28
- package/dist/web-assets/assets/index-CI5wgghI.css +1 -0
- package/dist/web-assets/assets/index-is5CvJWH.js +75 -0
- package/dist/web-assets/favicon.png +0 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0015_notes.sql +23 -0
- package/drizzle/0016_note_reactions_and_replies.sql +15 -0
- package/drizzle/0017_minds.sql +16 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +2 -1
- package/templates/_base/.init/.config/hooks/wake-context.sh +7 -0
- package/templates/_base/.init/.config/prompts.json +2 -2
- package/templates/_base/home/VOLUTE.md +5 -5
- package/templates/_base/src/lib/startup.ts +10 -2
- package/templates/claude/src/agent.ts +51 -1
- package/templates/claude/src/server.ts +1 -0
- package/templates/pi/package.json.tmpl +1 -0
- package/templates/pi/src/agent.ts +48 -1
- package/templates/pi/src/lib/subagents.ts +150 -0
- package/templates/pi/src/server.ts +1 -0
- package/dist/channel-HZOSHGNF.js +0 -260
- package/dist/chunk-33XAVCS4.js +0 -203
- package/dist/chunk-B2CPS4QU.js +0 -283
- package/dist/chunk-NWPT4ASZ.js +0 -89
- package/dist/chunk-SIAG3QMM.js +0 -42
- package/dist/chunk-WSLPZF72.js +0 -173
- package/dist/connector-M6XFI6GM.js +0 -147
- package/dist/connectors/discord.js +0 -177
- package/dist/connectors/slack.js +0 -181
- package/dist/connectors/telegram.js +0 -187
- package/dist/down-674SX2IZ.js +0 -14
- package/dist/message-delivery-XMGV3FUM.js +0 -23
- package/dist/service-FASYWLTC.js +0 -247
- package/dist/setup-BMLM2UTK.js +0 -230
- package/dist/sleep-manager-RKTFZPD3.js +0 -27
- package/dist/up-CJ26KQLN.js +0 -15
- package/dist/variant-UGREB4G5.js +0 -207
- package/dist/web-assets/assets/index-CGPSVu19.js +0 -69
- package/dist/web-assets/assets/index-V_rNDsM8.css +0 -1
|
Binary file
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Averia+Serif+Libre:wght@300;400;700&family=Fira+Code:wght@300;400;500;600&family=Averia+Sans+Libre:ital,wght@0,300;0,400;0,500;0,600;1,400&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-is5CvJWH.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CI5wgghI.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
CREATE TABLE `notes` (
|
|
2
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
3
|
+
`author_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE,
|
|
4
|
+
`title` text NOT NULL,
|
|
5
|
+
`slug` text NOT NULL,
|
|
6
|
+
`content` text NOT NULL,
|
|
7
|
+
`created_at` text NOT NULL DEFAULT (datetime('now')),
|
|
8
|
+
`updated_at` text NOT NULL DEFAULT (datetime('now'))
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
CREATE UNIQUE INDEX `idx_notes_author_slug` ON `notes` (`author_id`, `slug`);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE INDEX `idx_notes_created_at` ON `notes` (`created_at`);
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
CREATE TABLE `note_comments` (
|
|
16
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
17
|
+
`note_id` integer NOT NULL REFERENCES `notes`(`id`) ON DELETE CASCADE,
|
|
18
|
+
`author_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE,
|
|
19
|
+
`content` text NOT NULL,
|
|
20
|
+
`created_at` text NOT NULL DEFAULT (datetime('now'))
|
|
21
|
+
);
|
|
22
|
+
--> statement-breakpoint
|
|
23
|
+
CREATE INDEX `idx_note_comments_note_id` ON `note_comments` (`note_id`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE `note_reactions` (
|
|
2
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
3
|
+
`note_id` integer NOT NULL REFERENCES `notes`(`id`) ON DELETE CASCADE,
|
|
4
|
+
`user_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE,
|
|
5
|
+
`emoji` text NOT NULL,
|
|
6
|
+
`created_at` text NOT NULL DEFAULT (datetime('now'))
|
|
7
|
+
);
|
|
8
|
+
--> statement-breakpoint
|
|
9
|
+
CREATE UNIQUE INDEX `idx_note_reactions_unique` ON `note_reactions` (`note_id`, `user_id`, `emoji`);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
CREATE INDEX `idx_note_reactions_note_id` ON `note_reactions` (`note_id`);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
ALTER TABLE `notes` ADD COLUMN `reply_to_id` integer REFERENCES `notes`(`id`) ON DELETE SET NULL;
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
CREATE INDEX `idx_notes_reply_to` ON `notes`(`reply_to_id`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE `minds` (
|
|
2
|
+
`name` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`port` integer NOT NULL,
|
|
4
|
+
`parent` text REFERENCES `minds`(`name`) ON DELETE CASCADE,
|
|
5
|
+
`dir` text,
|
|
6
|
+
`branch` text,
|
|
7
|
+
`stage` text,
|
|
8
|
+
`template` text,
|
|
9
|
+
`template_hash` text,
|
|
10
|
+
`running` integer NOT NULL DEFAULT 0,
|
|
11
|
+
`created_at` text NOT NULL DEFAULT (datetime('now'))
|
|
12
|
+
);
|
|
13
|
+
--> statement-breakpoint
|
|
14
|
+
CREATE UNIQUE INDEX `idx_minds_port` ON `minds` (`port`);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE INDEX `idx_minds_parent` ON `minds` (`parent`);
|
|
@@ -106,6 +106,27 @@
|
|
|
106
106
|
"when": 1772300000000,
|
|
107
107
|
"tag": "0014_conversation_reads",
|
|
108
108
|
"breakpoints": true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"idx": 15,
|
|
112
|
+
"version": "6",
|
|
113
|
+
"when": 1772400000000,
|
|
114
|
+
"tag": "0015_notes",
|
|
115
|
+
"breakpoints": true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"idx": 16,
|
|
119
|
+
"version": "6",
|
|
120
|
+
"when": 1772500000000,
|
|
121
|
+
"tag": "0016_note_reactions_and_replies",
|
|
122
|
+
"breakpoints": true
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"idx": 17,
|
|
126
|
+
"version": "6",
|
|
127
|
+
"when": 1772600000000,
|
|
128
|
+
"tag": "0017_minds",
|
|
129
|
+
"breakpoints": true
|
|
109
130
|
}
|
|
110
131
|
]
|
|
111
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "volute",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "CLI for creating and managing self-modifying AI minds powered by the Claude Agent SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"db:migrate": "drizzle-kit migrate"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.39",
|
|
59
60
|
"@hono/node-server": "^1.19.9",
|
|
60
61
|
"@hono/zod-validator": "^0.7.6",
|
|
61
62
|
"@libsql/client": "^0.17.0",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Wake context hook — generates context when waking from sleep.
|
|
3
|
+
# Input: JSON on stdin with { "sleepingSince": "ISO", "duration": "Xh Ym", "wakeTime": "ISO" }
|
|
4
|
+
# Output: Plain text included in the wake-up summary. Empty output = nothing added.
|
|
5
|
+
#
|
|
6
|
+
# Skills and extensions can append to this script to surface what happened during sleep.
|
|
7
|
+
# Example: check for new dream files, summarize overnight activity, etc.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compaction_warning": "Context is getting long — compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.",
|
|
3
3
|
"compaction_instructions": "Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
|
|
4
|
-
"reply_instructions": "To reply to this message, use: volute send ${channel} \"your message\"",
|
|
5
|
-
"channel_invite": "[Channel Invite]\n${headers}\n\n[${sender} — ${time}]\n${preview}\n\nFurther messages will be saved to ${filePath}\n\nTo accept, add to .config/routes.json:\n Rule: { \"channel\": \"${channel}\", \"session\": \"${suggestedSession}\" }\n${batchRecommendation}To respond, use: volute send ${channel} \"your message\"\nTo reject, delete ${filePath}"
|
|
4
|
+
"reply_instructions": "To reply to this message, use: volute chat send ${channel} \"your message\"",
|
|
5
|
+
"channel_invite": "[Channel Invite]\n${headers}\n\n[${sender} — ${time}]\n${preview}\n\nFurther messages will be saved to ${filePath}\n\nTo accept, add to .config/routes.json:\n Rule: { \"channel\": \"${channel}\", \"session\": \"${suggestedSession}\" }\n${batchRecommendation}To respond, use: volute chat send ${channel} \"your message\"\nTo reject, delete ${filePath}"
|
|
6
6
|
}
|
|
@@ -4,17 +4,17 @@ You are a volute mind — a persistent being that communicates across multiple c
|
|
|
4
4
|
|
|
5
5
|
## How to Communicate
|
|
6
6
|
|
|
7
|
-
**Your text output stays in your session — it is not sent to anyone.** To send a message, you must use the `volute send` command:
|
|
7
|
+
**Your text output stays in your session — it is not sent to anyone.** To send a message, you must use the `volute chat send` command:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
volute send @other-mind "hello" # DM another user
|
|
11
|
-
volute send discord:server/channel "hello" # send to a channel
|
|
12
|
-
volute send animal-chat "hello" # send to a volute channel
|
|
10
|
+
volute chat send @other-mind "hello" # DM another user
|
|
11
|
+
volute chat send discord:server/channel "hello" # send to a channel
|
|
12
|
+
volute chat send animal-chat "hello" # send to a volute channel
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
This applies to everything: replying to messages, talking to other minds, and reaching out on your own initiative. Piping from stdin avoids shell escaping issues:
|
|
16
16
|
```sh
|
|
17
|
-
echo "message with 'quotes' and $special chars" | volute send @other-mind
|
|
17
|
+
echo "message with 'quotes' and $special chars" | volute chat send @other-mind
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
## Channels
|
|
@@ -16,11 +16,19 @@ export function parseArgs(): { port: number } {
|
|
|
16
16
|
return { port };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export type SubagentConfig = {
|
|
20
|
+
description: string;
|
|
21
|
+
systemPrompt: string; // path relative to home/, e.g. "SOUL.md"
|
|
22
|
+
tools?: string[];
|
|
23
|
+
maxTurns?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
export function loadConfig(): {
|
|
20
27
|
model?: string;
|
|
21
28
|
logLevel?: "error" | "warn" | "info" | "debug";
|
|
22
29
|
compactionMessage?: string;
|
|
23
30
|
compaction?: { maxContextTokens?: number };
|
|
31
|
+
subagents?: Record<string, SubagentConfig>;
|
|
24
32
|
} {
|
|
25
33
|
// Mind-own config lives in config.json; fall back to volute.json for older minds
|
|
26
34
|
for (const file of ["home/.config/config.json", "home/.config/volute.json"]) {
|
|
@@ -120,7 +128,7 @@ const DEFAULT_PROMPTS: MindPrompts = {
|
|
|
120
128
|
"Context is getting long — compaction is about to summarize this conversation. Before that happens, save anything important to files (MEMORY.md, memory/journal/${date}.md, etc.) since those survive compaction. Focus on: decisions made, open tasks, and anything you'd need to pick up where you left off.",
|
|
121
129
|
compaction_instructions:
|
|
122
130
|
"Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
|
|
123
|
-
reply_instructions: 'To reply to this message, use: volute send ${channel} "your message"',
|
|
131
|
+
reply_instructions: 'To reply to this message, use: volute chat send ${channel} "your message"',
|
|
124
132
|
channel_invite: `[Channel Invite]
|
|
125
133
|
\${headers}
|
|
126
134
|
|
|
@@ -131,7 +139,7 @@ Further messages will be saved to \${filePath}
|
|
|
131
139
|
|
|
132
140
|
To accept, add to .config/routes.json:
|
|
133
141
|
Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
|
|
134
|
-
\${batchRecommendation}To respond, use: volute send \${channel} "your message"
|
|
142
|
+
\${batchRecommendation}To respond, use: volute chat send \${channel} "your message"
|
|
135
143
|
To reject, delete \${filePath}`,
|
|
136
144
|
};
|
|
137
145
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
1
3
|
import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
2
4
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
3
5
|
import { toSDKContent } from "./lib/content.js";
|
|
@@ -10,7 +12,7 @@ import { createSessionContextHook } from "./lib/hooks/session-context.js";
|
|
|
10
12
|
import { log } from "./lib/logger.js";
|
|
11
13
|
import { createMessageChannel } from "./lib/message-channel.js";
|
|
12
14
|
import { createSessionStore } from "./lib/session-store.js";
|
|
13
|
-
import { loadPrompts } from "./lib/startup.js";
|
|
15
|
+
import { loadPrompts, type SubagentConfig } from "./lib/startup.js";
|
|
14
16
|
import { consumeStream } from "./lib/stream-consumer.js";
|
|
15
17
|
import type {
|
|
16
18
|
HandlerMeta,
|
|
@@ -40,6 +42,7 @@ export function createMind(options: {
|
|
|
40
42
|
sessionsDir: string;
|
|
41
43
|
compactionMessage?: string;
|
|
42
44
|
maxContextTokens?: number;
|
|
45
|
+
subagents?: Record<string, SubagentConfig>;
|
|
43
46
|
onIdentityReload?: () => Promise<void>;
|
|
44
47
|
}): { resolve: HandlerResolver; waitForCommits: () => Promise<void> } {
|
|
45
48
|
const autoCommit = createAutoCommitHook(options.cwd);
|
|
@@ -64,6 +67,52 @@ export function createMind(options: {
|
|
|
64
67
|
// Per-session compaction state
|
|
65
68
|
const compactionTriggered = new Map<string, boolean>();
|
|
66
69
|
|
|
70
|
+
// --- Subagents (config-driven) ---
|
|
71
|
+
|
|
72
|
+
type SDKAgent = {
|
|
73
|
+
description: string;
|
|
74
|
+
prompt: string;
|
|
75
|
+
tools: string[];
|
|
76
|
+
model: "inherit";
|
|
77
|
+
maxTurns?: number;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function loadSubagents(
|
|
81
|
+
configs: Record<string, SubagentConfig> | undefined,
|
|
82
|
+
): Record<string, SDKAgent> | undefined {
|
|
83
|
+
if (!configs || Object.keys(configs).length === 0) return undefined;
|
|
84
|
+
const agents: Record<string, SDKAgent> = {};
|
|
85
|
+
for (const [name, config] of Object.entries(configs)) {
|
|
86
|
+
if (typeof config.description !== "string" || typeof config.systemPrompt !== "string") {
|
|
87
|
+
log("mind", `subagent "${name}": missing description or systemPrompt, skipping`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const prompt = readFileSync(resolvePath(options.cwd, config.systemPrompt), "utf-8");
|
|
92
|
+
if (!prompt) {
|
|
93
|
+
log("mind", `subagent "${name}": ${config.systemPrompt} is empty, skipping`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
agents[name] = {
|
|
97
|
+
description: config.description,
|
|
98
|
+
prompt,
|
|
99
|
+
tools: config.tools ?? ["Read", "Write", "Bash"],
|
|
100
|
+
model: "inherit" as const,
|
|
101
|
+
maxTurns: config.maxTurns,
|
|
102
|
+
};
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
if (err?.code === "ENOENT") {
|
|
105
|
+
log("mind", `subagent "${name}": ${config.systemPrompt} not found, skipping`);
|
|
106
|
+
} else {
|
|
107
|
+
log("mind", `subagent "${name}": failed to read ${config.systemPrompt}: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return Object.keys(agents).length > 0 ? agents : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const agents = loadSubagents(options.subagents);
|
|
115
|
+
|
|
67
116
|
// --- Event broadcasting ---
|
|
68
117
|
|
|
69
118
|
function broadcastToSession(session: Session, event: VoluteEvent) {
|
|
@@ -106,6 +155,7 @@ export function createMind(options: {
|
|
|
106
155
|
model: options.model,
|
|
107
156
|
maxThinkingTokens: options.maxThinkingTokens,
|
|
108
157
|
resume,
|
|
158
|
+
agents,
|
|
109
159
|
hooks: {
|
|
110
160
|
PostToolUse: postToolUseHooks,
|
|
111
161
|
PreCompact: [{ hooks: [preCompactHook] }],
|
|
@@ -42,6 +42,7 @@ const mind = createMind({
|
|
|
42
42
|
sessionsDir,
|
|
43
43
|
compactionMessage: config.compactionMessage,
|
|
44
44
|
maxContextTokens: config.compaction?.maxContextTokens,
|
|
45
|
+
subagents: config.subagents,
|
|
45
46
|
onIdentityReload: async () => {
|
|
46
47
|
log("server", "identity file changed — restarting to reload");
|
|
47
48
|
await mind.waitForCommits();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
1
3
|
import {
|
|
2
4
|
AuthStorage,
|
|
3
5
|
createAgentSession,
|
|
@@ -13,7 +15,8 @@ import { log } from "./lib/logger.js";
|
|
|
13
15
|
import { createReplyInstructionsExtension } from "./lib/reply-instructions-extension.js";
|
|
14
16
|
import { resolveModel } from "./lib/resolve-model.js";
|
|
15
17
|
import { createSessionContextExtension } from "./lib/session-context-extension.js";
|
|
16
|
-
import { loadPrompts } from "./lib/startup.js";
|
|
18
|
+
import { loadPrompts, type SubagentConfig } from "./lib/startup.js";
|
|
19
|
+
import { createSubagentExtension, type SubagentDefinition } from "./lib/subagents.js";
|
|
17
20
|
import type {
|
|
18
21
|
HandlerMeta,
|
|
19
22
|
HandlerResolver,
|
|
@@ -44,6 +47,7 @@ export function createMind(options: {
|
|
|
44
47
|
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
45
48
|
compactionMessage?: string;
|
|
46
49
|
maxContextTokens?: number;
|
|
50
|
+
subagents?: Record<string, SubagentConfig>;
|
|
47
51
|
}): { resolve: HandlerResolver } {
|
|
48
52
|
const sessions = new Map<string, PiSession>();
|
|
49
53
|
const prompts = loadPrompts();
|
|
@@ -63,6 +67,48 @@ export function createMind(options: {
|
|
|
63
67
|
const authStorage = new AuthStorage();
|
|
64
68
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
65
69
|
|
|
70
|
+
// --- Subagents (config-driven) ---
|
|
71
|
+
|
|
72
|
+
function loadSubagents(
|
|
73
|
+
configs: Record<string, SubagentConfig> | undefined,
|
|
74
|
+
): Record<string, SubagentDefinition> {
|
|
75
|
+
const result: Record<string, SubagentDefinition> = {};
|
|
76
|
+
if (!configs) return result;
|
|
77
|
+
for (const [name, config] of Object.entries(configs)) {
|
|
78
|
+
if (typeof config.description !== "string" || typeof config.systemPrompt !== "string") {
|
|
79
|
+
log("mind", `subagent "${name}": missing description or systemPrompt, skipping`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const prompt = readFileSync(resolvePath(options.cwd, config.systemPrompt), "utf-8");
|
|
84
|
+
if (!prompt) {
|
|
85
|
+
log("mind", `subagent "${name}": ${config.systemPrompt} is empty, skipping`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
result[name] = {
|
|
89
|
+
description: config.description,
|
|
90
|
+
prompt,
|
|
91
|
+
tools: config.tools,
|
|
92
|
+
maxTurns: config.maxTurns,
|
|
93
|
+
};
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
if (err?.code === "ENOENT") {
|
|
96
|
+
log("mind", `subagent "${name}": ${config.systemPrompt} not found, skipping`);
|
|
97
|
+
} else {
|
|
98
|
+
log("mind", `subagent "${name}": failed to read ${config.systemPrompt}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const subagents = loadSubagents(options.subagents);
|
|
106
|
+
|
|
107
|
+
const subagentExtension =
|
|
108
|
+
Object.keys(subagents).length > 0
|
|
109
|
+
? createSubagentExtension(subagents, { cwd: options.cwd, model, authStorage, modelRegistry })
|
|
110
|
+
: undefined;
|
|
111
|
+
|
|
66
112
|
// --- Session lifecycle ---
|
|
67
113
|
|
|
68
114
|
function getOrCreateSession(name: string): PiSession {
|
|
@@ -158,6 +204,7 @@ export function createMind(options: {
|
|
|
158
204
|
preCompactExtension,
|
|
159
205
|
sessionContextExtension,
|
|
160
206
|
replyInstructionsExtension,
|
|
207
|
+
...(subagentExtension ? [subagentExtension] : []),
|
|
161
208
|
],
|
|
162
209
|
});
|
|
163
210
|
await resourceLoader.reload();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Model } from "@mariozechner/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
type AuthStorage,
|
|
4
|
+
bashTool,
|
|
5
|
+
codingTools,
|
|
6
|
+
createAgentSession,
|
|
7
|
+
DefaultResourceLoader,
|
|
8
|
+
type ExtensionFactory,
|
|
9
|
+
editTool,
|
|
10
|
+
type ModelRegistry,
|
|
11
|
+
readTool,
|
|
12
|
+
SessionManager,
|
|
13
|
+
SettingsManager,
|
|
14
|
+
writeTool,
|
|
15
|
+
} from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
import { log } from "./logger.js";
|
|
18
|
+
|
|
19
|
+
export type SubagentDefinition = {
|
|
20
|
+
description: string;
|
|
21
|
+
prompt: string;
|
|
22
|
+
tools?: string[]; // e.g. ["Read", "Write", "Bash"] — defaults to all coding tools
|
|
23
|
+
maxTurns?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function createSubagentExtension(
|
|
27
|
+
agents: Record<string, SubagentDefinition>,
|
|
28
|
+
context: {
|
|
29
|
+
cwd: string;
|
|
30
|
+
model: Model<any>;
|
|
31
|
+
authStorage: AuthStorage;
|
|
32
|
+
modelRegistry: ModelRegistry;
|
|
33
|
+
},
|
|
34
|
+
): ExtensionFactory {
|
|
35
|
+
return (pi) => {
|
|
36
|
+
for (const [name, def] of Object.entries(agents)) {
|
|
37
|
+
pi.registerTool({
|
|
38
|
+
name,
|
|
39
|
+
label: name.charAt(0).toUpperCase() + name.slice(1),
|
|
40
|
+
description: def.description,
|
|
41
|
+
parameters: Type.Object({
|
|
42
|
+
prompt: Type.String({ description: "The prompt for the subagent" }),
|
|
43
|
+
}),
|
|
44
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
45
|
+
try {
|
|
46
|
+
const tools = resolveTools(def.tools);
|
|
47
|
+
|
|
48
|
+
const loader = new DefaultResourceLoader({
|
|
49
|
+
cwd: context.cwd,
|
|
50
|
+
systemPromptOverride: () => def.prompt,
|
|
51
|
+
settingsManager: SettingsManager.inMemory({}),
|
|
52
|
+
});
|
|
53
|
+
await loader.reload();
|
|
54
|
+
|
|
55
|
+
const { session } = await createAgentSession({
|
|
56
|
+
cwd: context.cwd,
|
|
57
|
+
model: context.model,
|
|
58
|
+
tools,
|
|
59
|
+
resourceLoader: loader,
|
|
60
|
+
sessionManager: SessionManager.inMemory(),
|
|
61
|
+
settingsManager: SettingsManager.inMemory({}),
|
|
62
|
+
authStorage: context.authStorage,
|
|
63
|
+
modelRegistry: context.modelRegistry,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const textParts: string[] = [];
|
|
67
|
+
let turnCount = 0;
|
|
68
|
+
|
|
69
|
+
const done = new Promise<void>((resolve, reject) => {
|
|
70
|
+
const timeout = setTimeout(() => {
|
|
71
|
+
session.abort();
|
|
72
|
+
reject(new Error(`Subagent "${name}" timed out after 5 minutes`));
|
|
73
|
+
}, 300_000);
|
|
74
|
+
|
|
75
|
+
session.subscribe((event: any) => {
|
|
76
|
+
if (event.type === "agent_error") {
|
|
77
|
+
clearTimeout(timeout);
|
|
78
|
+
reject(
|
|
79
|
+
new Error(`Subagent "${name}" error: ${event.error?.message ?? "unknown"}`),
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (event.type === "turn_end") {
|
|
84
|
+
turnCount++;
|
|
85
|
+
if (def.maxTurns && turnCount >= def.maxTurns) {
|
|
86
|
+
session.abort();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (event.type === "agent_end") {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
for (const msg of event.messages ?? []) {
|
|
92
|
+
if (msg.role === "assistant" && msg.content) {
|
|
93
|
+
for (const block of msg.content) {
|
|
94
|
+
if (block.type === "text") textParts.push(block.text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
resolve();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await session.prompt(params.prompt);
|
|
104
|
+
await done;
|
|
105
|
+
|
|
106
|
+
log("mind", `subagent "${name}": completed after ${turnCount} turns`);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text" as const, text: textParts.join("\n") || "(no output)" }],
|
|
110
|
+
details: {},
|
|
111
|
+
};
|
|
112
|
+
} catch (err: any) {
|
|
113
|
+
log("mind", `subagent "${name}" failed: ${err.message}`);
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text" as const, text: `[subagent error] ${err.message}` }],
|
|
116
|
+
details: {},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const TOOL_MAP: Record<string, any> = {
|
|
126
|
+
Read: readTool,
|
|
127
|
+
Write: writeTool,
|
|
128
|
+
Bash: bashTool,
|
|
129
|
+
Edit: editTool,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function resolveTools(names: string[] | undefined) {
|
|
133
|
+
if (!names) return codingTools;
|
|
134
|
+
const resolved = names
|
|
135
|
+
.map((n) => {
|
|
136
|
+
if (!TOOL_MAP[n]) {
|
|
137
|
+
log(
|
|
138
|
+
"mind",
|
|
139
|
+
`unknown subagent tool "${n}" — available: ${Object.keys(TOOL_MAP).join(", ")}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return TOOL_MAP[n];
|
|
143
|
+
})
|
|
144
|
+
.filter(Boolean);
|
|
145
|
+
if (resolved.length === 0) {
|
|
146
|
+
log("mind", "no valid tools resolved for subagent, falling back to all coding tools");
|
|
147
|
+
return codingTools;
|
|
148
|
+
}
|
|
149
|
+
return resolved;
|
|
150
|
+
}
|