volute 0.4.0 → 0.6.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.
Files changed (82) hide show
  1. package/README.md +22 -22
  2. package/dist/agent-X7GJLBLW.js +79 -0
  3. package/dist/{agent-manager-AUCKMGPR.js → agent-manager-JDVXU3ON.js} +4 -4
  4. package/dist/channel-SMCNOIVQ.js +262 -0
  5. package/dist/chunk-AOKAQGO4.js +107 -0
  6. package/dist/{chunk-VRVVQIYY.js → chunk-AZEL2IEK.js} +1 -1
  7. package/dist/chunk-B3R6L2GW.js +24 -0
  8. package/dist/{chunk-MXUCNIBG.js → chunk-BX7KI4S3.js} +68 -3
  9. package/dist/{chunk-I6OHXCMV.js → chunk-G6ZNGLUX.js} +47 -9
  10. package/dist/{chunk-DNOXHLE5.js → chunk-H7AMDUIA.js} +1 -1
  11. package/dist/{chunk-YGFIWIOF.js → chunk-JR4UXCTO.js} +1 -1
  12. package/dist/{chunk-3C2XR4IY.js → chunk-UWHWAPGO.js} +120 -107
  13. package/dist/{chunk-SOZA2TLP.js → chunk-W76KWE23.js} +1 -1
  14. package/dist/{chunk-GSPKUPKU.js → chunk-XUA3JUFK.js} +2 -1
  15. package/dist/chunk-ZYGKG6VC.js +22 -0
  16. package/dist/chunk-ZZOOTYXK.js +583 -0
  17. package/dist/cli.js +83 -74
  18. package/dist/{connector-DKDJTLYZ.js → connector-Y7JPNROO.js} +11 -6
  19. package/dist/connectors/discord.js +34 -5
  20. package/dist/connectors/slack.js +36 -8
  21. package/dist/connectors/telegram.js +55 -6
  22. package/dist/create-G525LWEA.js +91 -0
  23. package/dist/{daemon-client-XR24PUJF.js → daemon-client-442IV43D.js} +2 -2
  24. package/dist/daemon.js +1273 -384
  25. package/dist/{delete-55MXCEY5.js → delete-2PH2CGDY.js} +7 -8
  26. package/dist/{down-3OB6UVAJ.js → down-FXWAN66A.js} +1 -1
  27. package/dist/{env-JB27UAC3.js → env-7GLUJCWS.js} +8 -5
  28. package/dist/{history-BKG74I43.js → history-H72ZUIBN.js} +3 -3
  29. package/dist/{import-4CI2ZUTJ.js → import-AVKQJDYC.js} +8 -8
  30. package/dist/{logs-NXFFGUKY.js → logs-EDGK26AK.js} +2 -2
  31. package/dist/message-SCOQDR3P.js +32 -0
  32. package/dist/{package-Z2SFO2SV.js → package-4DP4Y4UO.js} +1 -1
  33. package/dist/restart-O4ETYLJF.js +29 -0
  34. package/dist/{schedule-A35SH4HT.js → schedule-S6QVC5ON.js} +10 -5
  35. package/dist/send-G7PE4DOJ.js +72 -0
  36. package/dist/{setup-2FDVN7OF.js → setup-F4TCWVSP.js} +5 -5
  37. package/dist/{start-LDPMCMYT.js → start-VHQ7LNWM.js} +3 -3
  38. package/dist/{status-MVSQG54T.js → status-QAJWXKMZ.js} +3 -3
  39. package/dist/{stop-5PZTZCLL.js → stop-CAGCT5NI.js} +6 -7
  40. package/dist/{up-F7TMTLRE.js → up-CSX3ZUIU.js} +16 -4
  41. package/dist/update-XSIX3GGP.js +140 -0
  42. package/dist/update-check-5ZADDHCK.js +17 -0
  43. package/dist/{upgrade-6ZW2RD64.js → upgrade-YXKPWDRU.js} +16 -15
  44. package/dist/{variant-T64BKARF.js → variant-4Z6W3PP6.js} +15 -10
  45. package/dist/web-assets/assets/index-D5PzIndO.js +308 -0
  46. package/dist/web-assets/index.html +2 -2
  47. package/drizzle/0003_clean_ego.sql +12 -0
  48. package/drizzle/meta/0003_snapshot.json +417 -0
  49. package/drizzle/meta/_journal.json +7 -0
  50. package/package.json +1 -1
  51. package/templates/_base/.init/.config/hooks/startup-context.sh +19 -1
  52. package/templates/_base/.init/.config/scripts/session-reader.ts +59 -0
  53. package/templates/_base/_skills/sessions/SKILL.md +49 -0
  54. package/templates/_base/_skills/volute-agent/SKILL.md +114 -14
  55. package/templates/_base/home/.config/routes.json +10 -0
  56. package/templates/_base/home/VOLUTE.md +14 -35
  57. package/templates/_base/src/lib/format-prefix.ts +7 -1
  58. package/templates/_base/src/lib/router.ts +193 -19
  59. package/templates/_base/src/lib/routing.ts +55 -18
  60. package/templates/_base/src/lib/session-monitor.ts +400 -0
  61. package/templates/_base/src/lib/types.ts +5 -1
  62. package/templates/agent-sdk/.init/.config/routes.json +5 -0
  63. package/templates/agent-sdk/.init/CLAUDE.md +2 -2
  64. package/templates/agent-sdk/src/agent.ts +18 -1
  65. package/templates/agent-sdk/src/lib/hooks/session-context.ts +32 -0
  66. package/templates/agent-sdk/src/server.ts +8 -2
  67. package/templates/agent-sdk/volute-template.json +1 -1
  68. package/templates/pi/.init/.config/routes.json +5 -0
  69. package/templates/pi/.init/AGENTS.md +1 -1
  70. package/templates/pi/src/agent.ts +12 -4
  71. package/templates/pi/src/lib/session-context-extension.ts +33 -0
  72. package/templates/pi/src/server.ts +1 -1
  73. package/templates/pi/volute-template.json +1 -1
  74. package/dist/channel-DQ6UY7QB.js +0 -67
  75. package/dist/chunk-5OCWMTVS.js +0 -152
  76. package/dist/chunk-ZHCE4DPY.js +0 -110
  77. package/dist/create-ILVOG75A.js +0 -79
  78. package/dist/send-3U6OTKG7.js +0 -57
  79. package/dist/web-assets/assets/index-NS621maO.js +0 -296
  80. package/templates/agent-sdk/.init/.config/sessions.json +0 -4
  81. package/templates/pi/.init/.config/sessions.json +0 -1
  82. 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 status # list all agents
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 fork atlas experiment
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 variants atlas
101
+ volute variant list --agent atlas
102
102
 
