volute 0.16.0 → 0.18.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 (49) hide show
  1. package/dist/chunk-AYB7XAWO.js +812 -0
  2. package/dist/{chunk-3FD4ZZUL.js → chunk-FW5API7X.js} +116 -10
  3. package/dist/{chunk-3FC42ZBM.js → chunk-GK4E7LM7.js} +3 -0
  4. package/dist/cli.js +18 -6
  5. package/dist/connectors/discord.js +1 -1
  6. package/dist/connectors/slack.js +1 -1
  7. package/dist/connectors/telegram.js +1 -1
  8. package/dist/{daemon-restart-MS5FI44G.js → daemon-restart-2HVTHZAT.js} +1 -1
  9. package/dist/daemon.js +1443 -592
  10. package/dist/history-YUEKTJ2N.js +108 -0
  11. package/dist/{mind-manager-PN5SUDJ4.js → mind-manager-Z7O7PN2O.js} +1 -1
  12. package/dist/{package-3QGV3KX6.js → package-OKLFO7UY.js} +8 -9
  13. package/dist/{send-KBBZNYG6.js → send-BNDTLUPM.js} +41 -9
  14. package/dist/skill-2Y42P4JY.js +287 -0
  15. package/dist/{up-GZLWZAQE.js → up-7B3BWF2U.js} +1 -1
  16. package/dist/web-assets/assets/index-CtiimdWK.css +1 -0
  17. package/dist/web-assets/assets/index-kt1_EcuO.js +63 -0
  18. package/dist/web-assets/index.html +2 -1
  19. package/drizzle/0006_mind_history.sql +20 -0
  20. package/drizzle/0007_system_prompts.sql +5 -0
  21. package/drizzle/0008_volute_channels.sql +24 -0
  22. package/drizzle/0009_shared_skills.sql +9 -0
  23. package/drizzle/meta/0006_snapshot.json +7 -0
  24. package/drizzle/meta/0007_snapshot.json +7 -0
  25. package/drizzle/meta/0008_snapshot.json +7 -0
  26. package/drizzle/meta/0009_snapshot.json +7 -0
  27. package/drizzle/meta/_journal.json +28 -0
  28. package/package.json +8 -9
  29. package/templates/_base/.init/.config/prompts.json +5 -0
  30. package/templates/_base/_skills/volute-mind/SKILL.md +19 -5
  31. package/templates/_base/src/lib/daemon-client.ts +45 -0
  32. package/templates/_base/src/lib/logger.ts +19 -0
  33. package/templates/_base/src/lib/router.ts +48 -41
  34. package/templates/_base/src/lib/routing.ts +5 -8
  35. package/templates/_base/src/lib/startup.ts +43 -0
  36. package/templates/_base/src/lib/transparency.ts +89 -0
  37. package/templates/_base/src/lib/types.ts +0 -1
  38. package/templates/_base/src/lib/volute-server.ts +3 -35
  39. package/templates/claude/src/agent.ts +9 -22
  40. package/templates/claude/src/lib/hooks/reply-instructions.ts +6 -9
  41. package/templates/claude/src/lib/stream-consumer.ts +39 -12
  42. package/templates/pi/src/agent.ts +9 -22
  43. package/templates/pi/src/lib/event-handler.ts +58 -7
  44. package/templates/pi/src/lib/reply-instructions-extension.ts +6 -9
  45. package/dist/chunk-J52CJCVI.js +0 -447
  46. package/dist/history-LKCJJMUV.js +0 -50
  47. package/dist/web-assets/assets/index-B1XIIGCh.js +0 -307
  48. package/templates/_base/src/lib/auto-reply.ts +0 -38
  49. /package/dist/{chunk-LLBBVTEY.js → chunk-6DVBMLVN.js} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  slugify,
6
6
  splitMessage,
7
7
  writeChannelEntry
8
- } from "./chunk-3FC42ZBM.js";
8
+ } from "./chunk-GK4E7LM7.js";
9
9
  import {
10
10
  voluteHome
11
11
  } from "./chunk-M77QBTEH.js";
@@ -50,9 +50,34 @@ async function read(env, channelSlug, limit) {
50
50
  const messages = await res.json();
51
51
  return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
52
52
  }
