volute 0.11.4 → 0.13.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 (61) hide show
  1. package/dist/{agent-2AQPI3QV.js → agent-HYX2ZTFM.js} +10 -10
  2. package/dist/{agent-manager-AZUDAKCP.js → agent-manager-HHBAAL2D.js} +4 -4
  3. package/dist/{channel-2WHBRDTD.js → channel-72YET5JC.js} +2 -2
  4. package/dist/{chunk-QF22MYDJ.js → chunk-BJDLYTPS.js} +1 -1
  5. package/dist/{chunk-46S7YHUB.js → chunk-EN3NHRQC.js} +12 -19
  6. package/dist/{chunk-DP2DX4WV.js → chunk-ESTOWEG2.js} +12 -3
  7. package/dist/{chunk-N6MLQ26B.js → chunk-GPZCPGV3.js} +11 -1
  8. package/dist/{chunk-ZKNBD5P3.js → chunk-IELXXS7E.js} +2 -2
  9. package/dist/{chunk-STOEJOJO.js → chunk-J3IHIXDB.js} +1 -1
  10. package/dist/{chunk-R3VB7NF5.js → chunk-KYHC7LHS.js} +2 -2
  11. package/dist/{chunk-YY2QX2J6.js → chunk-LG4ROCHN.js} +1 -1
  12. package/dist/{chunk-YTOPX4PB.js → chunk-NQKKTRET.js} +28 -37
  13. package/dist/{chunk-D5EVQTGJ.js → chunk-PEQQ7MRI.js} +2 -2
  14. package/dist/{chunk-LIPPXNIE.js → chunk-PTK3GBCG.js} +17 -18
  15. package/dist/{chunk-RGWADNLT.js → chunk-RH3XLDY2.js} +2 -2
  16. package/dist/{chunk-WTJI3JVR.js → chunk-VRAOTXDF.js} +9 -6
  17. package/dist/chunk-XUA3JUFK.js +121 -0
  18. package/dist/cli.js +26 -17
  19. package/dist/{connector-L2HBLZBW.js → connector-Z5KYVTZ5.js} +2 -2
  20. package/dist/connectors/discord.js +2 -2
  21. package/dist/connectors/slack.js +2 -2
  22. package/dist/connectors/telegram.js +2 -2
  23. package/dist/{create-VBZZNJOG.js → create-AAI52BC2.js} +1 -1
  24. package/dist/{daemon-client-P44NU3KU.js → daemon-client-WXN43USO.js} +2 -2
  25. package/dist/{daemon-restart-5W5AGBZ2.js → daemon-restart-4OXIGWV6.js} +6 -5
  26. package/dist/daemon.js +533 -557
  27. package/dist/{delete-BOTVU4YO.js → delete-BJ3LNU2I.js} +1 -1
  28. package/dist/down-IMZE7V42.js +14 -0
  29. package/dist/{env-CGORIKVF.js → env-EOO2C7L7.js} +2 -2
  30. package/dist/{history-NI5QP27M.js → history-J7TURCZS.js} +2 -2
  31. package/dist/{import-2BZUWT23.js → import-TKF67X4R.js} +3 -3
  32. package/dist/{logs-APWVWGNX.js → logs-KHBOS6IZ.js} +2 -2
  33. package/dist/{package-KVUXPTEW.js → package-TPXKJXLG.js} +1 -1
  34. package/dist/{restart-CCYM3MEC.js → restart-CMP63H6A.js} +2 -2
  35. package/dist/{schedule-E4MFGYSA.js → schedule-YMAJZ52M.js} +2 -2
  36. package/dist/seed-M6QPHFTV.js +68 -0
  37. package/dist/{send-X6OQGSD6.js → send-EZYEIVMA.js} +18 -6
  38. package/dist/{service-UL3OCODG.js → service-DDFZA7Q3.js} +4 -3
  39. package/dist/{setup-7N4KYOYN.js → setup-DCZWBBD7.js} +7 -7
  40. package/dist/sprout-EAWETTWZ.js +89 -0
  41. package/dist/{start-6YRS6FF6.js → start-54JXJ7VV.js} +2 -2
  42. package/dist/{status-DFWM342I.js → status-PMMS4XUH.js} +7 -5
  43. package/dist/{stop-UQSNF4CG.js → stop-WXU42NWP.js} +2 -2
  44. package/dist/{up-365HL7UT.js → up-N2OWDCT6.js} +5 -4
  45. package/dist/{update-PV3XM6DX.js → update-FXLGIIWH.js} +5 -4
  46. package/dist/{update-check-YPGH5X4E.js → update-check-NBHTSTHK.js} +2 -2
  47. package/dist/{upgrade-RSE4CZNE.js → upgrade-TEI7N6CQ.js} +1 -1
  48. package/dist/{variant-7IZF6OWO.js → variant-ZCZS3JAP.js} +4 -4
  49. package/dist/web-assets/assets/index-TqXd1QOX.js +307 -0
  50. package/dist/web-assets/index.html +1 -1
  51. package/package.json +1 -1
  52. package/templates/_base/_skills/orientation/SKILL.md +58 -0
  53. package/templates/_base/home/.config/config.json.tmpl +3 -0
  54. package/templates/_base/src/lib/startup.ts +8 -4
  55. package/templates/agent-sdk/volute-template.json +1 -1
  56. package/templates/pi/home/.config/config.json.tmpl +3 -0
  57. package/templates/pi/volute-template.json +1 -1
  58. package/dist/down-O2EQJ5DO.js +0 -13
  59. package/dist/web-assets/assets/index-D-3zx6vs.js +0 -307
  60. package/templates/_base/home/.config/volute.json.tmpl +0 -3
  61. package/templates/pi/home/.config/volute.json.tmpl +0 -3
