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/dist/cli.js CHANGED
@@ -1,70 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import { homedir } from "os";
5
+ import { resolve } from "path";
6
+ if (!process.env.VOLUTE_HOME) {
7
+ process.env.VOLUTE_HOME = resolve(homedir(), ".volute");
8
+ }
4
9
  var command = process.argv[2];
5
10
  var args = process.argv.slice(3);
6
11
  if (command === "--version" || command === "-v") {
7
- const { default: pkg } = await import("./package-Z2SFO2SV.js");
12
+ const { default: pkg } = await import("./package-4DP4Y4UO.js");
8
13
  console.log(pkg.version);
9
14
  process.exit(0);
10
15
  }
11
16
  switch (command) {
12
- case "create":
13
- await import("./create-ILVOG75A.js").then((m) => m.run(args));
14
- break;
15
- case "start":
16
- await import("./start-LDPMCMYT.js").then((m) => m.run(args));
17
- break;
18
- case "stop":
19
- await import("./stop-5PZTZCLL.js").then((m) => m.run(args));
20
- break;
21
- case "logs":
22
- await import("./logs-NXFFGUKY.js").then((m) => m.run(args));
17
+ case "agent":
18
+ await import("./agent-X7GJLBLW.js").then((m) => m.run(args));
23
19
  break;
24
- case "status":
25
- await import("./status-MVSQG54T.js").then((m) => m.run(args));
20
+ case "message":
21
+ await import("./message-SCOQDR3P.js").then((m) => m.run(args));
26
22
  break;
27
23
  case "variant":
28
- await import("./variant-T64BKARF.js").then((m) => m.run(args));
29
- break;
30
- case "send":
31
- await import("./send-3U6OTKG7.js").then((m) => m.run(args));
32
- break;
33
- case "import":
34
- await import("./import-4CI2ZUTJ.js").then((m) => m.run(args));
35
- break;
36
- case "delete":
37
- await import("./delete-55MXCEY5.js").then((m) => m.run(args));
38
- break;
39
- case "env":
40
- await import("./env-JB27UAC3.js").then((m) => m.run(args));
24
+ await import("./variant-4Z6W3PP6.js").then((m) => m.run(args));
41
25
  break;
42
26
  case "connector":
43
- await import("./connector-DKDJTLYZ.js").then((m) => m.run(args));
27
+ await import("./connector-Y7JPNROO.js").then((m) => m.run(args));
44
28
  break;
45
29
  case "channel":
46
- await import("./channel-DQ6UY7QB.js").then((m) => m.run(args));
30
+ await import("./channel-SMCNOIVQ.js").then((m) => m.run(args));
31
+ break;
32
+ case "schedule":
33
+ await import("./schedule-S6QVC5ON.js").then((m) => m.run(args));
47
34
  break;
48
- case "upgrade":
49
- await import("./upgrade-6ZW2RD64.js").then((m) => m.run(args));
35
+ case "env":
36
+ await import("./env-7GLUJCWS.js").then((m) => m.run(args));
50
37
  break;
51
38
  case "up":
52
- await import("./up-F7TMTLRE.js").then((m) => m.run(args));
39
+ await import("./up-CSX3ZUIU.js").then((m) => m.run(args));
53
40
  break;
54
41
  case "down":
55
- await import("./down-3OB6UVAJ.js").then((m) => m.run(args));
56
- break;
57
- case "schedule":
58
- await import("./schedule-A35SH4HT.js").then((m) => m.run(args));
42
+ await import("./down-FXWAN66A.js").then((m) => m.run(args));
59
43
  break;
60
- case "history":
61
- await import("./history-BKG74I43.js").then((m) => m.run(args));
44
+ case "setup":
45
+ await import("./setup-F4TCWVSP.js").then((m) => m.run(args));
62
46
  break;
63
47
  case "service":
64
- await import("./service-SA4TTMDU.js").then((m) => m.run(args));
48
+ await import("./service-HZNIDNJF.js").then((m) => m.run(args));
65
49
  break;
66
- case "setup":
67
- await import("./setup-2FDVN7OF.js").then((m) => m.run(args));
50
+ case "update":
51
+ await import("./update-XSIX3GGP.js").then((m) => m.run(args));
68
52
  break;
69
53
  case "--help":
70
54
  case "-h":
@@ -72,45 +56,70 @@ switch (command) {
72
56
  console.log(`volute \u2014 create and manage AI agents
73
57
 
74
58
  Commands:
75
- volute create <name> Create a new agent
76
- volute start <name> Start an agent (daemonized)
77
- volute stop <name> Stop an agent
78
- volute status [<name>] Check agent status (or list all)
79
- volute logs [--agent <name>] Tail agent logs
80
- volute send <name> "<msg>" Send a message to an agent
81
- volute variant create <name> Create a variant (worktree + server)
82
- volute variant list List variants for an agent
83
- volute variant merge <name> Merge a variant back
84
- volute variant delete <name> Delete a variant
85
- volute import <path> Import an OpenClaw workspace
86
- volute env <set|get|list|remove> Manage environment variables
87
- volute connector connect <type> Enable a connector for an agent
88
- volute connector disconnect <type> Disable a connector for an agent
89
- volute channel read <uri> Read recent messages from a channel
90
- volute channel send <uri> "<msg>" Send a message to a channel
91
- volute schedule list List schedules for an agent
92
- volute schedule add ... Add a cron schedule
93
- volute schedule remove ... Remove a schedule
94
- volute history View message history
95
- volute up [--port N] Start the daemon (default: 4200)
96
- volute down Stop the daemon
97
- volute upgrade <name> Upgrade agent to latest template
98
- volute delete <name> [--force] Delete an agent (--force removes files)
99
- volute service install [--port N] Install as system service (auto-start)
100
- volute service uninstall Remove system service
101
- volute service status Check service status
102
- volute setup [--port N] [--host H] Install system service with user isolation
103
- volute setup uninstall [--force] Remove system service + isolation
59
+ volute agent create <name> Create a new agent
60
+ volute agent start <name> Start an agent (daemonized)
61
+ volute agent stop <name> Stop an agent
62
+ volute agent restart <name> Restart an agent
63
+ volute agent delete <name> [--force] Delete an agent (--force removes files)
64
+ volute agent list List all agents
65
+ volute agent status <name> Check agent status
66
+ volute agent logs <name> [--follow] Tail agent logs
67
+ volute agent upgrade <name> Upgrade agent to latest template
68
+ volute agent import <path> Import an OpenClaw workspace
69
+
70
+ volute message send <name> "<msg>" Send a message to an agent
71
+ volute message history [--agent <name>] View message history
72
+
73
+ volute variant create <name> Create a variant (worktree + server)
74
+ volute variant list List variants for an agent
75
+ volute variant merge <name> Merge a variant back
76
+ volute variant delete <name> Delete a variant
77
+
78
+ volute connector connect <type> Enable a connector for an agent
79
+ volute connector disconnect <type> Disable a connector for an agent
80
+
81
+ volute channel read <uri> Read recent messages from a channel
82
+ volute channel send <uri> "<msg>" Send a message to a channel
83
+ volute channel list [<platform>] List conversations on a platform
84
+ volute channel users <platform> List users on a platform
85
+ volute channel create <platform> ... Create a conversation on a platform
86
+
87
+ volute schedule list List schedules for an agent
88
+ volute schedule add ... Add a cron schedule
89
+ volute schedule remove ... Remove a schedule
90
+
91
+ volute env <set|get|list|remove> Manage environment variables
92
+
93
+ volute up [--port N] Start the daemon (default: 4200)
94
+ volute down Stop the daemon
95
+
96
+ volute service install [--port N] Install as system service (auto-start)
97
+ volute service uninstall Remove system service
98
+ volute service status Check service status
99
+ volute setup [--port N] [--host H] Install system service with user isolation
100
+ volute setup uninstall [--force] Remove system service + isolation
101
+
102
+ volute update Update to latest version
104
103
 
105
104
  Options:
106
- --version, -v Show version number
107
- --help, -h Show this help message
105
+ --version, -v Show version number
106
+ --help, -h Show this help message
108
107
 
109
- Agent commands (variant, connector, schedule, logs, history, channel) use
110
- --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
108
+ Agent-scoped commands (variant, connector, schedule, channel, message history)
109
+ use --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
111
110
  break;
112
111
  default:
113
112
  console.error(`Unknown command: ${command}
114
113
  Run 'volute --help' for usage.`);
115
114
  process.exit(1);
116
115
  }
116
+ if (command !== "update") {
117
+ import("./update-check-5ZADDHCK.js").then((m) => m.checkForUpdate()).then((result) => {
118
+ if (result.updateAvailable) {
119
+ console.error(`
120
+ Update available: ${result.current} \u2192 ${result.latest}`);
121
+ console.error(" Run `volute update` to update\n");
122
+ }
123
+ }).catch(() => {
124
+ });
125
+ }
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveAgentName
4
- } from "./chunk-VRVVQIYY.js";
4
+ } from "./chunk-AZEL2IEK.js";
5
5
  import {
6
6
  agentEnvPath,
7
7
  readEnv,
8
8
  writeEnv
9
- } from "./chunk-DNOXHLE5.js";
9
+ } from "./chunk-H7AMDUIA.js";
10
10
  import {
11
11
  parseArgs
12
12
  } from "./chunk-D424ZQGI.js";
13
13
  import {
14
14
  daemonFetch
15
- } from "./chunk-YGFIWIOF.js";
15
+ } from "./chunk-JR4UXCTO.js";
16
16
  import {
17
17
  agentDir
18
- } from "./chunk-3C2XR4IY.js";
18
+ } from "./chunk-UWHWAPGO.js";
19
19
  import "./chunk-K3NQKI34.js";
20
20
 
21
21
  // src/commands/connector.ts
@@ -28,13 +28,18 @@ async function run(args) {
28
28
  case "disconnect":
29
29
  await disconnectConnector(args.slice(1));
30
30
  break;
31
+ case "--help":
32
+ case "-h":
33
+ case void 0:
34
+ printUsage();
35
+ break;
31
36
  default:
32
37
  printUsage();
33
- process.exit(subcommand ? 1 : 0);
38
+ process.exit(1);
34
39
  }
35
40
  }
36
41
  function printUsage() {
37
- console.error(`Usage:
42
+ console.log(`Usage:
38
43
  volute connector connect <type> [--agent <name>]
39
44
  volute connector disconnect <type> [--agent <name>]`);
40
45
  }
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ buildChannelSlug,
3
4
  fireAndForget,
4
5
  handleAgentMessage,
5
6
  loadEnv,
6
7
  loadFollowedChannels,
7
- splitMessage
8
- } from "../chunk-MXUCNIBG.js";
8
+ reportTyping,
9
+ slugify,
10
+ splitMessage,
11
+ writeChannelEntry
12
+ } from "../chunk-BX7KI4S3.js";
9
13
  import "../chunk-K3NQKI34.js";