53
- async function send(env, channelSlug, message) {
53
+ async function send(env, channelSlug, message, images) {
54
54
  const token = requireToken(env);
55
55
  const channelId = resolveChannelId2(env, channelSlug);
56
+ if (images?.length) {
57
+ for (let i = 0; i < images.length; i++) {
58
+ const img = images[i];
59
+ const ext = img.media_type.split("/")[1] || "png";
60
+ const form = new FormData();
61
+ const content = i === 0 ? message.slice(0, DISCORD_MAX_LENGTH) : "";
62
+ form.append("payload_json", JSON.stringify({ content }));
63
+ form.append(
64
+ "files[0]",
65
+ new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
66
+ `image.${ext}`
67
+ );
68
+ const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
69
+ method: "POST",
70
+ headers: { Authorization: `Bot ${token}` },
71
+ body: form
72
+ });
73
+ if (!res.ok) {
74
+ const body = await res.text().catch(() => "");
75
+ const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
76
+ throw new Error(`Discord API error: ${res.status} ${body || res.statusText}${partial}`);
77
+ }
78
+ }
79
+ return;
80
+ }
56
81
  const chunks = splitMessage(message, DISCORD_MAX_LENGTH);
57
82
  for (let i = 0; i < chunks.length; i++) {
58
83
  const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
@@ -196,9 +221,41 @@ async function read2(env, channelSlug, limit) {
196
221
  });
197
222
  return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
198
223
  }