@@ -6,42 +6,42 @@ async function run(args) {
6
6
  const subcommand = args[0];
7
7
  switch (subcommand) {
8
8
  case "create":
9
- await import("./create-VBZZNJOG.js").then((m) => m.run(args.slice(1)));
9
+ await import("./create-AAI52BC2.js").then((m) => m.run(args.slice(1)));
10
10
  break;
11
11
  case "start":
12
- await import("./start-6YRS6FF6.js").then((m) => m.run(args.slice(1)));
12
+ await import("./start-54JXJ7VV.js").then((m) => m.run(args.slice(1)));
13
13
  break;
14
14
  case "stop":
15
- await import("./stop-UQSNF4CG.js").then((m) => m.run(args.slice(1)));
15
+ await import("./stop-WXU42NWP.js").then((m) => m.run(args.slice(1)));
16
16
  break;
17
17
  case "restart":
18
- await import("./restart-CCYM3MEC.js").then((m) => m.run(args.slice(1)));
18
+ await import("./restart-CMP63H6A.js").then((m) => m.run(args.slice(1)));
19
19
  break;
20
20
  case "delete":
21
- await import("./delete-BOTVU4YO.js").then((m) => m.run(args.slice(1)));
21
+ await import("./delete-BJ3LNU2I.js").then((m) => m.run(args.slice(1)));
22
22
  break;
23
23
  case "list":
24
- await import("./status-DFWM342I.js").then((m) => m.run(args.slice(1)));
24
+ await import("./status-PMMS4XUH.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-DFWM342I.js").then((m) => m.run(rest));
31
+ await import("./status-PMMS4XUH.js").then((m) => m.run(rest));
32
32
  break;
33
33
  }
34
34
  case "logs": {
35
35
  const rest = args.slice(1);
36
36
  const logsArgs = transformAgentFlag(rest);
37
- await import("./logs-APWVWGNX.js").then((m) => m.run(logsArgs));
37
+ await import("./logs-KHBOS6IZ.js").then((m) => m.run(logsArgs));
38
38
  break;
39
39
  }
40
40
  case "upgrade":
41
- await import("./upgrade-RSE4CZNE.js").then((m) => m.run(args.slice(1)));
41
+ await import("./upgrade-TEI7N6CQ.js").then((m) => m.run(args.slice(1)));
42
42
  break;
43
43
  case "import":
44
- await import("./import-2BZUWT23.js").then((m) => m.run(args.slice(1)));
44
+ await import("./import-TKF67X4R.js").then((m) => m.run(args.slice(1)));
45
45
  break;
46
46
  case "--help":
47
47
  case "-h":
@@ -3,10 +3,10 @@ import {
3
3
  AgentManager,
4
4
  getAgentManager,
5
5
  initAgentManager
6
- } from "./chunk-YTOPX4PB.js";
7
- import "./chunk-46S7YHUB.js";
8
- import "./chunk-QF22MYDJ.js";
9
- import "./chunk-DP2DX4WV.js";
6
+ } from "./chunk-NQKKTRET.js";
7
+ import "./chunk-BJDLYTPS.js";
8
+ import "./chunk-EN3NHRQC.js";
9
+ import "./chunk-ESTOWEG2.js";
10
10
  import "./chunk-K3NQKI34.js";
