volute 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/{agent-manager-2LU6KULR.js → agent-manager-AUCKMGPR.js} +4 -4
- package/dist/{channel-H7N4SGR2.js → channel-DQ6UY7QB.js} +17 -40
- package/dist/{chunk-RALYNMHR.js → chunk-3C2XR4IY.js} +1 -1
- package/dist/chunk-5OCWMTVS.js +152 -0
- package/dist/{chunk-YEIHRP2J.js → chunk-DNOXHLE5.js} +1 -1
- package/dist/{chunk-IPIPLGME.js → chunk-I6OHXCMV.js} +4 -4
- package/dist/chunk-MXUCNIBG.js +168 -0
- package/dist/{chunk-DEUAVGSA.js → chunk-SOZA2TLP.js} +1 -1
- package/dist/{chunk-VVD3XO3E.js → chunk-YGFIWIOF.js} +1 -1
- package/dist/{chunk-N4YNKR3Q.js → chunk-ZHCE4DPY.js} +20 -0
- package/dist/cli.js +36 -24
- package/dist/connector-DKDJTLYZ.js +152 -0
- package/dist/connectors/discord.js +102 -158
- package/dist/connectors/slack.js +170 -0
- package/dist/connectors/telegram.js +156 -0
- package/dist/{create-RSWWMGKT.js → create-ILVOG75A.js} +5 -5
- package/dist/{daemon-client-27KMQQKX.js → daemon-client-XR24PUJF.js} +2 -2
- package/dist/daemon.js +271 -151
- package/dist/{delete-4ERL2QHH.js → delete-55MXCEY5.js} +5 -5
- package/dist/{down-HRC4MQCT.js → down-3OB6UVAJ.js} +1 -1
- package/dist/{env-DBWDTIP6.js → env-JB27UAC3.js} +2 -2
- package/dist/{history-W7BD2H74.js → history-BKG74I43.js} +4 -4
- package/dist/{import-6HTSSDFW.js → import-4CI2ZUTJ.js} +17 -2
- package/dist/{logs-NHWGHNBF.js → logs-NXFFGUKY.js} +1 -1
- package/dist/package-Z2SFO2SV.js +89 -0
- package/dist/{schedule-DKZ2E2CL.js → schedule-A35SH4HT.js} +4 -4
- package/dist/{send-5LEJXPYV.js → send-3U6OTKG7.js} +8 -4
- package/dist/{setup-ZMNTOJAV.js → setup-2FDVN7OF.js} +4 -4
- package/dist/{start-2BSXX6BS.js → start-LDPMCMYT.js} +2 -2
- package/dist/{status-N23CV27T.js → status-MVSQG54T.js} +2 -2
- package/dist/{stop-DSKBIJ2D.js → stop-5PZTZCLL.js} +2 -2
- package/dist/{up-4UGID4DM.js → up-F7TMTLRE.js} +1 -1
- package/dist/{upgrade-BGFVRCVP.js → upgrade-6ZW2RD64.js} +32 -19
- package/dist/{variant-JPLJTS2P.js → variant-T64BKARF.js} +130 -18
- package/dist/web-assets/assets/{index-BC5eSqbY.js → index-NS621maO.js} +23 -23
- package/dist/web-assets/index.html +1 -1
- package/package.json +3 -1
- package/templates/_base/_skills/volute-agent/SKILL.md +5 -4
- package/templates/_base/home/VOLUTE.md +18 -6
- package/templates/_base/src/lib/file-handler.ts +46 -0
- package/templates/_base/src/lib/router.ts +180 -0
- package/templates/_base/src/lib/routing.ts +100 -0
- package/templates/_base/src/lib/types.ts +13 -2
- package/templates/_base/src/lib/volute-server.ts +20 -48
- package/templates/agent-sdk/src/agent.ts +268 -82
- package/templates/agent-sdk/src/server.ts +12 -3
- package/templates/pi/src/agent.ts +277 -58
- package/templates/pi/src/server.ts +15 -4
- package/dist/chunk-MY74SUOL.js +0 -81
- package/dist/connector-6LWB5PRU.js +0 -96
- package/templates/_base/src/lib/sessions.ts +0 -71
- package/templates/agent-sdk/src/lib/agent-sessions.ts +0 -204
- package/templates/pi/src/lib/agent-sessions.ts +0 -210
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ Agents have access to the `volute` CLI from their working directory, so they can
|
|
|
121
121
|
|
|
122
122
|
## Connectors
|
|
123
123
|
|
|
124
|
-
Connect agents to external services.
|
|
124
|
+
Connect agents to external services. Connectors are generic — any connector type that has an implementation (built-in, shared, or agent-specific) can be enabled.
|
|
125
125
|
|
|
126
126
|
### Discord
|
|
127
127
|
|
|
@@ -130,21 +130,21 @@ Connect agents to external services.
|
|
|
130
130
|
volute env set DISCORD_TOKEN <your-bot-token>
|
|
131
131
|
|
|
132
132
|
# Connect
|
|
133
|
-
volute connect discord atlas
|
|
133
|
+
volute connector connect discord --agent atlas
|
|
134
134
|
|
|
135
135
|
# Disconnect
|
|
136
|
-
volute disconnect discord atlas
|
|
136
|
+
volute connector disconnect discord --agent atlas
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
The agent receives Discord messages and responds in-channel. Tool calls are filtered out —
|
|
139
|
+
The agent receives Discord messages and responds in-channel. Tool calls are filtered out — connector users see clean text responses.
|
|
140
140
|
|
|
141
141
|
### Channel commands
|
|
142
142
|
|
|
143
|
-
Read from and write to channels directly:
|
|
143
|
+
Read from and write to connector channels directly:
|
|
144
144
|
|
|
145
145
|
```sh
|
|
146
|
-
volute channel read discord:123456789 # recent messages
|
|
147
|
-
volute channel send discord:123456789 "hello" # send a message
|
|
146
|
+
volute channel read discord:123456789 --agent atlas # recent messages
|
|
147
|
+
volute channel send discord:123456789 "hello" --agent atlas # send a message
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
## Schedules
|
|
@@ -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-I6OHXCMV.js";
|
|
7
|
+
import "./chunk-DNOXHLE5.js";
|
|
8
|
+
import "./chunk-SOZA2TLP.js";
|
|
9
|
+
import "./chunk-3C2XR4IY.js";
|
|
10
10
|
import "./chunk-K3NQKI34.js";
|
|
11
11
|
export {
|
|
12
12
|
AgentManager,
|
|
@@ -1,44 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
loadMergedEnv
|
|
4
|
-
} from "./chunk-YEIHRP2J.js";
|
|
5
2
|
import {
|
|
6
3
|
resolveAgentName
|
|
7
4
|
} from "./chunk-VRVVQIYY.js";
|
|
5
|
+
import {
|
|
6
|
+
getChannelDriver
|
|
7
|
+
} from "./chunk-5OCWMTVS.js";
|
|
8
|
+
import {
|
|
9
|
+
loadMergedEnv
|
|
10
|
+
} from "./chunk-DNOXHLE5.js";
|
|
8
11
|
import {
|
|
9
12
|
parseArgs
|
|
10
13
|
} from "./chunk-D424ZQGI.js";
|
|
11
14
|
import {
|
|
12
15
|
resolveAgent
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-3C2XR4IY.js";
|
|
14
17
|
import "./chunk-K3NQKI34.js";
|
|
15
18
|
|
|
16
|
-
// src/lib/channels/discord.ts
|
|
17
|
-
var API_BASE = "https://discord.com/api/v10";
|
|
18
|
-
async function read(token, channelId, limit) {
|
|
19
|
-
const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
|
|
20
|
-
headers: { Authorization: `Bot ${token}` }
|
|
21
|
-
});
|
|
22
|
-
if (!res.ok) {
|
|
23
|
-
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
24
|
-
}
|
|
25
|
-
const messages = await res.json();
|
|
26
|
-
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
27
|
-
}
|
|
28
|
-
async function send(token, channelId, message) {
|
|
29
|
-
const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
|
|
30
|
-
method: "POST",
|
|
31
|
-
headers: {
|
|
32
|
-
Authorization: `Bot ${token}`,
|
|
33
|
-
"Content-Type": "application/json"
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify({ content: message })
|
|
36
|
-
});
|
|
37
|
-
if (!res.ok) {
|
|
38
|
-
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
19
|
// src/commands/channel.ts
|
|
43
20
|
async function run(args) {
|
|
44
21
|
const { positional, flags } = parseArgs(args, {
|
|
@@ -62,26 +39,26 @@ async function run(args) {
|
|
|
62
39
|
}
|
|
63
40
|
const platform = uri.slice(0, colonIdx);
|
|
64
41
|
const channelId = uri.slice(colonIdx + 1);
|
|
42
|
+
const driver = getChannelDriver(platform);
|
|
43
|
+
if (!driver) {
|
|
44
|
+
console.error(`No channel driver for platform: ${platform}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
65
47
|
const { dir } = resolveAgent(agentName);
|
|
66
48
|
const env = loadMergedEnv(dir);
|
|
67
|
-
|
|
68
|
-
const token = env.DISCORD_TOKEN;
|
|
69
|
-
if (!token) {
|
|
70
|
-
console.error("DISCORD_TOKEN not set. Run: volute env set DISCORD_TOKEN <token>");
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
49
|
+
try {
|
|
73
50
|
if (subcommand === "read") {
|
|
74
51
|
const limit = flags.limit ?? 20;
|
|
75
|
-
const output = await read(
|
|
52
|
+
const output = await driver.read(env, channelId, limit);
|
|
76
53
|
console.log(output);
|
|
77
54
|
} else if (subcommand === "send") {
|
|
78
|
-
await send(
|
|
55
|
+
await driver.send(env, channelId, message);
|
|
79
56
|
} else {
|
|
80
57
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
81
58
|
process.exit(1);
|
|
82
59
|
}
|
|
83
|
-
}
|
|
84
|
-
console.error(
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
85
62
|
process.exit(1);
|
|
86
63
|
}
|
|
87
64
|
}
|
|
@@ -81,7 +81,7 @@ function removeAllVariants(agentName) {
|
|
|
81
81
|
}
|
|
82
82
|
async function checkHealth(port) {
|
|
83
83
|
try {
|
|
84
|
-
const res = await fetch(`http://
|
|
84
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
85
85
|
signal: AbortSignal.timeout(2e3)
|
|
86
86
|
});
|
|
87
87
|
if (!res.ok) return { ok: false };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__export
|
|
4
|
+
} from "./chunk-K3NQKI34.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/channels/discord.ts
|
|
7
|
+
var discord_exports = {};
|
|
8
|
+
__export(discord_exports, {
|
|
9
|
+
read: () => read,
|
|
10
|
+
send: () => send
|
|
11
|
+
});
|
|
12
|
+
var API_BASE = "https://discord.com/api/v10";
|
|
13
|
+
function requireToken(env) {
|
|
14
|
+
const token = env.DISCORD_TOKEN;
|
|
15
|
+
if (!token) throw new Error("DISCORD_TOKEN not set");
|
|
16
|
+
return token;
|
|
17
|
+
}
|
|
18
|
+
async function read(env, channelId, limit) {
|
|
19
|
+
const token = requireToken(env);
|
|
20
|
+
const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
|
|
21
|
+
headers: { Authorization: `Bot ${token}` }
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
25
|
+
}
|
|
26
|
+
const messages = await res.json();
|
|
27
|
+
return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
|
|
28
|
+
}
|
|
29
|
+
async function send(env, channelId, message) {
|
|
30
|
+
const token = requireToken(env);
|
|
31
|
+
const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bot ${token}`,
|
|
35
|
+
"Content-Type": "application/json"
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ content: message })
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/lib/channels/slack.ts
|
|
45
|
+
var slack_exports = {};
|
|
46
|
+
__export(slack_exports, {
|
|
47
|
+
read: () => read2,
|
|
48
|
+
send: () => send2
|
|
49
|
+
});
|
|
50
|
+
var API_BASE2 = "https://slack.com/api";
|
|
51
|
+
function requireToken2(env) {
|
|
52
|
+
const token = env.SLACK_BOT_TOKEN;
|
|
53
|
+
if (!token) throw new Error("SLACK_BOT_TOKEN not set");
|
|
54
|
+
return token;
|
|
55
|
+
}
|
|
56
|
+
async function slackApi(token, method, body) {
|
|
57
|
+
const res = await fetch(`${API_BASE2}/${method}`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
Authorization: `Bearer ${token}`,
|
|
61
|
+
"Content-Type": "application/json"
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(body)
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new Error(`Slack API HTTP error: ${res.status} ${res.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
if (!data.ok) {
|
|
70
|
+
throw new Error(`Slack API error: ${data.error}`);
|
|
71
|
+
}
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
async function read2(env, channelId, limit) {
|
|
75
|
+
const token = requireToken2(env);
|
|
76
|
+
const data = await slackApi(token, "conversations.history", {
|
|
77
|
+
channel: channelId,
|
|
78
|
+
limit
|
|
79
|
+
});
|
|
80
|
+
return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
|
|
81
|
+
}
|
|
82
|
+
async function send2(env, channelId, message) {
|
|
83
|
+
const token = requireToken2(env);
|
|
84
|
+
await slackApi(token, "chat.postMessage", {
|
|
85
|
+
channel: channelId,
|
|
86
|
+
text: message
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/lib/channels/telegram.ts
|
|
91
|
+
var telegram_exports = {};
|
|
92
|
+
__export(telegram_exports, {
|
|
93
|
+
read: () => read3,
|
|
94
|
+
send: () => send3
|
|
95
|
+
});
|
|
96
|
+
var API_BASE3 = "https://api.telegram.org";
|
|
97
|
+
function requireToken3(env) {
|
|
98
|
+
const token = env.TELEGRAM_BOT_TOKEN;
|
|
99
|
+
if (!token) throw new Error("TELEGRAM_BOT_TOKEN not set");
|
|
100
|
+
return token;
|
|
101
|
+
}
|
|
102
|
+
async function read3(_env, _channelId, _limit) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"Telegram Bot API does not support reading chat history. Use volute channel send instead."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async function send3(env, chatId, message) {
|
|
108
|
+
const token = requireToken3(env);
|
|
109
|
+
const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify({ chat_id: chatId, text: message })
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
const body = await res.text().catch(() => "");
|
|
116
|
+
throw new Error(`Telegram API error: ${res.status} ${body}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/lib/channels.ts
|
|
121
|
+
var CHANNELS = {
|
|
122
|
+
web: { name: "web", displayName: "Web UI", showToolCalls: true },
|
|
123
|
+
discord: {
|
|
124
|
+
name: "discord",
|
|
125
|
+
displayName: "Discord",
|
|
126
|
+
showToolCalls: false,
|
|
127
|
+
driver: discord_exports
|
|
128
|
+
},
|
|
129
|
+
slack: {
|
|
130
|
+
name: "slack",
|
|
131
|
+
displayName: "Slack",
|
|
132
|
+
showToolCalls: false,
|
|
133
|
+
driver: slack_exports
|
|
134
|
+
},
|
|
135
|
+
telegram: {
|
|
136
|
+
name: "telegram",
|
|
137
|
+
displayName: "Telegram",
|
|
138
|
+
showToolCalls: false,
|
|
139
|
+
driver: telegram_exports
|
|
140
|
+
},
|
|
141
|
+
cli: { name: "cli", displayName: "CLI", showToolCalls: true },
|
|
142
|
+
agent: { name: "agent", displayName: "Agent", showToolCalls: true },
|
|
143
|
+
system: { name: "system", displayName: "System", showToolCalls: false }
|
|
144
|
+
};
|
|
145
|
+
function getChannelDriver(platform) {
|
|
146
|
+
return CHANNELS[platform]?.driver ?? null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
CHANNELS,
|
|
151
|
+
getChannelDriver
|
|
152
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadMergedEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DNOXHLE5.js";
|
|
5
5
|
import {
|
|
6
6
|
applyIsolation
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SOZA2TLP.js";
|
|
8
8
|
import {
|
|
9
9
|
agentDir,
|
|
10
10
|
findAgent,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
setVariantRunning,
|
|
14
14
|
validateBranchName,
|
|
15
15
|
voluteHome
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-3C2XR4IY.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/agent-manager.ts
|
|
19
19
|
import { execFile, spawn } from "child_process";
|
|
@@ -89,7 +89,7 @@ var AgentManager = class {
|
|
|
89
89
|
const { dir, isVariant, baseName, variantName } = target;
|
|
90
90
|
const port = target.port;
|
|
91
91
|
try {
|
|
92
|
-
const res = await fetch(`http://
|
|
92
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
93
93
|
if (res.ok) {
|
|
94
94
|
console.error(`[daemon] killing orphan process on port ${port}`);
|
|
95
95
|
await killProcessOnPort(port);
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/connectors/sdk.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
function loadEnv() {
|
|
7
|
+
const agentPort = process.env.VOLUTE_AGENT_PORT;
|
|
8
|
+
const agentName = process.env.VOLUTE_AGENT_NAME;
|
|
9
|
+
if (!agentPort || !agentName) {
|
|
10
|
+
console.error("Missing required env vars: VOLUTE_AGENT_PORT, VOLUTE_AGENT_NAME");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const agentDir = process.env.VOLUTE_AGENT_DIR;
|
|
14
|
+
const daemonUrl = process.env.VOLUTE_DAEMON_URL;
|
|
15
|
+
const daemonToken = process.env.VOLUTE_DAEMON_TOKEN;
|
|
16
|
+
const baseUrl = daemonUrl ? `${daemonUrl}/api/agents/${encodeURIComponent(agentName)}` : `http://127.0.0.1:${agentPort}`;
|
|
17
|
+
return { agentPort, agentName, agentDir, baseUrl, daemonUrl, daemonToken };
|
|
18
|
+
}
|
|
19
|
+
function loadFollowedChannels(env, platform) {
|
|
20
|
+
if (!env.agentDir) return [];
|
|
21
|
+
const configPath = resolve(env.agentDir, "home/.config/volute.json");
|
|
22
|
+
if (!existsSync(configPath)) return [];
|
|
23
|
+
try {
|
|
24
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
25
|
+
const platformConfig = config[platform];
|
|
26
|
+
return platformConfig?.channels ?? platformConfig?.chats ?? [];
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.warn(`Failed to load agent config: ${err}`);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function splitMessage(text, maxLength) {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
while (text.length > maxLength) {
|
|
35
|
+
let splitAt = text.lastIndexOf("\n", maxLength);
|
|
36
|
+
if (splitAt < maxLength / 2) splitAt = maxLength;
|
|
37
|
+
chunks.push(text.slice(0, splitAt));
|
|
38
|
+
text = text.slice(splitAt).replace(/^\n/, "");
|
|
39
|
+
}
|
|
40
|
+
if (text) chunks.push(text);
|
|
41
|
+
return chunks;
|
|
42
|
+
}
|
|
43
|
+
async function* readNdjson(body) {
|
|
44
|
+
const reader = body.getReader();
|
|
45
|
+
const decoder = new TextDecoder();
|
|
46
|
+
let buffer = "";
|
|
47
|
+
try {
|
|
48
|
+
while (true) {
|
|
49
|
+
const { done, value } = await reader.read();
|
|
50
|
+
if (done) break;
|
|
51
|
+
buffer += decoder.decode(value, { stream: true });
|
|
52
|
+
const lines = buffer.split("\n");
|
|
53
|
+
buffer = lines.pop() || "";
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (!line.trim()) continue;
|
|
56
|
+
try {
|
|
57
|
+
yield JSON.parse(line);
|
|
58
|
+
} catch {
|
|
59
|
+
console.warn(`ndjson: skipping invalid line: ${line.slice(0, 100)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (buffer.trim()) {
|
|
64
|
+
try {
|
|
65
|
+
yield JSON.parse(buffer);
|
|
66
|
+
} catch {
|
|
67
|
+
console.warn(`ndjson: skipping invalid line: ${buffer.slice(0, 100)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function getHeaders(env) {
|
|
75
|
+
const headers = { "Content-Type": "application/json" };
|
|
76
|
+
if (env.daemonUrl && env.daemonToken) {
|
|
77
|
+
headers.Authorization = `Bearer ${env.daemonToken}`;
|
|
78
|
+
headers.Origin = env.daemonUrl;
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
82
|
+
function onShutdown(cleanup) {
|
|
83
|
+
const handler = () => {
|
|
84
|
+
Promise.resolve(cleanup()).then(
|
|
85
|
+
() => process.exit(0),
|
|
86
|
+
(err) => {
|
|
87
|
+
console.error(`Shutdown error: ${err}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
process.on("SIGINT", handler);
|
|
93
|
+
process.on("SIGTERM", handler);
|
|
94
|
+
}
|
|
95
|
+
async function fireAndForget(env, payload) {
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(`${env.baseUrl}/message`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: getHeaders(env),
|
|
100
|
+
body: JSON.stringify(payload)
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
console.error(`fireAndForget: agent returned ${res.status}`);
|
|
104
|
+
}
|
|
105
|
+
if (res.body) {
|
|
106
|
+
const reader = res.body.getReader();
|
|
107
|
+
while (!(await reader.read()).done) {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`Failed to forward message: ${err}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function handleAgentMessage(env, payload, handlers) {
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch(`${env.baseUrl}/message`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: getHeaders(env),
|
|
119
|
+
body: JSON.stringify(payload)
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
const body = await res.text().catch(() => "");
|
|
123
|
+
console.error(`Agent returned ${res.status}: ${body}`);
|
|
124
|
+
await handlers.onError(`Error: agent returned ${res.status}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!res.body) {
|
|
128
|
+
await handlers.onError("Error: no response from agent");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let accumulated = "";
|
|
132
|
+
const pendingImages = [];
|
|
133
|
+
for await (const event of readNdjson(res.body)) {
|
|
134
|
+
if (event.type === "text") {
|
|
135
|
+
accumulated += event.content;
|
|
136
|
+
} else if (event.type === "image") {
|
|
137
|
+
pendingImages.push({ data: event.data, media_type: event.media_type });
|
|
138
|
+
} else if (event.type === "tool_use") {
|
|
139
|
+
const text2 = accumulated.trim();
|
|
140
|
+
accumulated = "";
|
|
141
|
+
const images2 = pendingImages.splice(0);
|
|
142
|
+
if (text2 || images2.length > 0) {
|
|
143
|
+
await handlers.onFlush(text2, images2);
|
|
144
|
+
}
|
|
145
|
+
} else if (event.type === "done") {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const text = accumulated.trim();
|
|
150
|
+
const images = pendingImages.splice(0);
|
|
151
|
+
if (text || images.length > 0) {
|
|
152
|
+
await handlers.onFlush(text, images);
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error(`Failed to reach agent at ${env.baseUrl}/message:`, err);
|
|
156
|
+
const errMsg = err instanceof TypeError && err.cause?.code === "ECONNREFUSED" ? "Agent is not running" : `Error: ${err}`;
|
|
157
|
+
await handlers.onError(errMsg);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
loadEnv,
|
|
163
|
+
loadFollowedChannels,
|
|
164
|
+
splitMessage,
|
|
165
|
+
onShutdown,
|
|
166
|
+
fireAndForget,
|
|
167
|
+
handleAgentMessage
|
|
168
|
+
};
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
|
+
|
|
3
21
|
// src/lib/log-buffer.ts
|
|
4
22
|
var LogBuffer = class {
|
|
5
23
|
entries = [];
|
|
@@ -84,6 +102,8 @@ async function* readNdjson(body) {
|
|
|
84
102
|
}
|
|
85
103
|
|
|
86
104
|
export {
|
|
105
|
+
summarizeTool,
|
|
106
|
+
collectPart,
|
|
87
107
|
logBuffer,
|
|
88
108
|
logger_default,
|
|
89
109
|
readNdjson
|
package/dist/cli.js
CHANGED
|
@@ -3,65 +3,72 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
var command = process.argv[2];
|
|
5
5
|
var args = process.argv.slice(3);
|
|
6
|
+
if (command === "--version" || command === "-v") {
|
|
7
|
+
const { default: pkg } = await import("./package-Z2SFO2SV.js");
|
|
8
|
+
console.log(pkg.version);
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
6
11
|
switch (command) {
|
|
7
12
|
case "create":
|
|
8
|
-
await import("./create-
|
|
13
|
+
await import("./create-ILVOG75A.js").then((m) => m.run(args));
|
|
9
14
|
break;
|
|
10
15
|
case "start":
|
|
11
|
-
await import("./start-
|
|
16
|
+
await import("./start-LDPMCMYT.js").then((m) => m.run(args));
|
|
12
17
|
break;
|
|
13
18
|
case "stop":
|
|
14
|
-
await import("./stop-
|
|
19
|
+
await import("./stop-5PZTZCLL.js").then((m) => m.run(args));
|
|
15
20
|
break;
|
|
16
21
|
case "logs":
|
|
17
|
-
await import("./logs-
|
|
22
|
+
await import("./logs-NXFFGUKY.js").then((m) => m.run(args));
|
|
18
23
|
break;
|
|
19
24
|
case "status":
|
|
20
|
-
await import("./status-
|
|
25
|
+
await import("./status-MVSQG54T.js").then((m) => m.run(args));
|
|
21
26
|
break;
|
|
22
27
|
case "variant":
|
|
23
|
-
await import("./variant-
|
|
28
|
+
await import("./variant-T64BKARF.js").then((m) => m.run(args));
|
|
24
29
|
break;
|
|
25
30
|
case "send":
|
|
26
|
-
await import("./send-
|
|
31
|
+
await import("./send-3U6OTKG7.js").then((m) => m.run(args));
|
|
27
32
|
break;
|
|
28
33
|
case "import":
|
|
29
|
-
await import("./import-
|
|
34
|
+
await import("./import-4CI2ZUTJ.js").then((m) => m.run(args));
|
|
30
35
|
break;
|
|
31
36
|
case "delete":
|
|
32
|
-
await import("./delete-
|
|
37
|
+
await import("./delete-55MXCEY5.js").then((m) => m.run(args));
|
|
33
38
|
break;
|
|
34
39
|
case "env":
|
|
35
|
-
await import("./env-
|
|
40
|
+
await import("./env-JB27UAC3.js").then((m) => m.run(args));
|
|
36
41
|
break;
|
|
37
42
|
case "connector":
|
|
38
|
-
await import("./connector-
|
|
43
|
+
await import("./connector-DKDJTLYZ.js").then((m) => m.run(args));
|
|
39
44
|
break;
|
|
40
45
|
case "channel":
|
|
41
|
-
await import("./channel-
|
|
46
|
+
await import("./channel-DQ6UY7QB.js").then((m) => m.run(args));
|
|
42
47
|
break;
|
|
43
48
|
case "upgrade":
|
|
44
|
-
await import("./upgrade-
|
|
49
|
+
await import("./upgrade-6ZW2RD64.js").then((m) => m.run(args));
|
|
45
50
|
break;
|
|
46
51
|
case "up":
|
|
47
|
-
await import("./up-
|
|
52
|
+
await import("./up-F7TMTLRE.js").then((m) => m.run(args));
|
|
48
53
|
break;
|
|
49
54
|
case "down":
|
|
50
|
-
await import("./down-
|
|
55
|
+
await import("./down-3OB6UVAJ.js").then((m) => m.run(args));
|
|
51
56
|
break;
|
|
52
57
|
case "schedule":
|
|
53
|
-
await import("./schedule-
|
|
58
|
+
await import("./schedule-A35SH4HT.js").then((m) => m.run(args));
|
|
54
59
|
break;
|
|
55
60
|
case "history":
|
|
56
|
-
await import("./history-
|
|
61
|
+
await import("./history-BKG74I43.js").then((m) => m.run(args));
|
|
57
62
|
break;
|
|
58
63
|
case "service":
|
|
59
64
|
await import("./service-SA4TTMDU.js").then((m) => m.run(args));
|
|
60
65
|
break;
|
|
61
66
|
case "setup":
|
|
62
|
-
await import("./setup-
|
|
67
|
+
await import("./setup-2FDVN7OF.js").then((m) => m.run(args));
|
|
63
68
|
break;
|
|
64
|
-
|
|
69
|
+
case "--help":
|
|
70
|
+
case "-h":
|
|
71
|
+
case void 0:
|
|
65
72
|
console.log(`volute \u2014 create and manage AI agents
|
|
66
73
|
|
|
67
74
|
Commands:
|
|
@@ -74,6 +81,7 @@ Commands:
|
|
|
74
81
|
volute variant create <name> Create a variant (worktree + server)
|
|
75
82
|
volute variant list List variants for an agent
|
|
76
83
|
volute variant merge <name> Merge a variant back
|
|
84
|
+
volute variant delete <name> Delete a variant
|
|
77
85
|
volute import <path> Import an OpenClaw workspace
|
|
78
86
|
volute env <set|get|list|remove> Manage environment variables
|
|
79
87
|
volute connector connect <type> Enable a connector for an agent
|
|
@@ -94,11 +102,15 @@ Commands:
|
|
|
94
102
|
volute setup [--port N] [--host H] Install system service with user isolation
|
|
95
103
|
volute setup uninstall [--force] Remove system service + isolation
|
|
96
104
|
|
|
105
|
+
Options:
|
|
106
|
+
--version, -v Show version number
|
|
107
|
+
--help, -h Show this help message
|
|
108
|
+
|
|
97
109
|
Agent commands (variant, connector, schedule, logs, history, channel) use
|
|
98
110
|
--agent <name> or VOLUTE_AGENT env var to identify the agent.`);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Unknown command: ${command}
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
console.error(`Unknown command: ${command}
|
|
114
|
+
Run 'volute --help' for usage.`);
|
|
115
|
+
process.exit(1);
|
|
104
116
|
}
|