199
- async function send2(env, channelSlug, message) {
224
+ async function send2(env, channelSlug, message, images) {
200
225
  const token = requireToken2(env);
201
226
  const channelId = resolveChannelId2(env, channelSlug);
227
+ if (images?.length) {
228
+ for (const img of images) {
229
+ const ext = img.media_type.split("/")[1] || "png";
230
+ const filename = `image.${ext}`;
231
+ const binary = Buffer.from(img.data, "base64");
232
+ const uploadData = await slackApi(token, "files.getUploadURLExternal", {
233
+ filename,
234
+ length: binary.length
235
+ });
236
+ const uploadRes = await fetch(uploadData.upload_url, {
237
+ method: "POST",
238
+ body: binary
239
+ });
240
+ if (!uploadRes.ok) {
241
+ throw new Error(`Slack file upload failed: ${uploadRes.status} ${uploadRes.statusText}`);
242
+ }
243
+ await slackApi(token, "files.completeUploadExternal", {
244
+ files: [{ id: uploadData.file_id }],
245
+ channel_id: channelId
246
+ });
247
+ }
248
+ if (message) {
249
+ const chunks2 = splitMessage(message, SLACK_MAX_LENGTH);
250
+ for (const chunk of chunks2) {
251
+ await slackApi(token, "chat.postMessage", {
252
+ channel: channelId,
253
+ text: chunk
254
+ });
255
+ }
256
+ }
257
+ return;
258
+ }
202
259
  const chunks = splitMessage(message, SLACK_MAX_LENGTH);
203
260
  for (let i = 0; i < chunks.length; i++) {
204
261
  try {
@@ -335,9 +392,51 @@ async function read3(_env, _channelSlug, _limit) {
335
392
  "Telegram Bot API does not support reading chat history. Use volute send instead."
336
393
  );
337
394
  }
338
- async function send3(env, channelSlug, message) {
395
+ async function send3(env, channelSlug, message, images) {
339
396
  const token = requireToken3(env);
340
397
  const chatId = resolveChannelId2(env, channelSlug);
398
+ if (images?.length) {
399
+ const CAPTION_MAX = 1024;
400
+ for (let i = 0; i < images.length; i++) {
401
+ const img = images[i];
402
+ const ext = img.media_type.split("/")[1] || "png";
403
+ const form = new FormData();
404
+ form.append("chat_id", chatId);
405
+ form.append(
406
+ "photo",
407
+ new Blob([Buffer.from(img.data, "base64")], { type: img.media_type }),
408
+ `image.${ext}`
409
+ );
410
+ if (i === 0 && message) {
411
+ form.append("caption", message.slice(0, CAPTION_MAX));
412
+ }
413
+ const res = await fetch(`${API_BASE3}/bot${token}/sendPhoto`, {
414
+ method: "POST",
415
+ body: form
416
+ });
417
+ if (!res.ok) {
418
+ const body = await res.text().catch(() => "");
419
+ const partial = i > 0 ? ` (${i}/${images.length} images were already sent)` : "";
420
+ throw new Error(`Telegram API error: ${res.status} ${body}${partial}`);
421
+ }
422
+ }
423
+ if (message && message.length > CAPTION_MAX) {
424
+ const remaining = message.slice(CAPTION_MAX);
425
+ const chunks2 = splitMessage(remaining, TELEGRAM_MAX_LENGTH);
426
+ for (const chunk of chunks2) {
427
+ const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
428
+ method: "POST",
429
+ headers: { "Content-Type": "application/json" },
430
+ body: JSON.stringify({ chat_id: chatId, text: chunk })
431
+ });
432
+ if (!res.ok) {
433
+ const body = await res.text().catch(() => "");
434
+ throw new Error(`Telegram API error: ${res.status} ${body}`);
435
+ }
436
+ }
437
+ }
438
+ return;
439
+ }
341
440
  const chunks = splitMessage(message, TELEGRAM_MAX_LENGTH);
342
441
  for (let i = 0; i < chunks.length; i++) {
343
442
  const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
@@ -418,7 +517,7 @@ async function read4(env, channelSlug, limit) {
418
517
  return `${m.sender_name ?? m.role}: ${text}`;
419
518
  }).join("\n");
420
519
  }
421
- async function send4(env, channelSlug, message) {
520
+ async function send4(env, channelSlug, message, images) {
422
521
  const mindName = env.VOLUTE_MIND;
423
522
  if (!mindName) throw new Error("VOLUTE_MIND not set");
424
523
  const conversationId = resolveChannelId2(env, channelSlug);
@@ -431,7 +530,12 @@ async function send4(env, channelSlug, message) {
431
530
  const res = await fetch(`${url}/api/minds/${encodeURIComponent(mindName)}/chat`, {
432
531
  method: "POST",
433
532
  headers,
434
- body: JSON.stringify({ message, conversationId, sender: env.VOLUTE_SENDER ?? mindName })
533
+ body: JSON.stringify({
534
+ message,
535
+ conversationId,
536
+ sender: env.VOLUTE_SENDER ?? mindName,
537
+ images
538
+ })
435
539
  });
436
540
  if (!res.ok) {
437
541
  const data = await res.json().catch(() => ({}));
@@ -467,18 +571,20 @@ async function listConversations4(env) {
467
571
  } catch (err) {
468
572
  console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
469
573
  }
470
- const isDM = participants.length === 2;
471
574
  const slug = buildVoluteSlug({
472
575
  participants,
473
576
  mindUsername: mindName,
474
577
  convTitle: conv.title,
475
- conversationId: conv.id
578
+ conversationId: conv.id,
579
+ convType: conv.type,
580
+ convName: conv.name
476
581
  });
582
+ const convType = conv.type === "channel" ? "channel" : participants.length === 2 ? "dm" : "group";
477
583
  results.push({
478
584
  id: slug,
479
585
  platformId: conv.id,
480
- name: conv.title ?? "(untitled)",
481
- type: isDM ? "dm" : "group",
586
+ name: conv.type === "channel" ? `#${conv.name}` : conv.title ?? "(untitled)",
587
+ type: convType,
482
588
  participantCount: participants.length
483
589
  });
484
590
  }
@@ -8,6 +8,9 @@ function slugify(text) {
8
8
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
9
9
  }
10
10
  function buildVoluteSlug(opts) {
11
+ if (opts.convType === "channel" && opts.convName) {
12
+ return `volute:#${opts.convName}`;
13
+ }
11
14
  const isDM = opts.participants.length === 2;
12
15
  if (isDM) {
13
16
  const other = opts.participants.find((p) => p.username !== opts.mindUsername);
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ if (!process.env.VOLUTE_HOME) {
9
9
  var command = process.argv[2];
10
10
  var args = process.argv.slice(3);
11
11
  if (command === "--version" || command === "-v") {
12
- const { default: pkg } = await import("./package-3QGV3KX6.js");
12
+ const { default: pkg } = await import("./package-OKLFO7UY.js");
13
13
  console.log(pkg.version);
14
14
  process.exit(0);
15
15
  }
@@ -18,10 +18,10 @@ switch (command) {
18
18
  await import("./mind-OJN6RBZW.js").then((m) => m.run(args));
19
19
  break;
20
20
  case "send":
21
- await import("./send-KBBZNYG6.js").then((m) => m.run(args));
21
+ await import("./send-BNDTLUPM.js").then((m) => m.run(args));
22
22
  break;
23
23
  case "history":
24
- await import("./history-LKCJJMUV.js").then((m) => m.run(args));
24
+ await import("./history-YUEKTJ2N.js").then((m) => m.run(args));
25
25
  break;
26
26
  case "variant":
27
27
  await import("./variant-X5QFG6KK.js").then((m) => m.run(args));
@@ -35,17 +35,20 @@ switch (command) {
35
35
  case "schedule":
36
36
  await import("./schedule-AGYLDMNS.js").then((m) => m.run(args));
37
37
  break;
38
+ case "skill":
39
+ await import("./skill-2Y42P4JY.js").then((m) => m.run(args));
40
+ break;
38
41
  case "env":
39
42
  await import("./env-6IDWGBUH.js").then((m) => m.run(args));
40
43
  break;
41
44
  case "up":
42
- await import("./up-GZLWZAQE.js").then((m) => m.run(args));
45
+ await import("./up-7B3BWF2U.js").then((m) => m.run(args));
43
46
  break;
44
47
  case "down":
45
48
  await import("./down-A56B5JLK.js").then((m) => m.run(args));
46
49
  break;
47
50
  case "restart":
48
- await import("./daemon-restart-MS5FI44G.js").then((m) => m.run(args));
51
+ await import("./daemon-restart-2HVTHZAT.js").then((m) => m.run(args));
49
52
  break;
50
53
  case "setup":
51
54
  await import("./setup-DJKIZKGW.js").then((m) => m.run(args));
@@ -114,6 +117,15 @@ Commands:
114
117
  volute schedule add ... Add a cron schedule
115
118
  volute schedule remove ... Remove a schedule
116
119
 
120
+ volute skill list List shared skills
121
+ volute skill list --mind <name> List installed skills for a mind
122
+ volute skill info <name> Show details of a shared skill
123
+ volute skill install <name> --mind Install a shared skill into a mind
124
+ volute skill update <name> --mind Update an installed skill
125
+ volute skill publish <name> --mind Publish a mind's skill to shared repo
126
+ volute skill remove <name> Remove a shared skill
127
+ volute skill uninstall <name> --mind Uninstall a skill from a mind
128
+
117
129
  volute env <set|get|list|remove> Manage environment variables
118
130
 
119
131
  volute up [--port N] Start the daemon (default: 4200)
@@ -143,7 +155,7 @@ Options:
143
155
  --version, -v Show version number
144
156
  --help, -h Show this help message
145
157
 
146
- Mind-scoped commands (send, history, variant, connector, schedule, channel, pages)
158
+ Mind-scoped commands (send, history, variant, connector, schedule, channel, skill, pages)
147
159
  use --mind <name> or VOLUTE_MIND env var to identify the mind.`);
148
160
  break;
149
161
  default:
@@ -7,7 +7,7 @@ import {
7
7
  sendToMind,
8
8
  slugify,
9
9
  writeChannelEntry
10
- } from "../chunk-3FC42ZBM.js";
10
+ } from "../chunk-GK4E7LM7.js";
11
11
  import "../chunk-M77QBTEH.js";
12
12
  import "../chunk-K3NQKI34.js";
13
13
 
@@ -6,7 +6,7 @@ import {
6
6
  onShutdown,
7
7
  sendToMind,
8
8
  writeChannelEntry
9
- } from "../chunk-3FC42ZBM.js";
9
+ } from "../chunk-GK4E7LM7.js";
10
10
  import "../chunk-M77QBTEH.js";
11
11
  import "../chunk-K3NQKI34.js";
12
12
 
@@ -5,7 +5,7 @@ import {
5
5
  loadFollowedChannels,
6
6
  sendToMind,
7
7
  writeChannelEntry
8
- } from "../chunk-3FC42ZBM.js";
8
+ } from "../chunk-GK4E7LM7.js";
9
9
  import "../chunk-M77QBTEH.js";
10
10
  import "../chunk-K3NQKI34.js";
11
11
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-LLBBVTEY.js";
4
+ } from "./chunk-6DVBMLVN.js";
5
5
  import {
6
6
  stopDaemon
7
7
  } from "./chunk-QJIIHU32.js";