11
11
  export {
12
12
  AgentManager,
@@ -7,8 +7,8 @@ import {
7
7
  } from "./chunk-D424ZQGI.js";
8
8
  import {
9
9
  daemonFetch
10
- } from "./chunk-STOEJOJO.js";
11
- import "./chunk-DP2DX4WV.js";
10
+ } from "./chunk-J3IHIXDB.js";
11
+ import "./chunk-ESTOWEG2.js";
12
12
  import {
13
13
  getClient,
14
14
  urlOf
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  stateDir,
4
4
  voluteHome
5
- } from "./chunk-DP2DX4WV.js";
5
+ } from "./chunk-ESTOWEG2.js";
6
6
 
7
7
  // src/lib/env.ts
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  validateAgentName
4
- } from "./chunk-DP2DX4WV.js";
4
+ } from "./chunk-ESTOWEG2.js";
5
5
 
6
6
  // src/lib/isolation.ts
7
- import { execFile, execFileSync } from "child_process";
8
- import { promisify } from "util";
9
- var execFileAsync = promisify(execFile);
7
+ import { execFileSync } from "child_process";
10
8
  function isIsolationEnabled() {
11
9
  return process.env.VOLUTE_ISOLATION === "user";
12
10
  }
@@ -29,7 +27,7 @@ function ensureVoluteGroup(opts) {
29
27
  }
30
28
  }
31
29
  }