103
103
  # Merge it back (verifies, merges, cleans up, restarts the main agent)
104
- volute merge atlas experiment --summary "improved response style"
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 fork atlas poet --soul "You are a poet who responds only in verse."
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 upgrade
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,79 @@
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-G525LWEA.js").then((m) => m.run(args.slice(1)));
10
+ break;
11
+ case "start":
12
+ await import("./start-VHQ7LNWM.js").then((m) => m.run(args.slice(1)));
13
+ break;
14
+ case "stop":
15
+ await import("./stop-CAGCT5NI.js").then((m) => m.run(args.slice(1)));
16
+ break;
17
+ case "restart":
18
+ await import("./restart-O4ETYLJF.js").then((m) => m.run(args.slice(1)));
19
+ break;
20
+ case "delete":
21
+ await import("./delete-2PH2CGDY.js").then((m) => m.run(args.slice(1)));
22
+ break;
23
+ case "list":
24
+ await import("./status-QAJWXKMZ.js").then((m) => m.run(args.slice(1)));
25
+ break;
26
+ case "status": {
27
+ const rest = args.slice(1);
28
+ if (!rest[0] && process.env.VOLUTE_AGENT) {
29
+ rest.unshift(process.env.VOLUTE_AGENT);
30
+ }
31
+ await import("./status-QAJWXKMZ.js").then((m) => m.run(rest));
32
+ break;
33
+ }
34
+ case "logs": {
35
+ const rest = args.slice(1);
36
+ const logsArgs = transformAgentFlag(rest);
37
+ await import("./logs-EDGK26AK.js").then((m) => m.run(logsArgs));
38
+ break;
39
+ }
40
+ case "upgrade":
41
+ await import("./upgrade-YXKPWDRU.js").then((m) => m.run(args.slice(1)));
42
+ break;
43
+ case "import":
44
+ await import("./import-AVKQJDYC.js").then((m) => m.run(args.slice(1)));
45
+ break;
46
+ case "--help":
47
+ case "-h":
48
+ case void 0:
49
+ printUsage();
50
+ break;
51
+ default:
52
+ printUsage();
53
+ process.exit(1);
54
+ }
55
+ }
56
+ function transformAgentFlag(args) {
57
+ if (args.length > 0 && args[0] && !args[0].startsWith("-")) {
58
+ return ["--agent", args[0], ...args.slice(1)];
59
+ }
60
+ return args;
61
+ }
62
+ function printUsage() {
63
+ console.log(`Usage:
64
+ volute agent create <name> [--template <name>]
65
+ volute agent start <name>
66
+ volute agent stop [name]
67
+ volute agent restart [name]
68
+ volute agent delete [name] [--force]
69
+ volute agent list
70
+ volute agent status [name]
71
+ volute agent logs [name] [--follow] [-n N]
72
+ volute agent upgrade [name] [--template <name>] [--continue]
73
+ volute agent import <path> [--name <name>] [--session <path>] [--template <name>]
74
+
75
+ Agent name can be omitted (where shown as [name]) if VOLUTE_AGENT is set.`);
76
+ }
77
+ export {
78
+ run
79
+ };
@@ -3,10 +3,10 @@ import {
3
3
  AgentManager,
4
4
  getAgentManager,
5
5
  initAgentManager
6
- } from "./chunk-I6OHXCMV.js";
7
- import "./chunk-DNOXHLE5.js";
8
- import "./chunk-SOZA2TLP.js";
9
- import "./chunk-3C2XR4IY.js";
6
+ } from "./chunk-G6ZNGLUX.js";
7
+ import "./chunk-H7AMDUIA.js";
8
+ import "./chunk-W76KWE23.js";
9
+ import "./chunk-UWHWAPGO.js";
10
10
  import "./chunk-K3NQKI34.js";
