volute 0.11.3 → 0.12.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.
@@ -21,14 +21,14 @@ async function run(args) {
21
21
  await import("./delete-BOTVU4YO.js").then((m) => m.run(args.slice(1)));
22
22
  break;
23
23
  case "list":
24
- await import("./status-QN5G4RRK.js").then((m) => m.run(args.slice(1)));
24
+ await import("./status-DFWM342I.js").then((m) => m.run(args.slice(1)));
25
25
  break;
26
26
  case "status": {
27
27
  const rest = args.slice(1);
28
28
  if (!rest[0] && process.env.VOLUTE_AGENT) {
29
29
  rest.unshift(process.env.VOLUTE_AGENT);
30
30
  }
31
- await import("./status-QN5G4RRK.js").then((m) => m.run(rest));
31
+ await import("./status-DFWM342I.js").then((m) => m.run(rest));
32
32
  break;
33
33
  }
34
34
  case "logs": {
@@ -3,7 +3,7 @@ import {
3
3
  AgentManager,
4
4
  getAgentManager,
5
5
  initAgentManager
6
- } from "./chunk-BG2IRSCA.js";
6
+ } from "./chunk-PHJCXAWJ.js";
7
7
  import "./chunk-46S7YHUB.js";
8
8
  import "./chunk-QF22MYDJ.js";
9
9
  import "./chunk-DP2DX4WV.js";
@@ -184,8 +184,18 @@ var AgentManager = class {
184
184
  }
185
185
  } catch {
186
186
  }
187
- const logsDir = resolve(stateDir(name), "logs");
187
+ const agentStateDir = stateDir(name);
188
+ const logsDir = resolve(agentStateDir, "logs");
188
189
  mkdirSync(logsDir, { recursive: true });
190
+ if (isIsolationEnabled()) {
191
+ try {
192
+ chownAgentDir(agentStateDir, baseName);
193
+ } catch (err) {
194
+ throw new Error(
195
+ `Cannot start agent ${name}: failed to set ownership on state directory ${agentStateDir}: ${err instanceof Error ? err.message : err}`
196
+ );
197
+ }
198
+ }
189
199
  const logStream = new RotatingLog(resolve(logsDir, "agent.log"));
190
200
  const agentEnv = loadMergedEnv(name);