32
- function createAgentUser(name) {
30
+ function createAgentUser(name, homeDir) {
33
31
  if (!isIsolationEnabled()) return;
34
32
  const user = agentUserName(name);
35
33
  try {
@@ -38,7 +36,10 @@ function createAgentUser(name) {
38
36
  } catch {
39
37
  }
40
38
  try {
41
- execFileSync("useradd", ["-r", "-M", "-G", "volute", "-s", "/usr/sbin/nologin", user], {
39
+ const args = ["-r", "-M", "-G", "volute", "-s", "/usr/sbin/nologin"];
40
+ if (homeDir) args.push("-d", homeDir);
41
+ args.push(user);
42
+ execFileSync("useradd", args, {
42
43
  stdio: ["ignore", "ignore", "pipe"]
43
44
  });
44
45
  } catch (err) {
@@ -54,18 +55,11 @@ function deleteAgentUser(name) {
54
55
  } catch {
55
56
  }
56
57
  }
57
- async function getAgentUserIds(name) {
58
- const user = agentUserName(name);
59
- const { stdout: uidStr } = await execFileAsync("id", ["-u", user]);
60
- const { stdout: gidStr } = await execFileAsync("id", ["-g", user]);
61
- return { uid: parseInt(uidStr.trim(), 10), gid: parseInt(gidStr.trim(), 10) };
62
- }
63
- async function applyIsolation(spawnOpts, agentName) {
64
- if (!isIsolationEnabled()) return;
58
+ function wrapForIsolation(cmd, args, agentName) {
59
+ if (!isIsolationEnabled()) return [cmd, args];
65
60
  const baseName = agentName.split("@", 2)[0];
66
- const { uid, gid } = await getAgentUserIds(baseName);
67
- spawnOpts.uid = uid;
68
- spawnOpts.gid = gid;
61
+ const user = agentUserName(baseName);
62
+ return ["runuser", ["-u", user, "--", cmd, ...args]];
69
63
  }
70
64
  function chownAgentDir(dir, name) {
71
65
  if (!isIsolationEnabled()) return;
@@ -89,7 +83,6 @@ export {
89
83
  ensureVoluteGroup,
90
84
  createAgentUser,
91
85
  deleteAgentUser,
92
- getAgentUserIds,
93
- applyIsolation,
86
+ wrapForIsolation,
94
87
  chownAgentDir
95
88
  };
@@ -28,7 +28,7 @@ function readRegistry() {
28
28
  if (!existsSync(registryPath)) return [];
29
29
  try {
30
30
  const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
31
- return entries.map((e) => ({ ...e, running: e.running ?? false }));
31
+ return entries.map((e) => ({ ...e, running: e.running ?? false, stage: e.stage ?? "mind" }));
32
32
  } catch {
33
33
  return [];
34
34
  }
@@ -52,12 +52,12 @@ function validateAgentName(name) {
52
52
  }
53
53
  return null;
54
54
  }
55
- function addAgent(name, port) {
55
+ function addAgent(name, port, stage) {
56
56
  const err = validateAgentName(name);
57
57
  if (err) throw new Error(err);
58
58
  const entries = readRegistry();
59
59
  const filtered = entries.filter((e) => e.name !== name);
60
- filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false });
60
+ filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false, stage });
61
61
  writeRegistry(filtered);
62
62
  }
63
63
  function removeAgent(name) {
@@ -72,6 +72,14 @@ function setAgentRunning(name, running) {
72
72
  writeRegistry(entries);
73
73
  }
74
74
  }
75
+ function setAgentStage(name, stage) {
76
+ const entries = readRegistry();
77
+ const entry = entries.find((e) => e.name === name);
78
+ if (entry) {
79
+ entry.stage = stage;
80
+ writeRegistry(entries);
81
+ }
82
+ }
75
83
  function findAgent(name) {
76
84
  return readRegistry().find((e) => e.name === name);
77
85
  }
@@ -237,6 +245,7 @@ export {
237
245
  addAgent,
238
246
  removeAgent,
239
247
  setAgentRunning,
248
+ setAgentStage,
240
249
  findAgent,
241
250
  agentDir,
242
251
  stateDir,
@@ -1,12 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  stateDir
4
- } from "./chunk-DP2DX4WV.js";
4
+ } from "./chunk-ESTOWEG2.js";
5
5
 
6
6
  // src/lib/slugify.ts
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,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execInherit
4
- } from "./chunk-WTJI3JVR.js";
4
+ } from "./chunk-VRAOTXDF.js";
5
5
  import {
6
6
  voluteHome
7
- } from "./chunk-DP2DX4WV.js";
7
+ } from "./chunk-ESTOWEG2.js";
8
8
 
9
9
  // src/lib/service-mode.ts
10
10
  import { execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-DP2DX4WV.js";
4
+ } from "./chunk-ESTOWEG2.js";
5
5
 
6
6
  // src/lib/daemon-client.ts
7
7
  import { existsSync, readFileSync } from "fs";
@@ -3,7 +3,7 @@ import {
3
3
  agentEnvPath,
4
4
  readEnv,
5
5
  writeEnv
6
- } from "./chunk-QF22MYDJ.js";
6
+ } from "./chunk-BJDLYTPS.js";
7
7
  import {
8
8
  parseArgs
9
9
  } from "./chunk-D424ZQGI.js";
@@ -43,7 +43,7 @@ async function run(args) {
43
43
  template: { type: "string" }
44
44
  });
45
45
  const wsDir = resolveWorkspace(positional[0]);
46
- const { daemonFetch } = await import("./daemon-client-P44NU3KU.js");
46
+ const { daemonFetch } = await import("./daemon-client-WXN43USO.js");
47
47
  const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
48
48
  const client = getClient();
49
49
  const res = await daemonFetch(urlOf(client.api.agents.import.$url()), {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-DP2DX4WV.js";
4
+ } from "./chunk-ESTOWEG2.js";
5
5
 
6
6
  // src/lib/update-check.ts
7
7
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- applyIsolation,
4
- chownAgentDir,
5
- isIsolationEnabled
6
- } from "./chunk-46S7YHUB.js";
7
2
  import {
8
3
  loadMergedEnv
9
- } from "./chunk-QF22MYDJ.js";
4
+ } from "./chunk-BJDLYTPS.js";
5
+ import {
6
+ chownAgentDir,
7
+ isIsolationEnabled,
8
+ wrapForIsolation
9
+ } from "./chunk-EN3NHRQC.js";
10
10
  import {
11
11
  agentDir,
12
12
  findAgent,
@@ -15,11 +15,11 @@ import {
15
15
  setVariantRunning,
16
16
  stateDir,
17
17
  voluteHome
18
- } from "./chunk-DP2DX4WV.js";
18
+ } from "./chunk-ESTOWEG2.js";
19
19
 