10
14
 
11
15
  // src/connectors/discord.ts
@@ -32,7 +36,9 @@ var client = new Client({
32
36
  GatewayIntentBits.Guilds,
33
37
  GatewayIntentBits.GuildMessages,
34
38
  GatewayIntentBits.MessageContent,
35
- GatewayIntentBits.DirectMessages
39
+ GatewayIntentBits.DirectMessages,
40
+ GatewayIntentBits.GuildMessageTyping,
41
+ GatewayIntentBits.DirectMessageTyping
36
42
  ],
37
43
  partials: [Partials.Channel]
38
44
  });
@@ -88,8 +94,24 @@ client.on(Events.MessageCreate, async (message) => {
88
94
  }
89
95
  if (content.length === 0) return;
90
96
  const senderName = message.author.displayName || message.author.username;
91
- const channelKey = `discord:${message.channelId}`;
92
97
  const channelName = !isDM && "name" in message.channel ? message.channel.name : void 0;
98
+ const channelKey = isDM ? buildChannelSlug("discord", {
99
+ isDM: true,
100
+ recipients: [message.author.username]
101
+ }) : buildChannelSlug("discord", {
102
+ channelName: channelName ?? message.channelId,
103
+ serverName: message.guild?.name
104
+ });
105
+ if (env.agentDir) {
106
+ writeChannelEntry(env.agentDir, channelKey, {
107
+ platformId: message.channelId,
108
+ platform: "discord",
109
+ name: channelName ? `#${channelName}` : void 0,
110
+ server: message.guild?.name,
111
+ type: isDM ? "dm" : "channel"
112
+ });
113
+ }
114
+ const participantCount = isDM ? 2 : message.guild?.memberCount;
93
115
  const payload = {
94
116
  content,
95
117
  channel: channelKey,
@@ -97,7 +119,8 @@ client.on(Events.MessageCreate, async (message) => {
97
119
  platform: "Discord",
98
120
  ...isDM ? { isDM: true } : {},
99
121
  ...channelName ? { channelName } : {},
100
- ...message.guild?.name ? { guildName: message.guild.name } : {}
122
+ ...message.guild?.name ? { serverName: message.guild.name } : {},
123
+ ...participantCount ? { participantCount } : {}
101
124
  };
102
125
  if (isFollowedChannel && !isMentioned) {
103
126
  await fireAndForget(env, payload);
@@ -105,6 +128,12 @@ client.on(Events.MessageCreate, async (message) => {
105
128
  }
106
129
  await handleDiscordMessage(message, payload);
107
130
  });
131
+ client.on(Events.TypingStart, (typing) => {
132
+ if (typing.user.bot) return;
133
+ const sender = typing.member?.displayName ?? typing.user.username ?? typing.user.id ?? "unknown";
134
+ const typingChannel = typing.guild ? `discord:${slugify(typing.guild.name)}/${slugify("name" in typing.channel ? String(typing.channel.name) : typing.channel.id)}` : `discord:@${slugify(typing.user.username ?? typing.user.id)}`;
135
+ reportTyping(env, typingChannel, sender, true);
136
+ });
108
137
  async function handleDiscordMessage(message, payload) {
109
138
  const channel = message.channel;
110
139
  if (!("sendTyping" in channel)) return;
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ buildChannelSlug,
3
4
  fireAndForget,
4
5
  handleAgentMessage,
5
6
  loadEnv,
6
7
  loadFollowedChannels,
7
8
  onShutdown,
8
- splitMessage
9
- } from "../chunk-MXUCNIBG.js";
9
+ splitMessage,
10
+ writeChannelEntry
11
+ } from "../chunk-BX7KI4S3.js";
10
12
  import "../chunk-K3NQKI34.js";
11
13
 
12
14
  // src/connectors/slack.ts
@@ -27,11 +29,12 @@ var app = new App({
27
29
  appToken
28
30
  });
29
31
  var botUserId;
32
+ var serverName;
30
33
  app.message(async ({ message, say }) => {
31
34
  if (message.subtype) return;
32
35
  if (!("user" in message) || !("text" in message)) return;
33
36
  if ("bot_id" in message && message.bot_id) return;
34
- const isDM = message.channel_type === "im";
37
+ const isDM = message.channel_type === "im" || message.channel_type === "mpim";
35
38
  const isMentioned = !isDM && botUserId && message.text?.includes(`<@${botUserId}>`);
36
39
  const isFollowedChannel = !isDM && followedChannelIds.has(message.channel);
37
40
  if (!isDM && !isMentioned && !isFollowedChannel) return;
@@ -65,33 +68,55 @@ app.message(async ({ message, say }) => {
65
68
  }
66
69
  if (content.length === 0) return;
67
70
  let channelName;
68
- if (!isDM) {
71
+ let numMembers;
72
+ if (message.channel_type !== "im") {
69
73
  try {
70
74
  const info = await app.client.conversations.info({
71
75
  channel: message.channel
72
76
  });
73
77
  channelName = info.channel?.name;
78
+ numMembers = info.channel?.num_members;
74
79
  } catch (err) {
75
- console.warn(`Failed to get channel name: ${err}`);
80
+ console.warn(`Failed to get channel info: ${err}`);
76
81
  }
77
82
  }
78
83
  let senderName = message.user;
84
+ let senderUsername = message.user;
79
85
  try {
80
86
  const userInfo = await app.client.users.info({
81
87
  user: message.user
82
88
  });
83
89
  senderName = userInfo.user?.profile?.display_name || userInfo.user?.profile?.real_name || message.user;
90
+ senderUsername = userInfo.user?.name ?? message.user;
84
91
  } catch (err) {
85
92
  console.warn(`Failed to get user info: ${err}`);
86
93
  }
87
- const channelKey = `slack:${message.channel}`;
94
+ const channelKey = isDM ? buildChannelSlug("slack", {
95
+ isDM: true,
96
+ senderName: senderUsername
97
+ }) : buildChannelSlug("slack", {
98
+ channelName: channelName ?? message.channel,
99
+ serverName
100
+ });
101
+ if (env.agentDir) {
102
+ writeChannelEntry(env.agentDir, channelKey, {
103
+ platformId: message.channel,
104
+ platform: "slack",
105
+ name: channelName ? `#${channelName}` : void 0,
106
+ server: serverName,
107
+ type: isDM ? "dm" : "channel"
108
+ });
109
+ }
110
+ const participantCount = message.channel_type === "im" ? 2 : numMembers;
88
111
  const payload = {
89
112
  content,
90
113
  channel: channelKey,
91
114
  sender: senderName,
92
115
  platform: "Slack",
93
116
  ...isDM ? { isDM: true } : {},
94
- ...channelName ? { channelName } : {}
117
+ ...channelName ? { channelName } : {},
118
+ ...serverName ? { serverName } : {},
119
+ ...participantCount ? { participantCount } : {}
95
120
  };
96
121
  if (isFollowedChannel && !isMentioned) {
97
122
  await fireAndForget(env, payload);
@@ -134,7 +159,10 @@ async function start() {
134
159
  throw new Error("auth.test succeeded but returned no user_id");
135
160
  }
136
161
  botUserId = auth.user_id;
137
- console.log(`Connected to Slack as bot user ${botUserId}`);
162
+ serverName = auth.team;
163
+ console.log(
164
+ `Connected to Slack as bot user ${botUserId}${serverName ? ` in ${serverName}` : ""}`
165
+ );
138
166
  console.log(`Bridging to agent: ${env.agentName} via ${env.baseUrl}/message`);
139
167
  if (followedChannelNames.length > 0) {
140
168
  try {
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ buildChannelSlug,
3
4
  fireAndForget,
4
5
  handleAgentMessage,
5
6
  loadEnv,
6
7
  loadFollowedChannels,
7
- splitMessage
8
- } from "../chunk-MXUCNIBG.js";
8
+ splitMessage,
9
+ writeChannelEntry
10
+ } from "../chunk-BX7KI4S3.js";
9
11
  import "../chunk-K3NQKI34.js";
10
12
 
11
13
  // src/connectors/telegram.ts
@@ -40,13 +42,36 @@ bot.on(message("text"), async (ctx) => {
40
42
  if (content.length === 0) return;
41
43
  const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
42
44
  const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
45
+ let participantCount = isDM ? 2 : void 0;
46
+ if (!isDM) {
47
+ try {
48
+ participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
49
+ } catch (err) {
50
+ console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
51
+ }
52
+ }
53
+ const channelSlug = isDM ? buildChannelSlug("telegram", {
54
+ isDM: true,
55
+ senderName: ctx.message.from.username ?? ctx.message.from.first_name
56
+ }) : buildChannelSlug("telegram", {
57
+ channelName: chatTitle ?? String(ctx.chat.id)
58
+ });
59
+ if (env.agentDir) {
60
+ writeChannelEntry(env.agentDir, channelSlug, {
61
+ platformId: String(ctx.chat.id),
62
+ platform: "telegram",
63
+ name: chatTitle,
64
+ type: isDM ? "dm" : "channel"
65
+ });
66
+ }
43
67
  const payload = {
44
68
  content,
45
- channel: `telegram:${ctx.chat.id}`,
69
+ channel: channelSlug,
46
70
  sender: senderName,
47
71
  platform: "Telegram",
48
72
  ...isDM ? { isDM: true } : {},
49
- ...chatTitle ? { channelName: chatTitle } : {}
73
+ ...chatTitle ? { channelName: chatTitle } : {},
74
+ ...participantCount ? { participantCount } : {}
50
75
  };
51
76
  if (isFollowedChat && !isMentioned) {
52
77
  await fireAndForget(env, payload);
@@ -88,12 +113,36 @@ bot.on(message("photo"), async (ctx) => {
88
113
  if (content.length === 0) return;
89
114
  const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
90
115
  const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
116
+ let participantCount = isDM ? 2 : void 0;
117
+ if (!isDM) {
118
+ try {
119
+ participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
120
+ } catch (err) {
121
+ console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
122
+ }
123
+ }
124
+ const channelSlug = isDM ? buildChannelSlug("telegram", {
125
+ isDM: true,
126
+ senderName: ctx.message.from.username ?? ctx.message.from.first_name
127
+ }) : buildChannelSlug("telegram", {
128
+ channelName: chatTitle ?? String(ctx.chat.id)
129
+ });
130
+ if (env.agentDir) {
131
+ writeChannelEntry(env.agentDir, channelSlug, {
132
+ platformId: String(ctx.chat.id),
133
+ platform: "telegram",
134
+ name: chatTitle,
135
+ type: isDM ? "dm" : "channel"
136
+ });
137
+ }
91
138
  const payload = {
92
139
  content,
93
- channel: `telegram:${ctx.chat.id}`,
140
+ channel: channelSlug,
94
141
  sender: senderName,
95
142
  platform: "Telegram",
96
- ...chatTitle ? { channelName: chatTitle } : {}
143
+ ...isDM ? { isDM: true } : {},
144
+ ...chatTitle ? { channelName: chatTitle } : {},
145
+ ...participantCount ? { participantCount } : {}
97
146
  };
98
147
  if (isFollowedChat) {
99
148
  await fireAndForget(env, payload);
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ applyInitFiles,
4
+ composeTemplate,
5
+ copyTemplateToDir,
6
+ findTemplatesRoot,
7
+ listFiles
8
+ } from "./chunk-XUA3JUFK.js";
9
+ import {
10
+ parseArgs
11
+ } from "./chunk-D424ZQGI.js";
12
+ import {
13
+ chownAgentDir,
14
+ createAgentUser,
15
+ ensureVoluteGroup
16
+ } from "./chunk-W76KWE23.js";
17
+ import {
18
+ exec,
19
+ execInherit
20
+ } from "./chunk-5SKQ6J7T.js";
21
+ import {
22
+ addAgent,
23
+ agentDir,
24
+ ensureVoluteHome,
25
+ nextPort
26
+ } from "./chunk-UWHWAPGO.js";
27
+ import "./chunk-K3NQKI34.js";
28
+
29
+ // src/commands/create.ts
30
+ import { existsSync, rmSync } from "fs";
31
+ import { resolve } from "path";
32
+ var TEMPLATE_BRANCH = "volute/template";
33
+ async function run(args) {
34
+ const { positional, flags } = parseArgs(args, {
35
+ template: { type: "string" }
36
+ });
37
+ const name = positional[0];
38
+ const template = flags.template ?? "agent-sdk";
39
+ if (!name) {
40
+ console.error("Usage: volute agent create <name> [--template <name>]");
41
+ process.exit(1);
42
+ }
43
+ ensureVoluteHome();
44
+ const dest = agentDir(name);
45
+ if (existsSync(dest)) {
46
+ console.error(`Agent already exists: ${name}`);
47
+ process.exit(1);
48
+ }
49
+ const templatesRoot = findTemplatesRoot();
50
+ const { composedDir, manifest } = composeTemplate(templatesRoot, template);
51
+ try {
52
+ copyTemplateToDir(composedDir, dest, name, manifest);
53
+ applyInitFiles(dest);
54
+ const port = nextPort();
55
+ addAgent(name, port);
56
+ console.log("Installing dependencies...");
57
+ await execInherit("npm", ["install"], { cwd: dest });
58
+ try {
59
+ await exec("git", ["init"], { cwd: dest });
60
+ await initTemplateBranch(dest, composedDir, manifest);
61
+ } catch (err) {
62
+ rmSync(resolve(dest, ".git"), { recursive: true, force: true });
63
+ console.warn(
64
+ "\nWarning: git setup failed \u2014 variants and upgrades won't be available.",
65
+ "\nTo fix: ensure git is installed with user.name/user.email configured."
66
+ );
67
+ console.warn("Details:", err.message ?? err);
68
+ }
69
+ ensureVoluteGroup();
70
+ createAgentUser(name);
71
+ chownAgentDir(dest, name);
72
+ console.log(`
73
+ Created agent: ${name} (port ${port})`);
74
+ console.log(`
75
+ volute agent start ${name}`);
76
+ } finally {
77
+ rmSync(composedDir, { recursive: true, force: true });
78
+ }
79
+ }
80
+ async function initTemplateBranch(projectRoot, composedDir, manifest) {
81
+ const templateFiles = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).map((f) => manifest.rename[f] ?? f);
82
+ await exec("git", ["checkout", "--orphan", TEMPLATE_BRANCH], { cwd: projectRoot });
83
+ await exec("git", ["add", "--", ...templateFiles], { cwd: projectRoot });
84
+ await exec("git", ["commit", "-m", "template update"], { cwd: projectRoot });
85
+ await exec("git", ["checkout", "-b", "main"], { cwd: projectRoot });
86
+ await exec("git", ["add", "-A"], { cwd: projectRoot });
87
+ await exec("git", ["commit", "-m", "initial commit"], { cwd: projectRoot });
88
+ }
89
+ export {
90
+ run
91
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  daemonFetch
4
- } from "./chunk-YGFIWIOF.js";
5
- import "./chunk-3C2XR4IY.js";
4
+ } from "./chunk-JR4UXCTO.js";
5
+ import "./chunk-UWHWAPGO.js";
6
6
  import "./chunk-K3NQKI34.js";
7
7
  export {
8
8
  daemonFetch