11
11
  export {
12
12
  AgentManager,
@@ -0,0 +1,262 @@
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
+ CHANNELS,
10
+ getChannelDriver
11
+ } from "./chunk-ZZOOTYXK.js";
12
+ import {
13
+ loadMergedEnv
14
+ } from "./chunk-H7AMDUIA.js";
15
+ import {
16
+ writeChannelEntry
17
+ } from "./chunk-BX7KI4S3.js";
18
+ import {
19
+ parseArgs
20
+ } from "./chunk-D424ZQGI.js";
21
+ import {
22
+ daemonFetch
23
+ } from "./chunk-JR4UXCTO.js";
24
+ import {
25
+ resolveAgent
26
+ } from "./chunk-UWHWAPGO.js";
27
+ import "./chunk-K3NQKI34.js";
28
+
29
+ // src/commands/channel.ts
30
+ async function run(args) {
31
+ const subcommand = args[0];
32
+ switch (subcommand) {
33
+ case "read":
34
+ await readChannel(args.slice(1));
35
+ break;
36
+ case "send":
37
+ await sendChannel(args.slice(1));
38
+ break;
39
+ case "list":
40
+ await listChannels(args.slice(1));
41
+ break;
42
+ case "users":
43
+ await listUsers(args.slice(1));
44
+ break;
45
+ case "create":
46
+ await createChannel(args.slice(1));
47
+ break;
48
+ case "typing":
49
+ await typingChannel(args.slice(1));
50
+ break;
51
+ case "--help":
52
+ case "-h":
53
+ case void 0:
54
+ printUsage();
55
+ break;
56
+ default:
57
+ printUsage();
58
+ process.exit(1);
59
+ }
60
+ }
61
+ function printUsage() {
62
+ console.log(`Usage:
63
+ volute channel read <channel-uri> [--limit N] [--agent <name>]
64
+ volute channel send <channel-uri> "<message>" [--agent <name>]
65
+ volute channel list [<platform>] [--agent <name>]
66
+ volute channel users <platform> [--agent <name>]
67
+ volute channel create <platform> --participants user1,user2 [--name "..."] [--agent <name>]
68
+ volute channel typing <channel-uri> [--agent <name>]
69
+ echo "message" | volute channel send <channel-uri> [--agent <name>]`);
70
+ }
71
+ async function readChannel(args) {
72
+ const { positional, flags } = parseArgs(args, {
73
+ agent: { type: "string" },
74
+ limit: { type: "number" }
75
+ });
76
+ const uri = positional[0];
77
+ if (!uri) {
78
+ console.error("Usage: volute channel read <channel-uri> [--limit N] [--agent <name>]");
79
+ process.exit(1);
80
+ }
81
+ const agentName = resolveAgentName(flags);
82
+ const { platform } = parseUri(uri);
83
+ const driver = requireDriver(platform);
84
+ const { dir } = resolveAgent(agentName);
85
+ const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName, VOLUTE_AGENT_DIR: dir };
86
+ try {
87
+ const limit = flags.limit ?? 20;
88
+ const output = await driver.read(env, uri, limit);
89
+ console.log(output);
90
+ } catch (err) {
91
+ console.error(err instanceof Error ? err.message : String(err));
92
+ process.exit(1);
93
+ }
94
+ }
95
+ async function sendChannel(args) {
96
+ const { positional, flags } = parseArgs(args, {
97
+ agent: { type: "string" }
98
+ });
99
+ const uri = positional[0];
100
+ const message = positional[1] ?? await readStdin();
101
+ if (!uri || !message) {
102
+ console.error('Usage: volute channel send <channel-uri> "<message>" [--agent <name>]');
103
+ console.error(' echo "message" | volute channel send <channel-uri> [--agent <name>]');
104
+ process.exit(1);
105
+ }
106
+ const agentName = resolveAgentName(flags);
107
+ const { platform } = parseUri(uri);
108
+ const driver = requireDriver(platform);
109
+ const { dir } = resolveAgent(agentName);
110
+ const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName, VOLUTE_AGENT_DIR: dir };
111
+ try {
112
+ await driver.send(env, uri, message);
113
+ try {
114
+ await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/history`, {
115
+ method: "POST",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify({ channel: uri, content: message })
118
+ });
119
+ } catch (err) {
120
+ console.error(`Failed to persist to history: ${err instanceof Error ? err.message : err}`);
121
+ }
122
+ } catch (err) {
123
+ console.error(err instanceof Error ? err.message : String(err));
124
+ process.exit(1);
125
+ }
126
+ }
127
+ async function listChannels(args) {
128
+ const { positional, flags } = parseArgs(args, {
129
+ agent: { type: "string" }
130
+ });
131
+ const platform = positional[0];
132
+ const agentName = resolveAgentName(flags);
133
+ const { dir } = resolveAgent(agentName);
134
+ const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName, VOLUTE_AGENT_DIR: dir };
135
+ const platforms = platform ? [platform] : Object.keys(CHANNELS);
136
+ for (const p of platforms) {
137
+ const driver = getChannelDriver(p);
138
+ if (!driver?.listConversations) continue;
139
+ try {
140
+ const convs = await driver.listConversations(env);
141
+ for (const conv of convs) {
142
+ writeChannelEntry(dir, conv.id, {
143
+ platformId: conv.platformId,
144
+ platform: p,
145
+ name: conv.name,
146
+ type: conv.type
147
+ });
148
+ const parts = [conv.id.padEnd(24), conv.name.padEnd(28), conv.type];
149
+ if (conv.participantCount != null) {
150
+ parts.push(String(conv.participantCount));
151
+ }
152
+ console.log(parts.join(" "));
153
+ }
154
+ } catch (err) {
155
+ console.error(`${p}: ${err instanceof Error ? err.message : String(err)}`);
156
+ }
157
+ }
158
+ }
159
+ async function listUsers(args) {
160
+ const { positional, flags } = parseArgs(args, {
161
+ agent: { type: "string" }
162
+ });
163
+ const platform = positional[0];
164
+ if (!platform) {
165
+ console.error("Usage: volute channel users <platform> [--agent <name>]");
166
+ process.exit(1);
167
+ }
168
+ const driver = requireDriver(platform);
169
+ if (!driver.listUsers) {
170
+ console.error(`Platform ${platform} does not support listing users`);
171
+ process.exit(1);
172
+ }
173
+ const agentName = resolveAgentName(flags);
174
+ const { dir } = resolveAgent(agentName);
175
+ const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName, VOLUTE_AGENT_DIR: dir };
176
+ try {
177
+ const users = await driver.listUsers(env);
178
+ for (const user of users) {
179
+ console.log(`${user.username.padEnd(20)} ${user.id.padEnd(20)} ${user.type ?? ""}`);
180
+ }
181
+ } catch (err) {
182
+ console.error(err instanceof Error ? err.message : String(err));
183
+ process.exit(1);
184
+ }
185
+ }
186
+ async function createChannel(args) {
187
+ const { positional, flags } = parseArgs(args, {
188
+ agent: { type: "string" },
189
+ participants: { type: "string" },
190
+ name: { type: "string" }
191
+ });
192
+ const platform = positional[0];
193
+ if (!platform || !flags.participants) {
194
+ console.error(
195
+ 'Usage: volute channel create <platform> --participants user1,user2 [--name "..."] [--agent <name>]'
196
+ );
197
+ process.exit(1);
198
+ }
199
+ const driver = requireDriver(platform);
200
+ if (!driver.createConversation) {
201
+ console.error(`Platform ${platform} does not support creating conversations`);
202
+ process.exit(1);
203
+ }
204
+ const agentName = resolveAgentName(flags);
205
+ const { dir } = resolveAgent(agentName);
206
+ const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName, VOLUTE_AGENT_DIR: dir };
207
+ const participants = flags.participants.split(",").map((s) => s.trim());
208
+ try {
209
+ const slug = await driver.createConversation(env, participants, flags.name);
210
+ console.log(slug);
211
+ } catch (err) {
212
+ console.error(err instanceof Error ? err.message : String(err));
213
+ process.exit(1);
214
+ }
215
+ }
216
+ async function typingChannel(args) {
217
+ const { positional, flags } = parseArgs(args, {
218
+ agent: { type: "string" }
219
+ });
220
+ const uri = positional[0];
221
+ if (!uri) {
222
+ console.error("Usage: volute channel typing <channel-uri> [--agent <name>]");
223
+ process.exit(1);
224
+ }
225
+ const agentName = resolveAgentName(flags);
226
+ try {
227
+ const res = await daemonFetch(
228
+ `/api/agents/${encodeURIComponent(agentName)}/typing?channel=${encodeURIComponent(uri)}`
229
+ );
230
+ if (!res.ok) {
231
+ const body = await res.json().catch(() => ({}));
232
+ console.error(body.error ?? `Server responded with ${res.status}`);
233
+ process.exit(1);
234
+ }
235
+ const data = await res.json();
236
+ if (data.typing.length > 0) {
237
+ console.log(data.typing.join(", "));
238
+ }
239
+ } catch (err) {
240
+ console.error(err instanceof Error ? err.message : String(err));
241
+ process.exit(1);
242
+ }
243
+ }
244
+ function parseUri(uri) {
245
+ const colonIdx = uri.indexOf(":");
246
+ if (colonIdx === -1) {
247
+ console.error(`Invalid channel URI: ${uri} (expected format: platform:id)`);
248
+ process.exit(1);
249
+ }
250
+ return { platform: uri.slice(0, colonIdx), channelId: uri.slice(colonIdx + 1) };
251
+ }
252
+ function requireDriver(platform) {
253
+ const driver = getChannelDriver(platform);
254
+ if (!driver) {
255
+ console.error(`No channel driver for platform: ${platform}`);
256
+ process.exit(1);
257
+ }
258
+ return driver;
259
+ }
260
+ export {
261
+ run
262
+ };
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ voluteHome
4
+ } from "./chunk-UWHWAPGO.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. Use --agent <name> or set VOLUTE_AGENT.");
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,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/lib/slugify.ts
4
+ function slugify(text) {
5
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
6
+ }
7
+
3
8
  // src/connectors/sdk.ts
4
- import { existsSync, readFileSync } from "fs";
5
- import { resolve } from "path";
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
+ import { join, resolve } from "path";
6
11
  function loadEnv() {
7
12
  const agentPort = process.env.VOLUTE_AGENT_PORT;
8
13
  const agentName = process.env.VOLUTE_AGENT_NAME;
@@ -92,6 +97,15 @@ function onShutdown(cleanup) {
92
97
  process.on("SIGINT", handler);
93
98
  process.on("SIGTERM", handler);
94
99
  }
100
+ function reportTyping(env, channel, sender, active) {
101
+ fetch(`${env.baseUrl}/typing`, {
102
+ method: "POST",
103
+ headers: getHeaders(env),
104
+ body: JSON.stringify({ channel, sender, active })
105
+ }).catch((err) => {
106
+ console.warn(`[typing] failed to report for ${sender} on ${channel}: ${err}`);
107
+ });
108
+ }
95
109
  async function fireAndForget(env, payload) {
96
110
  try {
97
111
  const res = await fetch(`${env.baseUrl}/message`, {
@@ -157,12 +171,63 @@ async function handleAgentMessage(env, payload, handlers) {
157
171
  await handlers.onError(errMsg);
158
172
  }
159
173
  }
174
+ function buildChannelSlug(platform, meta) {
175
+ if (meta.isDM) {
176
+ if (meta.recipients && meta.recipients.length > 0) {
177
+ const sorted = meta.recipients.map(slugify).sort();
178
+ return `${platform}:@${sorted.join(",")}`;
179
+ }
180
+ if (meta.senderName) {
181
+ return `${platform}:@${slugify(meta.senderName)}`;
182
+ }
183
+ }
184
+ if (meta.channelName && meta.serverName) {
185
+ return `${platform}:${slugify(meta.serverName)}/${slugify(meta.channelName)}`;
186
+ }
187
+ if (meta.channelName) {
188
+ return `${platform}:${slugify(meta.channelName)}`;
189
+ }
190
+ if (meta.platformId) {
191
+ return `${platform}:${meta.platformId}`;
192
+ }
193
+ return `${platform}:unknown`;
194
+ }
195
+ function readChannelMap(agentDir) {
196
+ const filePath = join(agentDir, ".volute", "channels.json");
197
+ if (!existsSync(filePath)) return {};
198
+ try {
199
+ return JSON.parse(readFileSync(filePath, "utf-8"));
200
+ } catch {
201
+ return {};
202
+ }
203
+ }
204
+ function writeChannelEntry(agentDir, slug, entry) {
205
+ const voluteDir = join(agentDir, ".volute");
206
+ mkdirSync(voluteDir, { recursive: true });
207
+ const filePath = join(voluteDir, "channels.json");
208
+ const map = readChannelMap(agentDir);
209
+ map[slug] = entry;
210
+ writeFileSync(filePath, JSON.stringify(map, null, 2) + "\n");
211
+ }
212
+ function resolveChannelId(agentDir, slug) {
213
+ const map = readChannelMap(agentDir);
214
+ if (map[slug]) {
215
+ return map[slug].platformId;
216
+ }
217
+ const colonIndex = slug.indexOf(":");
218
+ return colonIndex >= 0 ? slug.slice(colonIndex + 1) : slug;
219
+ }
160
220
 
161
221
  export {
222
+ slugify,
162
223
  loadEnv,
163
224
  loadFollowedChannels,
164
225
  splitMessage,
165
226
  onShutdown,
227
+ reportTyping,
166
228
  fireAndForget,
167
- handleAgentMessage
229
+ handleAgentMessage,
230
+ buildChannelSlug,
231
+ writeChannelEntry,
232
+ resolveChannelId
168
233
  };