20
20
  // src/lib/agent-manager.ts
21
21
  import { execFile, spawn } from "child_process";
22
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
22
+ import { chmodSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
23
23
  import { resolve } from "path";
24
24
  import { promisify } from "util";
25
25
 
@@ -188,7 +188,13 @@ var AgentManager = class {
188
188
  const logsDir = resolve(agentStateDir, "logs");
189
189
  mkdirSync(logsDir, { recursive: true });
190
190
  if (isIsolationEnabled()) {
191
- chownAgentDir(agentStateDir, baseName);
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
+ }
192
198
  }
193
199
  const logStream = new RotatingLog(resolve(logsDir, "agent.log"));
194
200
  const agentEnv = loadMergedEnv(name);
@@ -200,43 +206,24 @@ var AgentManager = class {
200
206
  VOLUTE_AGENT_DIR: dir,
201
207
  VOLUTE_AGENT_PORT: String(port)
202
208
  };
203
- if (isIsolationEnabled() && process.env.CLAUDE_CONFIG_DIR) {
204
- const agentClaudeDir = resolve(dir, ".claude");
205
- try {
206
- mkdirSync(agentClaudeDir, { recursive: true });
207
- } catch (err) {
208
- throw new Error(
209
- `Cannot start agent ${name}: failed to create config directory at ${agentClaudeDir}: ${err instanceof Error ? err.message : err}`
210
- );
211
- }
212
- const sharedCreds = resolve(process.env.CLAUDE_CONFIG_DIR, ".credentials.json");
213
- const agentCreds = resolve(agentClaudeDir, ".credentials.json");
214
- if (existsSync3(sharedCreds)) {
215
- try {
216
- writeFileSync2(agentCreds, readFileSync2(sharedCreds));
217
- } catch (err) {
218
- throw new Error(
219
- `Cannot start agent ${name}: failed to copy credentials to ${agentClaudeDir}: ${err instanceof Error ? err.message : err}`
220
- );
221
- }
222
- } else {
223
- console.warn(
224
- `[daemon] shared credentials not found at ${sharedCreds} for agent ${name}. Copy ~/.claude/.credentials.json to ${process.env.CLAUDE_CONFIG_DIR}/.credentials.json or set ANTHROPIC_API_KEY in the agent's environment.`
225
- );
209
+ if (isIsolationEnabled()) {
210
+ env.HOME = resolve(dir, "home");
211
+ if (process.env.CLAUDE_CONFIG_DIR) {
212
+ const projectsDir = resolve(process.env.CLAUDE_CONFIG_DIR, "projects");
213
+ mkdirSync(projectsDir, { recursive: true });
214
+ chmodSync(projectsDir, 1528);
226
215
  }
227
- const baseName2 = name.split("@", 2)[0];
228
- chownAgentDir(agentClaudeDir, baseName2);
229
- env.CLAUDE_CONFIG_DIR = agentClaudeDir;
230
216
  }
231
217
  const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
218
+ const tsxArgs = ["src/server.ts", "--port", String(port)];
219
+ const [spawnCmd, spawnArgs] = wrapForIsolation(tsxBin, tsxArgs, name);
232
220
  const spawnOpts = {
233
221
  cwd: dir,
234
222
  stdio: ["ignore", "pipe", "pipe"],
235
223
  detached: true,
236
224
  env
237
225
  };
238
- await applyIsolation(spawnOpts, name);
239
- const child = spawn(tsxBin, ["src/server.ts", "--port", String(port)], spawnOpts);
226
+ const child = spawn(spawnCmd, spawnArgs, spawnOpts);
240
227
  this.agents.set(name, { child, port });
241
228
  child.stdout?.pipe(logStream);
242
229
  child.stderr?.pipe(logStream);
@@ -299,6 +286,10 @@ var AgentManager = class {
299
286
  const parts = [];
300
287
  if (context.type === "merge" || context.type === "merged") {
301
288
  parts.push(`[system] Variant "${context.name}" has been merged and you have been restarted.`);
289
+ } else if (context.type === "sprouted") {
290
+ parts.push(
291
+ "[system] You've sprouted. You now have full agent capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details."
292
+ );
302
293
  } else {
303
294
  parts.push("[system] You have been restarted.");
304
295
  }
@@ -4,13 +4,13 @@ import {
4
4
  modeLabel,
5
5
  pollHealth,
6
6
  startService
7
- } from "./chunk-ZKNBD5P3.js";
7
+ } from "./chunk-IELXXS7E.js";
8
8
  import {
9
9
  parseArgs
10
10
  } from "./chunk-D424ZQGI.js";
11
11
  import {
12
12
  voluteHome
13
- } from "./chunk-DP2DX4WV.js";
13
+ } from "./chunk-ESTOWEG2.js";
14
14
 
15
15
  // src/commands/up.ts
16
16
  import { spawn } from "child_process";
@@ -1,13 +1,14 @@
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-GPZCPGV3.js";
8
9
  import {
9
10
  voluteHome
10
- } from "./chunk-DP2DX4WV.js";
11
+ } from "./chunk-ESTOWEG2.js";
11
12
  import {
12
13
  __export
13
14
  } from "./chunk-K3NQKI34.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
@@ -5,10 +5,10 @@ import {
5
5
  pollHealthDown,
6
6
  readDaemonConfig,
7
7
  stopService
8
- } from "./chunk-ZKNBD5P3.js";
8
+ } from "./chunk-IELXXS7E.js";
9
9
  import {
10
10
  voluteHome
11
- } from "./chunk-DP2DX4WV.js";
11
+ } from "./chunk-ESTOWEG2.js";
12
12
 
