volute 0.4.0 → 0.5.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 +22 -22
- package/dist/agent-Z2B6EFEQ.js +75 -0
- package/dist/{agent-manager-AUCKMGPR.js → agent-manager-PXBKA2GK.js} +4 -4
- package/dist/channel-MK5OK2SI.js +113 -0
- package/dist/chunk-5X7HGB6L.js +107 -0
- package/dist/{chunk-YGFIWIOF.js → chunk-7L4AN5D4.js} +1 -1
- package/dist/{chunk-VRVVQIYY.js → chunk-AZEL2IEK.js} +1 -1
- package/dist/chunk-B3R6L2GW.js +24 -0
- package/dist/{chunk-DNOXHLE5.js → chunk-HE67X4T6.js} +1 -1
- package/dist/{chunk-I6OHXCMV.js → chunk-MW2KFO3B.js} +47 -9
- package/dist/{chunk-5OCWMTVS.js → chunk-SMISE4SV.js} +77 -3
- package/dist/{chunk-SOZA2TLP.js → chunk-UAVD2AHX.js} +1 -1
- package/dist/{chunk-3C2XR4IY.js → chunk-UX25Z2ND.js} +113 -107
- package/dist/{chunk-GSPKUPKU.js → chunk-XUA3JUFK.js} +2 -1
- package/dist/chunk-ZYGKG6VC.js +22 -0
- package/dist/cli.js +86 -74
- package/dist/{connector-DKDJTLYZ.js → connector-LYEMXQEV.js} +11 -6
- package/dist/connectors/discord.js +3 -1
- package/dist/connectors/slack.js +14 -5
- package/dist/connectors/telegram.js +21 -2
- package/dist/conversation-ERXEQZTY.js +163 -0
- package/dist/create-RVCZN6HE.js +91 -0
- package/dist/{daemon-client-XR24PUJF.js → daemon-client-ZY6UUN2M.js} +2 -2
- package/dist/daemon.js +629 -177
- package/dist/{delete-55MXCEY5.js → delete-3QH7VYIN.js} +7 -8
- package/dist/{down-3OB6UVAJ.js → down-O7IFZLVJ.js} +1 -1
- package/dist/{env-JB27UAC3.js → env-4D4REPJF.js} +8 -5
- package/dist/{history-BKG74I43.js → history-OEONB53Z.js} +3 -3
- package/dist/{import-4CI2ZUTJ.js → import-MXJB2EII.js} +8 -8
- package/dist/{logs-NXFFGUKY.js → logs-DF342W4M.js} +2 -2
- package/dist/message-ADHWFHSI.js +32 -0
- package/dist/{package-Z2SFO2SV.js → package-VQOE7JNH.js} +1 -1
- package/dist/{schedule-A35SH4HT.js → schedule-NAG6F463.js} +10 -5
- package/dist/send-66QMKRUH.js +75 -0
- package/dist/{setup-2FDVN7OF.js → setup-RPRRGG2F.js} +5 -5
- package/dist/{start-LDPMCMYT.js → start-TUOXDSFL.js} +3 -3
- package/dist/{status-MVSQG54T.js → status-A36EHRO4.js} +3 -3
- package/dist/{stop-5PZTZCLL.js → stop-AOJZLQ5X.js} +6 -7
- package/dist/{up-F7TMTLRE.js → up-7ILD7GU7.js} +2 -2
- package/dist/update-LPSIAWQ2.js +140 -0
- package/dist/update-check-Y33QDCFL.js +17 -0
- package/dist/{upgrade-6ZW2RD64.js → upgrade-FX2TKJ2S.js} +16 -15
- package/dist/{variant-T64BKARF.js → variant-LAB67OC2.js} +15 -10
- package/dist/web-assets/assets/index-BbRmoxoA.js +308 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0003_clean_ego.sql +12 -0
- package/drizzle/meta/0003_snapshot.json +417 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/.init/.config/hooks/startup-context.sh +19 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +110 -14
- package/templates/_base/home/.config/routes.json +10 -0
- package/templates/_base/home/VOLUTE.md +14 -35
- package/templates/_base/src/lib/format-prefix.ts +1 -1
- package/templates/_base/src/lib/router.ts +163 -16
- package/templates/_base/src/lib/routing.ts +55 -18
- package/templates/_base/src/lib/types.ts +3 -1
- package/templates/agent-sdk/.init/.config/routes.json +5 -0
- package/templates/agent-sdk/.init/CLAUDE.md +2 -2
- package/templates/agent-sdk/src/agent.ts +2 -1
- package/templates/agent-sdk/src/server.ts +8 -2
- package/templates/agent-sdk/volute-template.json +1 -1
- package/templates/pi/.init/.config/routes.json +5 -0
- package/templates/pi/.init/AGENTS.md +1 -1
- package/templates/pi/src/agent.ts +5 -3
- package/templates/pi/src/server.ts +1 -1
- package/templates/pi/volute-template.json +1 -1
- package/dist/channel-DQ6UY7QB.js +0 -67
- package/dist/chunk-ZHCE4DPY.js +0 -110
- package/dist/create-ILVOG75A.js +0 -79
- package/dist/send-3U6OTKG7.js +0 -57
- package/dist/web-assets/assets/index-NS621maO.js +0 -296
- package/templates/agent-sdk/.init/.config/sessions.json +0 -4
- package/templates/pi/.init/.config/sessions.json +0 -1
- package/dist/{service-SA4TTMDU.js → service-HZNIDNJF.js} +3 -3
package/README.md
CHANGED
|
@@ -17,13 +17,13 @@ npm install -g volute
|
|
|
17
17
|
volute up
|
|
18
18
|
|
|
19
19
|
# Create an agent
|
|
20
|
-
volute create atlas
|
|
20
|
+
volute agent create atlas
|
|
21
21
|
|
|
22
22
|
# Start it
|
|
23
|
-
volute start atlas
|
|
23
|
+
volute agent start atlas
|
|
24
24
|
|
|
25
25
|
# Talk to it
|
|
26
|
-
volute send atlas "hey, what can you do?"
|
|
26
|
+
volute message send atlas "hey, what can you do?"
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
You now have a running AI agent with persistent memory, auto-committing file changes, and session resume across restarts. Open `http://localhost:4200` for the web dashboard.
|
|
@@ -45,20 +45,20 @@ The daemon handles agent lifecycle, crash recovery (auto-restarts after 3 second
|
|
|
45
45
|
### Lifecycle
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
|
-
volute create atlas # scaffold a new agent
|
|
49
|
-
volute start atlas # start it
|
|
50
|
-
volute stop atlas # stop it
|
|
51
|
-
volute
|
|
52
|
-
volute status atlas # check one
|
|
53
|
-
volute logs atlas --follow # tail logs
|
|
54
|
-
volute delete atlas # remove from registry
|
|
55
|
-
volute delete atlas --force # also delete files
|
|
48
|
+
volute agent create atlas # scaffold a new agent
|
|
49
|
+
volute agent start atlas # start it
|
|
50
|
+
volute agent stop atlas # stop it
|
|
51
|
+
volute agent list # list all agents
|
|
52
|
+
volute agent status atlas # check one
|
|
53
|
+
volute agent logs atlas --follow # tail logs
|
|
54
|
+
volute agent delete atlas # remove from registry
|
|
55
|
+
volute agent delete atlas --force # also delete files
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
### Sending messages
|
|
59
59
|
|
|
60
60
|
```sh
|
|
61
|
-
volute send atlas "what's on your mind?"
|
|
61
|
+
volute message send atlas "what's on your mind?"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Responses stream back to your terminal in real time. The agent knows which channel each message came from — CLI, web, Discord, or system — and routes its response back to the source.
|
|
@@ -92,16 +92,16 @@ This is the interesting part. Agents can fork themselves into isolated branches,
|
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
94
|
# Create a variant — gets its own git worktree and running server
|
|
95
|
-
volute
|
|
95
|
+
volute variant create experiment --agent atlas
|
|
96
96
|
|
|
97
97
|
# Talk to the variant directly
|
|
98
|
-
volute send atlas@experiment "try a different approach"
|
|
98
|
+
volute message send atlas@experiment "try a different approach"
|
|
99
99
|
|
|
100
100
|
# List all variants
|
|
101
|
-
volute
|
|
101
|
+
volute variant list --agent atlas
|
|
102
102
|
|
|
103
103
|
# Merge it back (verifies, merges, cleans up, restarts the main agent)
|
|
104
|
-
volute merge
|
|
104
|
+
volute variant merge experiment --agent atlas --summary "improved response style"
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
What happens:
|
|
@@ -114,7 +114,7 @@ What happens:
|
|
|
114
114
|
You can fork with a custom personality:
|
|
115
115
|
|
|
116
116
|
```sh
|
|
117
|
-
volute
|
|
117
|
+
volute variant create poet --agent atlas --soul "You are a poet who responds only in verse."
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
Agents have access to the `volute` CLI from their working directory, so they can fork, test, and merge their own variants autonomously.
|
|
@@ -187,13 +187,13 @@ The daemon serves a web UI at `http://localhost:4200` (or whatever port you chos
|
|
|
187
187
|
When the Volute template updates, you can upgrade agents without touching their identity:
|
|
188
188
|
|
|
189
189
|
```sh
|
|
190
|
-
volute upgrade atlas # creates an "upgrade" variant
|
|
190
|
+
volute agent upgrade atlas # creates an "upgrade" variant
|
|
191
191
|
# resolve conflicts if needed, then:
|
|
192
|
-
volute upgrade atlas --continue
|
|
192
|
+
volute agent upgrade atlas --continue
|
|
193
193
|
# test:
|
|
194
|
-
volute send atlas@upgrade "are you working?"
|
|
194
|
+
volute message send atlas@upgrade "are you working?"
|
|
195
195
|
# merge:
|
|
196
|
-
volute merge atlas
|
|
196
|
+
volute variant merge upgrade --agent atlas
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
Your agent's `SOUL.md` and `MEMORY.md` are never overwritten.
|
|
@@ -206,7 +206,7 @@ Two built-in templates:
|
|
|
206
206
|
- **`pi`** — [pi-coding-agent](https://github.com/nicepkg/pi) for multi-provider LLM support
|
|
207
207
|
|
|
208
208
|
```sh
|
|
209
|
-
volute create atlas --template pi
|
|
209
|
+
volute agent create atlas --template pi
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
## Model configuration
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-K3NQKI34.js";
|
|
3
|
+
|
|
4
|
+
// src/commands/agent.ts
|
|
5
|
+
async function run(args) {
|
|
6
|
+
const subcommand = args[0];
|
|
7
|
+
switch (subcommand) {
|
|
8
|
+
case "create":
|
|
9
|
+
await import("./create-RVCZN6HE.js").then((m) => m.run(args.slice(1)));
|
|
10
|
+
break;
|
|
11
|
+
case "start":
|
|
12
|
+
await import("./start-TUOXDSFL.js").then((m) => m.run(args.slice(1)));
|
|
13
|
+
break;
|
|
14
|
+
case "stop":
|
|
15
|
+
await import("./stop-AOJZLQ5X.js").then((m) => m.run(args.slice(1)));
|
|
16
|
+
break;
|
|
17
|
+
case "delete":
|
|
18
|
+
await import("./delete-3QH7VYIN.js").then((m) => m.run(args.slice(1)));
|
|
19
|
+
break;
|
|
20
|
+
case "list":
|
|
21
|
+
await import("./status-A36EHRO4.js").then((m) => m.run(args.slice(1)));
|
|
22
|
+
break;
|
|
23
|
+
case "status": {
|
|
24
|
+
const rest = args.slice(1);
|
|
25
|
+
if (!rest[0] && process.env.VOLUTE_AGENT) {
|
|
26
|
+
rest.unshift(process.env.VOLUTE_AGENT);
|
|
27
|
+
}
|
|
28
|
+
await import("./status-A36EHRO4.js").then((m) => m.run(rest));
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "logs": {
|
|
32
|
+
const rest = args.slice(1);
|
|
33
|
+
const logsArgs = transformAgentFlag(rest);
|
|
34
|
+
await import("./logs-DF342W4M.js").then((m) => m.run(logsArgs));
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case "upgrade":
|
|
38
|
+
await import("./upgrade-FX2TKJ2S.js").then((m) => m.run(args.slice(1)));
|
|
39
|
+
break;
|
|
40
|
+
case "import":
|
|
41
|
+
await import("./import-MXJB2EII.js").then((m) => m.run(args.slice(1)));
|
|
42
|
+
break;
|
|
43
|
+
case "--help":
|
|
44
|
+
case "-h":
|
|
45
|
+
case void 0:
|
|
46
|
+
printUsage();
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
printUsage();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function transformAgentFlag(args) {
|
|
54
|
+
if (args.length > 0 && args[0] && !args[0].startsWith("-")) {
|
|
55
|
+
return ["--agent", args[0], ...args.slice(1)];
|
|
56
|
+
}
|
|
57
|
+
return args;
|
|
58
|
+
}
|
|
59
|
+
function printUsage() {
|
|
60
|
+
console.log(`Usage:
|
|
61
|
+
volute agent create <name> [--template <name>]
|
|
62
|
+
volute agent start <name>
|
|
63
|
+
volute agent stop [name]
|
|
64
|
+
volute agent delete [name] [--force]
|
|
65
|
+
volute agent list
|
|
66
|
+
volute agent status [name]
|
|
67
|
+
volute agent logs [name] [--follow] [-n N]
|
|
68
|
+
volute agent upgrade [name] [--template <name>] [--continue]
|
|
69
|
+
volute agent import <path> [--name <name>] [--session <path>] [--template <name>]
|
|
70
|
+
|
|
71
|
+
Agent name can be omitted (where shown as [name]) if VOLUTE_AGENT is set.`);
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
run
|
|
75
|
+
};
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
AgentManager,
|
|
4
4
|
getAgentManager,
|
|
5
5
|
initAgentManager
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-MW2KFO3B.js";
|
|
7
|
+
import "./chunk-HE67X4T6.js";
|
|
8
|
+
import "./chunk-UAVD2AHX.js";
|
|
9
|
+
import "./chunk-UX25Z2ND.js";
|
|
10
10
|
import "./chunk-K3NQKI34.js";
|
|
11
11
|
export {
|
|
12
12
|
AgentManager,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readStdin
|
|
4
|
+
} from "./chunk-ZYGKG6VC.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveAgentName
|
|
7
|
+
} from "./chunk-AZEL2IEK.js";
|
|
8
|
+
import {
|
|
9
|
+
getChannelDriver
|
|
10
|
+
} from "./chunk-SMISE4SV.js";
|
|
11
|
+
import {
|
|
12
|
+
loadMergedEnv
|
|
13
|
+
} from "./chunk-HE67X4T6.js";
|
|
14
|
+
import {
|
|
15
|
+
parseArgs
|
|
16
|
+
} from "./chunk-D424ZQGI.js";
|
|
17
|
+
import {
|
|
18
|
+
resolveAgent
|
|
19
|
+
} from "./chunk-UX25Z2ND.js";
|
|
20
|
+
import "./chunk-K3NQKI34.js";
|
|
21
|
+
|
|
22
|
+
// src/commands/channel.ts
|
|
23
|
+
async function run(args) {
|
|
24
|
+
const subcommand = args[0];
|
|
25
|
+
switch (subcommand) {
|
|
26
|
+
case "read":
|
|
27
|
+
await readChannel(args.slice(1));
|
|
28
|
+
break;
|
|
29
|
+
case "send":
|
|
30
|
+
await sendChannel(args.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
case "--help":
|
|
33
|
+
case "-h":
|
|
34
|
+
case void 0:
|
|
35
|
+
printUsage();
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
printUsage();
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function printUsage() {
|
|
43
|
+
console.log(`Usage:
|
|
44
|
+
volute channel read <channel-uri> [--limit N] [--agent <name>]
|
|
45
|
+
volute channel send <channel-uri> "<message>" [--agent <name>]
|
|
46
|
+
echo "message" | volute channel send <channel-uri> [--agent <name>]`);
|
|
47
|
+
}
|
|
48
|
+
async function readChannel(args) {
|
|
49
|
+
const { positional, flags } = parseArgs(args, {
|
|
50
|
+
agent: { type: "string" },
|
|
51
|
+
limit: { type: "number" }
|
|
52
|
+
});
|
|
53
|
+
const uri = positional[0];
|
|
54
|
+
if (!uri) {
|
|
55
|
+
console.error("Usage: volute channel read <channel-uri> [--limit N] [--agent <name>]");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const agentName = resolveAgentName(flags);
|
|
59
|
+
const { platform, channelId } = parseUri(uri);
|
|
60
|
+
const driver = requireDriver(platform);
|
|
61
|
+
const { dir } = resolveAgent(agentName);
|
|
62
|
+
const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName };
|
|
63
|
+
try {
|
|
64
|
+
const limit = flags.limit ?? 20;
|
|
65
|
+
const output = await driver.read(env, channelId, limit);
|
|
66
|
+
console.log(output);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function sendChannel(args) {
|
|
73
|
+
const { positional, flags } = parseArgs(args, {
|
|
74
|
+
agent: { type: "string" }
|
|
75
|
+
});
|
|
76
|
+
const uri = positional[0];
|
|
77
|
+
const message = positional[1] ?? await readStdin();
|
|
78
|
+
if (!uri || !message) {
|
|
79
|
+
console.error('Usage: volute channel send <channel-uri> "<message>" [--agent <name>]');
|
|
80
|
+
console.error(' echo "message" | volute channel send <channel-uri> [--agent <name>]');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const agentName = resolveAgentName(flags);
|
|
84
|
+
const { platform, channelId } = parseUri(uri);
|
|
85
|
+
const driver = requireDriver(platform);
|
|
86
|
+
const { dir } = resolveAgent(agentName);
|
|
87
|
+
const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName };
|
|
88
|
+
try {
|
|
89
|
+
await driver.send(env, channelId, message);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function parseUri(uri) {
|
|
96
|
+
const colonIdx = uri.indexOf(":");
|
|
97
|
+
if (colonIdx === -1) {
|
|
98
|
+
console.error(`Invalid channel URI: ${uri} (expected format: platform:id)`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
return { platform: uri.slice(0, colonIdx), channelId: uri.slice(colonIdx + 1) };
|
|
102
|
+
}
|
|
103
|
+
function requireDriver(platform) {
|
|
104
|
+
const driver = getChannelDriver(platform);
|
|
105
|
+
if (!driver) {
|
|
106
|
+
console.error(`No channel driver for platform: ${platform}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
return driver;
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
run
|
|
113
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
voluteHome
|
|
4
|
+
} from "./chunk-UX25Z2ND.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/update-check.ts
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
var CACHE_TTL = 60 * 60 * 1e3;
|
|
10
|
+
function cachePath() {
|
|
11
|
+
return resolve(voluteHome(), "update-check.json");
|
|
12
|
+
}
|
|
13
|
+
function readCache() {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(readFileSync(cachePath(), "utf-8"));
|
|
16
|
+
if (data.latest && typeof data.checkedAt === "number") return data;
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function writeCache(latest) {
|
|
22
|
+
try {
|
|
23
|
+
writeFileSync(cachePath(), `${JSON.stringify({ latest, checkedAt: Date.now() })}
|
|
24
|
+
`);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getCurrentVersion() {
|
|
29
|
+
const thisDir = new URL(".", import.meta.url).pathname;
|
|
30
|
+
const candidates = [
|
|
31
|
+
resolve(thisDir, "../../package.json"),
|
|
32
|
+
resolve(thisDir, "../../../package.json")
|
|
33
|
+
];
|
|
34
|
+
for (const p of candidates) {
|
|
35
|
+
if (existsSync(p)) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(p, "utf-8")).version;
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return "0.0.0";
|
|
43
|
+
}
|
|
44
|
+
async function fetchLatestVersion() {
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
47
|
+
timeout.unref?.();
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch("https://registry.npmjs.org/volute/latest", {
|
|
50
|
+
signal: controller.signal
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok) throw new Error(`npm registry returned ${res.status}`);
|
|
53
|
+
const data = await res.json();
|
|
54
|
+
if (typeof data.version !== "string") throw new Error("invalid npm response");
|
|
55
|
+
return data.version;
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function isNewer(current, latest) {
|
|
61
|
+
const parse = (v) => v.split("-")[0].split(".").map(Number);
|
|
62
|
+
const hasPrerelease = (v) => v.includes("-");
|
|
63
|
+
const c = parse(current);
|
|
64
|
+
const l = parse(latest);
|
|
65
|
+
for (let i = 0; i < 3; i++) {
|
|
66
|
+
if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
|
|
67
|
+
if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
|
|
68
|
+
}
|
|
69
|
+
if (hasPrerelease(current) && !hasPrerelease(latest)) return true;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
async function checkForUpdate() {
|
|
73
|
+
const current = getCurrentVersion();
|
|
74
|
+
const cache = readCache();
|
|
75
|
+
if (cache && Date.now() - cache.checkedAt < CACHE_TTL) {
|
|
76
|
+
return {
|
|
77
|
+
current,
|
|
78
|
+
latest: cache.latest,
|
|
79
|
+
updateAvailable: isNewer(current, cache.latest)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const latest = await fetchLatestVersion();
|
|
84
|
+
writeCache(latest);
|
|
85
|
+
return { current, latest, updateAvailable: isNewer(current, latest) };
|
|
86
|
+
} catch {
|
|
87
|
+
return { current, latest: current, updateAvailable: false, checkFailed: true };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function checkForUpdateCached() {
|
|
91
|
+
const cache = readCache();
|
|
92
|
+
if (!cache) return null;
|
|
93
|
+
const current = getCurrentVersion();
|
|
94
|
+
return {
|
|
95
|
+
current,
|
|
96
|
+
latest: cache.latest,
|
|
97
|
+
updateAvailable: isNewer(current, cache.latest)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
getCurrentVersion,
|
|
103
|
+
fetchLatestVersion,
|
|
104
|
+
isNewer,
|
|
105
|
+
checkForUpdate,
|
|
106
|
+
checkForUpdateCached
|
|
107
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
function resolveAgentName(flags) {
|
|
5
5
|
const name = flags.agent || process.env.VOLUTE_AGENT;
|
|
6
6
|
if (!name) {
|
|
7
|
-
console.error("No agent specified.
|
|
7
|
+
console.error("No agent specified. Provide a name or set VOLUTE_AGENT.");
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
10
|
return name;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/format-tool.ts
|
|
4
|
+
function summarizeTool(name, input) {
|
|
5
|
+
if (input && typeof input === "object") {
|
|
6
|
+
const args = input;
|
|
7
|
+
const val = args.path ?? args.command ?? args.query ?? args.url;
|
|
8
|
+
if (typeof val === "string") {
|
|
9
|
+
const brief = val.length > 60 ? `${val.slice(0, 57)}...` : val;
|
|
10
|
+
return `[${name} ${brief}]`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return `[${name}]`;
|
|
14
|
+
}
|
|
15
|
+
function collectPart(event) {
|
|
16
|
+
if (event.type === "text") return event.content ?? "";
|
|
17
|
+
if (event.type === "tool_use") return summarizeTool(event.name ?? "", event.input);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
summarizeTool,
|
|
23
|
+
collectPart
|
|
24
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadMergedEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HE67X4T6.js";
|
|
5
5
|
import {
|
|
6
6
|
applyIsolation
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UAVD2AHX.js";
|
|
8
8
|
import {
|
|
9
9
|
agentDir,
|
|
10
10
|
findAgent,
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
setVariantRunning,
|
|
14
14
|
validateBranchName,
|
|
15
15
|
voluteHome
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UX25Z2ND.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/agent-manager.ts
|
|
19
19
|
import { execFile, spawn } from "child_process";
|
|
20
|
-
import {
|
|
20
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
21
21
|
import { resolve } from "path";
|
|
22
22
|
import { promisify } from "util";
|
|
23
23
|
|
|
@@ -58,6 +58,45 @@ function clearJsonMap(path, map) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/lib/rotating-log.ts
|
|
62
|
+
import { createWriteStream, existsSync as existsSync2, renameSync, statSync } from "fs";
|
|
63
|
+
import { Writable } from "stream";
|
|
64
|
+
var MAX_SIZE = 10 * 1024 * 1024;
|
|
65
|
+
var RotatingLog = class extends Writable {
|
|
66
|
+
constructor(path, maxSize = MAX_SIZE) {
|
|
67
|
+
super();
|
|
68
|
+
this.path = path;
|
|
69
|
+
this.maxSize = maxSize;
|
|
70
|
+
this.on("error", () => {
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
this.size = existsSync2(path) ? statSync(path).size : 0;
|
|
74
|
+
} catch {
|
|
75
|
+
this.size = 0;
|
|
76
|
+
}
|
|
77
|
+
this.stream = createWriteStream(path, { flags: "a" });
|
|
78
|
+
}
|
|
79
|
+
stream;
|
|
80
|
+
size;
|
|
81
|
+
_write(chunk, _encoding, callback) {
|
|
82
|
+
this.size += chunk.length;
|
|
83
|
+
if (this.size > this.maxSize) {
|
|
84
|
+
try {
|
|
85
|
+
renameSync(this.path, `${this.path}.1`);
|
|
86
|
+
const oldStream = this.stream;
|
|
87
|
+
this.stream = createWriteStream(this.path);
|
|
88
|
+
this.size = chunk.length;
|
|
89
|
+
oldStream.end();
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
this.stream.write(chunk, callback);
|
|
94
|
+
}
|
|
95
|
+
_final(callback) {
|
|
96
|
+
this.stream.end(callback);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
61
100
|
// src/lib/agent-manager.ts
|
|
62
101
|
var execFileAsync = promisify(execFile);
|
|
63
102
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
@@ -78,7 +117,7 @@ var AgentManager = class {
|
|
|
78
117
|
return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
|
|
79
118
|
}
|
|
80
119
|
const dir = agentDir(baseName);
|
|
81
|
-
if (!
|
|
120
|
+
if (!existsSync3(dir)) throw new Error(`Agent directory missing: ${dir}`);
|
|
82
121
|
return { dir, port: entry.port, isVariant: false, baseName };
|
|
83
122
|
}
|
|
84
123
|
async startAgent(name) {
|
|
@@ -100,9 +139,7 @@ var AgentManager = class {
|
|
|
100
139
|
const voluteDir = resolve(dir, ".volute");
|
|
101
140
|
const logsDir = resolve(voluteDir, "logs");
|
|
102
141
|
mkdirSync(logsDir, { recursive: true });
|
|
103
|
-
const logStream =
|
|
104
|
-
flags: "a"
|
|
105
|
-
});
|
|
142
|
+
const logStream = new RotatingLog(resolve(logsDir, "agent.log"));
|
|
106
143
|
const agentEnv = loadMergedEnv(dir);
|
|
107
144
|
const { VOLUTE_DAEMON_TOKEN: _, ...parentEnv } = process.env;
|
|
108
145
|
const env = { ...parentEnv, ...agentEnv, VOLUTE_AGENT: name };
|
|
@@ -198,7 +235,7 @@ var AgentManager = class {
|
|
|
198
235
|
}
|
|
199
236
|
async handleRestart(name, dir) {
|
|
200
237
|
const restartPath = resolve(dir, ".volute", "restart.json");
|
|
201
|
-
if (!
|
|
238
|
+
if (!existsSync3(restartPath)) return false;
|
|
202
239
|
try {
|
|
203
240
|
const signal = JSON.parse(readFileSync2(restartPath, "utf-8"));
|
|
204
241
|
unlinkSync2(restartPath);
|
|
@@ -326,6 +363,7 @@ export {
|
|
|
326
363
|
loadJsonMap,
|
|
327
364
|
saveJsonMap,
|
|
328
365
|
clearJsonMap,
|
|
366
|
+
RotatingLog,
|
|
329
367
|
AgentManager,
|
|
330
368
|
initAgentManager,
|
|
331
369
|
getAgentManager
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
voluteHome
|
|
4
|
+
} from "./chunk-UX25Z2ND.js";
|
|
2
5
|
import {
|
|
3
6
|
__export
|
|
4
7
|
} from "./chunk-K3NQKI34.js";
|
|
@@ -117,9 +120,82 @@ async function send3(env, chatId, message) {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
// src/lib/channels/volute.ts
|
|
124
|
+
var volute_exports = {};
|
|
125
|
+
__export(volute_exports, {
|
|
126
|
+
read: () => read4,
|
|
127
|
+
send: () => send4
|
|
128
|
+
});
|
|
129
|
+
import { existsSync, readFileSync } from "fs";
|
|
130
|
+
import { resolve } from "path";
|
|
131
|
+
function getDaemonConfig() {
|
|
132
|
+
const configPath = resolve(voluteHome(), "daemon.json");
|
|
133
|
+
if (!existsSync(configPath)) {
|
|
134
|
+
throw new Error("Volute daemon is not running");
|
|
135
|
+
}
|
|
136
|
+
let config;
|
|
137
|
+
try {
|
|
138
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
throw new Error(`Failed to parse ${configPath}: ${err}`);
|
|
141
|
+
}
|
|
142
|
+
if (typeof config.port !== "number") {
|
|
143
|
+
throw new Error(`Invalid or missing port in ${configPath}`);
|
|
144
|
+
}
|
|
145
|
+
const url = new URL("http://localhost");
|
|
146
|
+
url.hostname = config.hostname || "localhost";
|
|
147
|
+
url.port = String(config.port);
|
|
148
|
+
return { url: url.origin, token: config.token };
|
|
149
|
+
}
|
|
150
|
+
async function read4(env, conversationId, limit) {
|
|
151
|
+
const agentName = env.VOLUTE_AGENT;
|
|
152
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
153
|
+
const { url, token } = getDaemonConfig();
|
|
154
|
+
const headers = { Origin: url };
|
|
155
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
156
|
+
const res = await fetch(
|
|
157
|
+
`${url}/api/agents/${encodeURIComponent(agentName)}/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
158
|
+
{ headers }
|
|
159
|
+
);
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
|
|
162
|
+
}
|
|
163
|
+
const messages = await res.json();
|
|
164
|
+
return messages.slice(-limit).map((m) => {
|
|
165
|
+
const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
|
|
166
|
+
return `${m.sender_name ?? m.role}: ${text}`;
|
|
167
|
+
}).join("\n");
|
|
168
|
+
}
|
|
169
|
+
async function send4(env, conversationId, message) {
|
|
170
|
+
const agentName = env.VOLUTE_AGENT;
|
|
171
|
+
if (!agentName) throw new Error("VOLUTE_AGENT not set");
|
|
172
|
+
const { url, token } = getDaemonConfig();
|
|
173
|
+
const headers = {
|
|
174
|
+
"Content-Type": "application/json",
|
|
175
|
+
Origin: url
|
|
176
|
+
};
|
|
177
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
178
|
+
const res = await fetch(`${url}/api/agents/${encodeURIComponent(agentName)}/chat`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers,
|
|
181
|
+
body: JSON.stringify({ message, conversationId, sender: agentName })
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok) {
|
|
184
|
+
const data = await res.json().catch(() => ({}));
|
|
185
|
+
throw new Error(data.error ?? `Failed to send: ${res.status}`);
|
|
186
|
+
}
|
|
187
|
+
if (res.body) {
|
|
188
|
+
const reader = res.body.getReader();
|
|
189
|
+
while (true) {
|
|
190
|
+
const { done } = await reader.read();
|
|
191
|
+
if (done) break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
120
196
|
// src/lib/channels.ts
|
|
121
197
|
var CHANNELS = {
|
|
122
|
-
|
|
198
|
+
volute: { name: "volute", displayName: "Volute", showToolCalls: true, driver: volute_exports },
|
|
123
199
|
discord: {
|
|
124
200
|
name: "discord",
|
|
125
201
|
displayName: "Discord",
|
|
@@ -138,8 +214,6 @@ var CHANNELS = {
|
|
|
138
214
|
showToolCalls: false,
|
|
139
215
|
driver: telegram_exports
|
|
140
216
|
},
|
|
141
|
-
cli: { name: "cli", displayName: "CLI", showToolCalls: true },
|
|
142
|
-
agent: { name: "agent", displayName: "Agent", showToolCalls: true },
|
|
143
217
|
system: { name: "system", displayName: "System", showToolCalls: false }
|
|
144
218
|
};
|
|
145
219
|
function getChannelDriver(platform) {
|