191
201
  const env = {
@@ -196,8 +206,11 @@ var AgentManager = class {
196
206
  VOLUTE_AGENT_DIR: dir,
197
207
  VOLUTE_AGENT_PORT: String(port)
198
208
  };
209
+ if (isIsolationEnabled()) {
210
+ env.HOME = resolve(dir, "home");
211
+ }
199
212
  if (isIsolationEnabled() && process.env.CLAUDE_CONFIG_DIR) {
200
- const agentClaudeDir = resolve(dir, ".claude");
213
+ const agentClaudeDir = resolve(dir, "home", ".claude");
201
214
  try {
202
215
  mkdirSync(agentClaudeDir, { recursive: true });
203
216
  } catch (err) {
@@ -7,6 +7,15 @@ import {
7
7
  function slugify(text) {
8
8
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
9
9
  }
10
+ function buildVoluteSlug(opts) {
11
+ const isDM = opts.participants.length === 2;
12
+ if (isDM) {
13
+ const other = opts.participants.find((p) => p.username !== opts.agentUsername);
14
+ const otherSlug = other ? slugify(other.username) : "";
15
+ return otherSlug ? `volute:@${otherSlug}` : `volute:${opts.conversationId}`;
16
+ }
17
+ return opts.convTitle ? `volute:${slugify(opts.convTitle)}` : `volute:${opts.conversationId}`;
18
+ }
10
19
 
11
20
  // src/connectors/sdk.ts
12
21
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -148,6 +157,7 @@ function resolveChannelId(agentName, slug) {
148
157
 
149
158
  export {
150
159
  slugify,
160
+ buildVoluteSlug,
151
161
  loadEnv,
152
162
  loadFollowedChannels,
153
163
  splitMessage,
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ buildVoluteSlug,
3
4
  resolveChannelId,
4
5
  slugify,
5
6
  splitMessage,
6
7
  writeChannelEntry
7
- } from "./chunk-N6MLQ26B.js";
8
+ } from "./chunk-POLMWJFC.js";
8
9
  import {
9
10
  voluteHome
10
11
  } from "./chunk-DP2DX4WV.js";
@@ -452,26 +453,33 @@ async function listConversations4(env) {
452
453
  const convs = await res.json();
453
454
  const results = [];
454
455
  for (const conv of convs) {
455
- let participantCount;
456
+ let participants = [];
456
457
  try {
457
458
  const pRes = await fetch(
458
459
  `${url}/api/agents/${encodeURIComponent(agentName)}/conversations/${encodeURIComponent(conv.id)}/participants`,
459
460
  { headers }
460
461
  );
461
462
  if (pRes.ok) {
462
- const participants = await pRes.json();
463
- participantCount = participants.length;
463
+ participants = await pRes.json();
464
+ } else {
465
+ console.error(`[volute] failed to fetch participants for ${conv.id}: HTTP ${pRes.status}`);
464
466
  }
465
467
  } catch (err) {
466
468
  console.error(`[volute] failed to fetch participants for ${conv.id}:`, err);
467
469
  }
468
- const slug = conv.title ? `volute:${slugify(conv.title)}` : `volute:${conv.id}`;
470
+ const isDM = participants.length === 2;
471
+ const slug = buildVoluteSlug({
472
+ participants,
473
+ agentUsername: agentName,
474
+ convTitle: conv.title,
475
+ conversationId: conv.id
476
+ });
469
477
  results.push({
470
478
  id: slug,
471
479
  platformId: conv.id,
472
480
  name: conv.title ?? "(untitled)",
473
- type: participantCount === 2 ? "dm" : "group",
474
- participantCount
481
+ type: isDM ? "dm" : "group",
482
+ participantCount: participants.length
475
483
  });
476
484
  }
477
485
  return results;
@@ -510,16 +518,7 @@ async function createConversation4(env, participants, name) {
510
518
  throw new Error(data.error ?? `Failed to create conversation: ${res.status}`);
511
519
  }
512
520
  const conv = await res.json();
513
- const slug = name ? `volute:${slugify(name)}` : `volute:${conv.id}`;
514
- if (agentName) {
515
- writeChannelEntry(agentName, slug, {
516
- platformId: conv.id,
517
- platform: "volute",
518
- name: name ?? participants.join(", "),
519
- type: participants.length <= 1 ? "dm" : "group"
520
- });
521
- }
522
- return slug;
521
+ return `volute:${conv.id}`;
523
522
  }
524
523
 
525
524
  // src/lib/channels.ts
@@ -70,15 +70,17 @@ function isNewer(current, latest) {
70
70
  if (hasPrerelease(current) && !hasPrerelease(latest)) return true;
71
71
  return false;
72
72
  }
73
- async function checkForUpdate() {
73
+ async function checkForUpdate(force = false) {
74
74
  const current = getCurrentVersion();
75
- const cache = readCache();
76
- if (cache && Date.now() - cache.checkedAt < CACHE_TTL) {
77
- return {
78
- current,
79
- latest: cache.latest,
80
- updateAvailable: isNewer(current, cache.latest)
81
- };
75
+ if (!force) {
76
+ const cache = readCache();
77
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL) {
78
+ return {
79
+ current,
80
+ latest: cache.latest,
81
+ updateAvailable: isNewer(current, cache.latest)
82
+ };
83
+ }
82
84
  }
83
85
  try {
84
86
  const latest = await fetchLatestVersion();
package/dist/cli.js CHANGED
@@ -9,16 +9,16 @@ 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-KVRPKXLL.js");
12
+ const { default: pkg } = await import("./package-5LQNWBH7.js");
13
13
  console.log(pkg.version);
14
14
  process.exit(0);
15
15
  }
16
16
  switch (command) {
17
17
  case "agent":
18
- await import("./agent-ER2OFKWQ.js").then((m) => m.run(args));
18
+ await import("./agent-2AQPI3QV.js").then((m) => m.run(args));
19
19
  break;
20
20
  case "send":
21
- await import("./send-X6OQGSD6.js").then((m) => m.run(args));
21
+ await import("./send-V5ESR5O2.js").then((m) => m.run(args));
22
22
  break;
23
23
  case "history":
24
24
  await import("./history-NI5QP27M.js").then((m) => m.run(args));
@@ -39,13 +39,13 @@ switch (command) {
39
39
  await import("./env-CGORIKVF.js").then((m) => m.run(args));
40
40
  break;
41
41
  case "up":
42
- await import("./up-CRA3LTS7.js").then((m) => m.run(args));
42
+ await import("./up-A6XT6AFX.js").then((m) => m.run(args));
43
43
  break;
44
44
  case "down":
45
45
  await import("./down-O2EQJ5DO.js").then((m) => m.run(args));
46
46
  break;
47
47
  case "restart":
48
- await import("./daemon-restart-4C7Q4FO7.js").then((m) => m.run(args));
48
+ await import("./daemon-restart-IVJ7X4PF.js").then((m) => m.run(args));
49
49
  break;
50
50
  case "setup":
51
51
  await import("./setup-7N4KYOYN.js").then((m) => m.run(args));
@@ -54,10 +54,10 @@ switch (command) {
54
54
  await import("./service-UL3OCODG.js").then((m) => m.run(args));
55
55
  break;
56
56
  case "update":
57
- await import("./update-EZNCCKXS.js").then((m) => m.run(args));
57
+ await import("./update-PV3XM6DX.js").then((m) => m.run(args));
58
58
  break;
59
59
  case "status":
60
- await import("./status-QN5G4RRK.js").then((m) => m.run(args));
60
+ await import("./status-DFWM342I.js").then((m) => m.run(args));
61
61
  break;
62
62
  case "--help":
63
63
  case "-h":
@@ -124,7 +124,7 @@ Run 'volute --help' for usage.`);
124
124
  process.exit(1);
125
125
  }
126
126
  if (command !== "update") {
127
- import("./update-check-SM4244SU.js").then((m) => m.checkForUpdate()).then((result) => {
127
+ import("./update-check-YPGH5X4E.js").then((m) => m.checkForUpdate()).then((result) => {
128
128
  if (result.updateAvailable) {
129
129
  console.error(`
130
130
  Update available: ${result.current} \u2192 ${result.latest}`);
@@ -7,7 +7,7 @@ import {
7
7
  sendToAgent,
8
8
  slugify,
9
9
  writeChannelEntry
10
- } from "../chunk-N6MLQ26B.js";
10
+ } from "../chunk-POLMWJFC.js";
11
11
  import "../chunk-DP2DX4WV.js";
12
12
  import "../chunk-K3NQKI34.js";
13
13
 
@@ -6,7 +6,7 @@ import {
6
6
  onShutdown,
7
7
  sendToAgent,
8
8
  writeChannelEntry
9
- } from "../chunk-N6MLQ26B.js";
9
+ } from "../chunk-POLMWJFC.js";
10
10
  import "../chunk-DP2DX4WV.js";
11
11
  import "../chunk-K3NQKI34.js";
12
12
 
@@ -5,7 +5,7 @@ import {
5
5
  loadFollowedChannels,
6
6
  sendToAgent,
7
7
  writeChannelEntry
8
- } from "../chunk-N6MLQ26B.js";
8
+ } from "../chunk-POLMWJFC.js";
9
9
  import "../chunk-DP2DX4WV.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-L4PDLAOB.js";
4
+ } from "./chunk-IZEQ47HW.js";
5
5
  import {
6
6
  stopDaemon
7
7
  } from "./chunk-RGWADNLT.js";
package/dist/daemon.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  initAgentManager,
7
7
  loadJsonMap,
8
8
  saveJsonMap
9
- } from "./chunk-BG2IRSCA.js";
9
+ } from "./chunk-PHJCXAWJ.js";
10
10
  import {
11
11
  applyIsolation,
12
12
  chownAgentDir,
@@ -34,7 +34,7 @@ import {
34
34
  import {
35
35
  CHANNELS,
36
36
  getChannelDriver
37
- } from "./chunk-LIPPXNIE.js";
37
+ } from "./chunk-WKPMR5B2.js";
38
38
  import {
39
39
  exec,
40
40
  gitExec,
@@ -44,12 +44,12 @@ import {
44
44
  checkForUpdate,
45
45
  checkForUpdateCached,
46
46
  getCurrentVersion
47
- } from "./chunk-RT6Y7AR3.js";
47
+ } from "./chunk-YY2QX2J6.js";
48
48
  import "./chunk-D424ZQGI.js";
49
49
  import {
50
- slugify,
50
+ buildVoluteSlug,
51
51
  writeChannelEntry
52
- } from "./chunk-N6MLQ26B.js";
52
+ } from "./chunk-POLMWJFC.js";
53
53
  import {
54
54
  addAgent,
55
55
  addVariant,
@@ -244,8 +244,19 @@ var ConnectorManager = class {
244
244
  } else {
245
245
  throw new Error(`No connector code found for type: ${type}`);
246
246
  }
247
- const logsDir = resolve2(stateDir(agentName), "logs");
247
+ const agentStateDir = stateDir(agentName);
248
+ const logsDir = resolve2(agentStateDir, "logs");
248
249
  mkdirSync(logsDir, { recursive: true });
250
+ if (isIsolationEnabled()) {
251
+ try {
252
+ const [base] = agentName.split("@", 2);
253
+ chownAgentDir(agentStateDir, base);
254
+ } catch (err) {
255
+ throw new Error(
256
+ `Cannot start connector ${type} for ${agentName}: failed to set ownership on state directory ${agentStateDir}: ${err instanceof Error ? err.message : err}`
257
+ );
258
+ }
259
+ }
249
260
  const logStream = new RotatingLog(resolve2(logsDir, `${type}.log`));
250
261
  const agentEnv = loadMergedEnv(agentName);
251
262
  const prefix = `${type.toUpperCase()}_`;
@@ -1708,7 +1719,7 @@ var app = new Hono().post("/", requireAdmin, async (c) => {
1708
1719
  createAgentUser(name);
1709
1720
  chownAgentDir(dest, name);
1710
1721
  const ids = isIsolationEnabled() ? await getAgentUserIds(name) : void 0;
1711
- const env = ids ? { ...process.env, HOME: dest } : void 0;
1722
+ const env = ids ? { ...process.env, HOME: resolve9(dest, "home") } : void 0;
1712
1723
  await exec("npm", ["install"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1713
1724
  let gitWarning;
1714
1725
  try {
@@ -1804,7 +1815,7 @@ ${user.trimEnd()}
1804
1815
  createAgentUser(name);
1805
1816
  chownAgentDir(dest, name);
1806
1817
  const ids = isIsolationEnabled() ? await getAgentUserIds(name) : void 0;
1807
- const env = ids ? { ...process.env, HOME: dest } : void 0;
1818
+ const env = ids ? { ...process.env, HOME: resolve9(dest, "home") } : void 0;
1808
1819
  await exec("npm", ["install"], { cwd: dest, uid: ids?.uid, gid: ids?.gid, env });
1809
1820
  if (!hasMemory && dailyLogCount > 0) {
1810
1821
  await consolidateMemory(dest);
@@ -3508,7 +3519,6 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
3508
3519
  }
3509
3520
  const conv = await getConversation(conversationId);
3510
3521
  const convTitle = conv?.title;
3511
- const channel = convTitle ? `volute:${slugify(convTitle)}` : `volute:${conversationId}`;
3512
3522
  const contentBlocks = [];
3513
3523
  if (body.message) {
3514
3524
  contentBlocks.push({ type: "text", text: body.message });
@@ -3522,13 +3532,21 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
3522
3532
  const participants = await getParticipants(conversationId);
3523
3533
  const agentParticipants = participants.filter((p) => p.userType === "agent");
3524
3534
  const participantNames = participants.map((p) => p.username);
3525
- const { getAgentManager: getAgentManager2 } = await import("./agent-manager-BHSSUMT3.js");
3535
+ const { getAgentManager: getAgentManager2 } = await import("./agent-manager-RMWXST3T.js");
3526
3536
  const manager = getAgentManager2();
3527
3537
  const runningAgents = agentParticipants.map((ap) => {
3528
3538
  const agentKey = ap.username === baseName ? name : ap.username;
3529
3539
  return manager.isRunning(agentKey) ? ap.username : null;
3530
3540
  }).filter((n) => n !== null && n !== senderName);
3531
3541
  const isDM = participants.length === 2;
3542
+ function channelForAgent(agentUsername) {
3543
+ return buildVoluteSlug({
3544
+ participants,
3545
+ agentUsername,
3546
+ convTitle,
3547
+ conversationId
3548
+ });
3549
+ }
3532
3550
  const channelEntry = {
3533
3551
  platformId: conversationId,
3534
3552
  platform: "volute",
@@ -3537,25 +3555,26 @@ var app13 = new Hono13().post("/:name/chat", zValidator3("json", chatSchema), as
3537
3555
  };
3538
3556
  for (const ap of agentParticipants) {
3539
3557
  try {
3540
- writeChannelEntry(ap.username, channel, channelEntry);
3558
+ writeChannelEntry(ap.username, channelForAgent(ap.username), channelEntry);
3541
3559
  } catch (err) {
3542
3560
  console.warn(`[chat] failed to write channel entry for ${ap.username}:`, err);
3543
3561
  }
3544
3562
  }
3545
- const typingMap = getTypingMap();
3546
- const currentlyTyping = typingMap.get(channel);
3547
- const payload = JSON.stringify({
3548
- content: contentBlocks,
3549
- channel,
3550
- conversationId,
3551
- sender: senderName,
3552
- participants: participantNames,
3553
- participantCount: participants.length,
3554
- isDM,
3555
- ...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
3556
- });
3557
3563
  for (const agentName of runningAgents) {
3558
3564
  const targetName = agentName === baseName ? name : agentName;
3565
+ const channel = channelForAgent(agentName);
3566
+ const typingMap = getTypingMap();
3567
+ const currentlyTyping = typingMap.get(channel);
3568
+ const payload = JSON.stringify({
3569
+ content: contentBlocks,
3570
+ channel,
3571
+ conversationId,
3572
+ sender: senderName,
3573
+ participants: participantNames,
3574
+ participantCount: participants.length,
3575
+ isDM,
3576
+ ...currentlyTyping.length > 0 ? { typing: currentlyTyping } : {}
3577
+ });
3559
3578
  daemonFetchInternal(`/api/agents/${encodeURIComponent(targetName)}/message`, payload).then(async (res) => {
3560
3579
  if (!res.ok) {
3561
3580
  const text2 = await res.text().catch(() => "");
@@ -4,7 +4,7 @@ import "./chunk-K3NQKI34.js";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "volute",
7
- version: "0.11.3",
7
+ version: "0.12.0",
8
8
  description: "CLI for creating and managing self-modifying AI agents powered by the Claude Agent SDK",
9
9
  type: "module",
10
10
  license: "MIT",
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-AZEL2IEK.js";
5
5
  import {
6
6
  getChannelDriver
7
- } from "./chunk-LIPPXNIE.js";
7
+ } from "./chunk-WKPMR5B2.js";
8
8
  import {
9
9
  parseArgs
10
10
  } from "./chunk-D424ZQGI.js";
11
- import "./chunk-N6MLQ26B.js";
11
+ import "./chunk-POLMWJFC.js";
12
12
  import {
13
13
  daemonFetch
14
14
  } from "./chunk-STOEJOJO.js";
@@ -8,7 +8,7 @@ import {
8
8
  import "./chunk-WTJI3JVR.js";
9
9
  import {
10
10
  checkForUpdate
11
- } from "./chunk-RT6Y7AR3.js";
11
+ } from "./chunk-YY2QX2J6.js";
12
12
  import "./chunk-DP2DX4WV.js";
13
13
  import "./chunk-K3NQKI34.js";
14
14
 
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readGlobalConfig,
4
4
  run
5
- } from "./chunk-L4PDLAOB.js";
5
+ } from "./chunk-IZEQ47HW.js";
6
6
  import "./chunk-ZKNBD5P3.js";
7
7
  import "./chunk-WTJI3JVR.js";
8
8
  import "./chunk-D424ZQGI.js";
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-WTJI3JVR.js";
14
14
  import {
15
15
  checkForUpdate
16
- } from "./chunk-RT6Y7AR3.js";
16
+ } from "./chunk-YY2QX2J6.js";
17
17
  import {
18
18
  voluteHome
19
19
  } from "./chunk-DP2DX4WV.js";
@@ -23,7 +23,7 @@ import "./chunk-K3NQKI34.js";
23
23
  import { existsSync, readFileSync, unlinkSync } from "fs";
24
24
  import { resolve } from "path";
25
25
  async function run(_args) {
26
- const result = await checkForUpdate();
26
+ const result = await checkForUpdate(true);
27
27
  if (result.checkFailed) {
28
28
  console.error("Could not reach npm registry. Check your network connection and try again.");
29
29
  process.exit(1);
@@ -5,7 +5,7 @@ import {
5
5
  fetchLatestVersion,
6
6
  getCurrentVersion,
7
7
  isNewer
8
- } from "./chunk-RT6Y7AR3.js";
8
+ } from "./chunk-YY2QX2J6.js";
9
9
  import "./chunk-DP2DX4WV.js";
10
10
  import "./chunk-K3NQKI34.js";
11
11
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volute",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for creating and managing self-modifying AI agents powered by the Claude Agent SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",
File without changes