13
13
  // src/commands/down.ts
14
14
  import { existsSync, readFileSync, unlinkSync } from "fs";
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ wrapForIsolation
4
+ } from "./chunk-EN3NHRQC.js";
2
5
 
3
6
  // src/lib/exec.ts
4
7
  import { execFile as execFileCb, execFileSync, spawn } from "child_process";
5
8
  function exec(cmd, args, options) {
9
+ const [wrappedCmd, wrappedArgs] = options?.agentName ? wrapForIsolation(cmd, args, options.agentName) : [cmd, args];
6
10
  return new Promise((resolve, reject) => {
7
11
  execFileCb(
8
- cmd,
9
- args,
10
- { cwd: options?.cwd, uid: options?.uid, gid: options?.gid, env: options?.env },
12
+ wrappedCmd,
13
+ wrappedArgs,
14
+ { cwd: options?.cwd, env: options?.env },
11
15
  (err, stdout, stderr) => {
12
16
  if (err) {
13
17
  err.stderr = stderr;
@@ -31,11 +35,10 @@ function resolveVoluteBin() {
31
35
  }
32
36
  }
33
37
  function execInherit(cmd, args, options) {
38
+ const [wrappedCmd, wrappedArgs] = options?.agentName ? wrapForIsolation(cmd, args, options.agentName) : [cmd, args];
34
39
  return new Promise((resolve, reject) => {
35
- const child = spawn(cmd, args, {
40
+ const child = spawn(wrappedCmd, wrappedArgs, {
36
41
  cwd: options?.cwd,
37
- uid: options?.uid,
38
- gid: options?.gid,
39
42
  env: options?.env,
40
43
  stdio: "inherit"
41
44
  });
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/template.ts
4
+ import {
5
+ cpSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ readFileSync,
10
+ renameSync,
11
+ rmSync,
12
+ statSync,
13
+ writeFileSync
14
+ } from "fs";
15
+ import { tmpdir } from "os";
16
+ import { dirname, join, relative, resolve } from "path";
17
+ function findTemplatesRoot() {
18
+ let dir = dirname(new URL(import.meta.url).pathname);
19
+ for (let i = 0; i < 5; i++) {
20
+ const candidate = resolve(dir, "templates");
21
+ if (existsSync(resolve(candidate, "_base"))) return candidate;
22
+ dir = dirname(dir);
23
+ }
24
+ console.error(
25
+ "Templates directory not found. Searched up from:",
26
+ dirname(new URL(import.meta.url).pathname)
27
+ );
28
+ process.exit(1);
29
+ }
30
+ function composeTemplate(templatesRoot, templateName) {
31
+ const baseDir = resolve(templatesRoot, "_base");
32
+ const templateDir = resolve(templatesRoot, templateName);
33
+ if (!existsSync(baseDir)) {
34
+ console.error("Base template not found:", baseDir);
35
+ process.exit(1);
36
+ }
37
+ if (!existsSync(templateDir)) {
38
+ console.error(`Template not found: ${templateName}`);
39
+ process.exit(1);
40
+ }
41
+ const composedDir = resolve(tmpdir(), `volute-template-${Date.now()}`);
42
+ mkdirSync(composedDir, { recursive: true });
43
+ cpSync(baseDir, composedDir, { recursive: true });
44
+ for (const file of listFiles(templateDir)) {
45
+ const src = resolve(templateDir, file);
46
+ const dest = resolve(composedDir, file);
47
+ mkdirSync(dirname(dest), { recursive: true });
48
+ cpSync(src, dest);
49
+ }
50
+ const manifestPath = resolve(composedDir, "volute-template.json");
51
+ if (!existsSync(manifestPath)) {
52
+ rmSync(composedDir, { recursive: true, force: true });
53
+ console.error(`Template manifest not found: ${templateName}/volute-template.json`);
54
+ process.exit(1);
55
+ }
56
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
57
+ const skillsSrc = resolve(composedDir, "_skills");
58
+ if (existsSync(skillsSrc)) {
59
+ const skillsDest = resolve(composedDir, manifest.skillsDir);
60
+ mkdirSync(skillsDest, { recursive: true });
61
+ cpSync(skillsSrc, skillsDest, { recursive: true });
62
+ rmSync(skillsSrc, { recursive: true, force: true });
63
+ }
64
+ rmSync(manifestPath);
65
+ return { composedDir, manifest };
66
+ }
67
+ function copyTemplateToDir(composedDir, destDir, agentName, manifest) {
68
+ cpSync(composedDir, destDir, { recursive: true });
69
+ for (const [from, to] of Object.entries(manifest.rename)) {
70
+ const fromPath = resolve(destDir, from);
71
+ if (existsSync(fromPath)) {
72
+ renameSync(fromPath, resolve(destDir, to));
73
+ }
74
+ }
75
+ for (const file of manifest.substitute) {
76
+ const path = resolve(destDir, file);
77
+ if (existsSync(path)) {
78
+ const content = readFileSync(path, "utf-8");
79
+ writeFileSync(path, content.replaceAll("{{name}}", agentName));
80
+ }
81
+ }
82
+ }
83
+ function applyInitFiles(destDir) {
84
+ const initDir = resolve(destDir, ".init");
85
+ if (!existsSync(initDir)) return;
86
+ const homeDir = resolve(destDir, "home");
87
+ for (const file of listFiles(initDir)) {
88
+ const src = resolve(initDir, file);
89
+ const dest = resolve(homeDir, file);
90
+ const parent = dirname(dest);
91
+ if (!existsSync(parent)) {
92
+ mkdirSync(parent, { recursive: true });
93
+ }
94
+ cpSync(src, dest);
95
+ }
96
+ rmSync(initDir, { recursive: true, force: true });
97
+ }
98
+ function listFiles(dir) {
99
+ const results = [];
100
+ function walk(current) {
101
+ for (const entry of readdirSync(current)) {
102
+ const full = join(current, entry);
103
+ if (statSync(full).isDirectory()) {
104
+ if (entry === ".git") continue;
105
+ walk(full);
106
+ } else {
107
+ results.push(relative(dir, full));
108
+ }
109
+ }
110
+ }
111
+ walk(dir);
112
+ return results;
113
+ }
114
+
115
+ export {
116
+ findTemplatesRoot,
117
+ composeTemplate,
118
+ copyTemplateToDir,
119
+ applyInitFiles,
120
+ listFiles
121
+ };