volute 0.5.0 → 0.7.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 (62) hide show
  1. package/dist/{agent-Z2B6EFEQ.js → agent-7JF7MT73.js} +13 -9
  2. package/dist/{agent-manager-PXBKA2GK.js → agent-manager-IMZ7ZMBF.js} +4 -4
  3. package/dist/channel-SMCNOIVQ.js +262 -0
  4. package/dist/{chunk-MW2KFO3B.js → chunk-62X577Y7.js} +10 -8
  5. package/dist/chunk-7ACDT3P2.js +265 -0
  6. package/dist/{chunk-MXUCNIBG.js → chunk-BX7KI4S3.js} +68 -3
  7. package/dist/{up-7ILD7GU7.js → chunk-EG45HBSJ.js} +16 -4
  8. package/dist/{chunk-HE67X4T6.js → chunk-H7AMDUIA.js} +1 -1
  9. package/dist/{chunk-7L4AN5D4.js → chunk-JR4UXCTO.js} +1 -1
  10. package/dist/{down-O7IFZLVJ.js → chunk-LLJNZPCU.js} +48 -13
  11. package/dist/{chunk-5X7HGB6L.js → chunk-NKXULRSW.js} +2 -1
  12. package/dist/{chunk-UX25Z2ND.js → chunk-UWHWAPGO.js} +7 -0
  13. package/dist/{chunk-UAVD2AHX.js → chunk-W76KWE23.js} +1 -1
  14. package/dist/chunk-ZZOOTYXK.js +583 -0
  15. package/dist/cli.js +22 -21
  16. package/dist/{connector-LYEMXQEV.js → connector-Y7JPNROO.js} +3 -3
  17. package/dist/connectors/discord.js +38 -7
  18. package/dist/connectors/slack.js +22 -3
  19. package/dist/connectors/telegram.js +34 -4
  20. package/dist/{create-RVCZN6HE.js → create-G525LWEA.js} +2 -2
  21. package/dist/{daemon-client-ZY6UUN2M.js → daemon-client-442IV43D.js} +2 -2
  22. package/dist/daemon-restart-4HVEKYFY.js +23 -0
  23. package/dist/daemon.js +1042 -809
  24. package/dist/{delete-3QH7VYIN.js → delete-UOU4AFQN.js} +7 -3
  25. package/dist/down-AZVH5TCD.js +11 -0
  26. package/dist/{env-4D4REPJF.js → env-7GLUJCWS.js} +2 -2
  27. package/dist/{history-OEONB53Z.js → history-H72ZUIBN.js} +2 -2
  28. package/dist/{import-MXJB2EII.js → import-AVKQJDYC.js} +2 -2
  29. package/dist/{logs-DF342W4M.js → logs-EDGK26AK.js} +1 -1
  30. package/dist/{message-ADHWFHSI.js → message-SCOQDR3P.js} +2 -2
  31. package/dist/{package-VQOE7JNH.js → package-T2WAVJOU.js} +1 -1
  32. package/dist/restart-O4ETYLJF.js +29 -0
  33. package/dist/{schedule-NAG6F463.js → schedule-S6QVC5ON.js} +2 -2
  34. package/dist/send-G7PE4DOJ.js +72 -0
  35. package/dist/{setup-RPRRGG2F.js → setup-F4TCWVSP.js} +2 -2
  36. package/dist/{start-TUOXDSFL.js → start-VHQ7LNWM.js} +2 -2
  37. package/dist/{status-A36EHRO4.js → status-QAJWXKMZ.js} +2 -2
  38. package/dist/{stop-AOJZLQ5X.js → stop-CAGCT5NI.js} +2 -2
  39. package/dist/up-RWZF6MLT.js +12 -0
  40. package/dist/{update-LPSIAWQ2.js → update-F7QWV2LB.js} +2 -2
  41. package/dist/{update-check-Y33QDCFL.js → update-check-B4J6IEQ4.js} +2 -2
  42. package/dist/{upgrade-FX2TKJ2S.js → upgrade-YXKPWDRU.js} +2 -2
  43. package/dist/{variant-LAB67OC2.js → variant-4Z6W3PP6.js} +2 -2
  44. package/dist/web-assets/assets/index-B1CqjUYD.js +308 -0
  45. package/dist/web-assets/index.html +1 -1
  46. package/package.json +1 -1
  47. package/templates/_base/.init/.config/scripts/session-reader.ts +59 -0
  48. package/templates/_base/_skills/sessions/SKILL.md +49 -0
  49. package/templates/_base/_skills/volute-agent/SKILL.md +13 -9
  50. package/templates/_base/src/lib/format-prefix.ts +6 -0
  51. package/templates/_base/src/lib/router.ts +30 -3
  52. package/templates/_base/src/lib/session-monitor.ts +400 -0
  53. package/templates/_base/src/lib/types.ts +2 -0
  54. package/templates/agent-sdk/src/agent.ts +16 -0
  55. package/templates/agent-sdk/src/lib/hooks/session-context.ts +32 -0
  56. package/templates/pi/src/agent.ts +7 -1
  57. package/templates/pi/src/lib/session-context-extension.ts +33 -0
  58. package/dist/channel-MK5OK2SI.js +0 -113
  59. package/dist/chunk-SMISE4SV.js +0 -226
  60. package/dist/conversation-ERXEQZTY.js +0 -163
  61. package/dist/send-66QMKRUH.js +0 -75
  62. package/dist/web-assets/assets/index-BbRmoxoA.js +0 -308
@@ -5,6 +5,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
5
5
  import { createAutoCommitHook } from "./lib/hooks/auto-commit.js";
6
6
  import { createIdentityReloadHook } from "./lib/hooks/identity-reload.js";
7
7
  import { createPreCompactHook } from "./lib/hooks/pre-compact.js";
8
+ import { createSessionContextHook } from "./lib/hooks/session-context.js";
8
9
  import { log, logText, logThinking, logToolUse } from "./lib/logger.js";
9
10
  import { createMessageChannel } from "./lib/message-channel.js";
10
11
  import type {
@@ -136,6 +137,12 @@ export function createAgent(options: {
136
137
  });
137
138
  });
138
139
 
140
+ const sessionContext = createSessionContextHook({
141
+ currentSession: session.name,
142
+ sessionsDir: options.sessionsDir,
143
+ cwd: options.cwd,
144
+ });
145
+
139
146
  return query({
140
147
  prompt: session.channel.iterable,
141
148
  options: {
@@ -150,6 +157,7 @@ export function createAgent(options: {
150
157
  hooks: {
151
158
  PostToolUse: postToolUseHooks,
152
159
  PreCompact: [{ hooks: [preCompact.hook] }],
160
+ UserPromptSubmit: [{ hooks: [sessionContext.hook] }],
153
161
  },
154
162
  },
155
163
  });
@@ -182,6 +190,14 @@ export function createAgent(options: {
182
190
  }
183
191
  if (msg.type === "result") {
184
192
  log("agent", `session "${session.name}": turn done`);
193
+ const result = msg as { usage?: { input_tokens?: number; output_tokens?: number } };
194
+ if (result.usage) {
195
+ broadcastToSession(session, {
196
+ type: "usage",
197
+ input_tokens: result.usage.input_tokens ?? 0,
198
+ output_tokens: result.usage.output_tokens ?? 0,
199
+ });
200
+ }
185
201
  broadcastToSession(session, { type: "done" });
186
202
  session.currentMessageId = undefined;
187
203
  if (identityReload.needsReload()) {
@@ -0,0 +1,32 @@
1
+ import { resolve } from "node:path";
2
+ import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
3
+ import { getSessionUpdates, resolveAgentSdkJsonl } from "../session-monitor.js";
4
+
5
+ export function createSessionContextHook(options: {
6
+ currentSession: string;
7
+ sessionsDir: string;
8
+ cwd: string;
9
+ }) {
10
+ const hook: HookCallback = async () => {
11
+ try {
12
+ const summary = getSessionUpdates({
13
+ currentSession: options.currentSession,
14
+ sessionsDir: options.sessionsDir,
15
+ cursorFile: resolve(options.sessionsDir, "../session-cursors.json"),
16
+ jsonlResolver: (name) => resolveAgentSdkJsonl(options.sessionsDir, name, options.cwd),
17
+ format: "agent-sdk",
18
+ });
19
+ if (!summary) return {};
20
+ return {
21
+ hookSpecificOutput: {
22
+ hookEventName: "UserPromptSubmit" as const,
23
+ additionalContext: summary,
24
+ },
25
+ };
26
+ } catch {
27
+ return {};
28
+ }
29
+ };
30
+
31
+ return { hook };
32
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from "@mariozechner/pi-coding-agent";
12
12
  import { commitFileChange } from "./lib/auto-commit.js";
13
13
  import { log, logText, logThinking, logToolResult, logToolUse } from "./lib/logger.js";
14
+ import { createSessionContextExtension } from "./lib/session-context-extension.js";
14
15
  import type {
15
16
  HandlerMeta,
16
17
  HandlerResolver,
@@ -137,11 +138,16 @@ export function createAgent(options: {
137
138
  retry: { enabled: true, maxRetries: 3 },
138
139
  });
139
140
 
141
+ const sessionContextExtension = createSessionContextExtension({
142
+ currentSession: session.name,
143
+ cwd: options.cwd,
144
+ });
145
+
140
146
  const resourceLoader = new DefaultResourceLoader({
141
147
  cwd: options.cwd,
142
148
  settingsManager,
143
149
  systemPrompt: options.systemPrompt,
144
- extensionFactories: [preCompactExtension],
150
+ extensionFactories: [preCompactExtension, sessionContextExtension],
145
151
  });
146
152
  await resourceLoader.reload();
147
153
 
@@ -0,0 +1,33 @@
1
+ import { resolve } from "node:path";
2
+ import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
3
+ import { getSessionUpdates, resolvePiJsonl } from "./session-monitor.js";
4
+
5
+ export function createSessionContextExtension(options: {
6
+ currentSession: string;
7
+ cwd: string;
8
+ }): ExtensionFactory {
9
+ return (pi) => {
10
+ pi.on("before_agent_start", () => {
11
+ try {
12
+ const sessionsDir = resolve(options.cwd, ".volute/pi-sessions");
13
+ const summary = getSessionUpdates({
14
+ currentSession: options.currentSession,
15
+ sessionsDir,
16
+ cursorFile: resolve(options.cwd, ".volute/session-cursors.json"),
17
+ jsonlResolver: (name) => resolvePiJsonl(sessionsDir, name),
18
+ format: "pi",
19
+ });
20
+ if (!summary) return {};
21
+ return {
22
+ message: {
23
+ customType: "session-update",
24
+ content: summary,
25
+ display: true,
26
+ },
27
+ };
28
+ } catch {
29
+ return {};
30
+ }
31
+ });
32
+ };
33
+ }
@@ -1,113 +0,0 @@
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
- getChannelDriver
10
- } from "./chunk-SMISE4SV.js";
11
- import {
12
- loadMergedEnv
13
- } from "./chunk-HE67X4T6.js";
14
- import {
15
- parseArgs
16
- } from "./chunk-D424ZQGI.js";
17
- import {
18
- resolveAgent
19
- } from "./chunk-UX25Z2ND.js";
20
- import "./chunk-K3NQKI34.js";
21
-
22
- // src/commands/channel.ts
23
- async function run(args) {
24
- const subcommand = args[0];
25
- switch (subcommand) {
26
- case "read":
27
- await readChannel(args.slice(1));
28
- break;
29
- case "send":
30
- await sendChannel(args.slice(1));
31
- break;
32
- case "--help":
33
- case "-h":
34
- case void 0:
35
- printUsage();
36
- break;
37
- default:
38
- printUsage();
39
- process.exit(1);
40
- }
41
- }
42
- function printUsage() {
43
- console.log(`Usage:
44
- volute channel read <channel-uri> [--limit N] [--agent <name>]
45
- volute channel send <channel-uri> "<message>" [--agent <name>]
46
- echo "message" | volute channel send <channel-uri> [--agent <name>]`);
47
- }
48
- async function readChannel(args) {
49
- const { positional, flags } = parseArgs(args, {
50
- agent: { type: "string" },
51
- limit: { type: "number" }
52
- });
53
- const uri = positional[0];
54
- if (!uri) {
55
- console.error("Usage: volute channel read <channel-uri> [--limit N] [--agent <name>]");
56
- process.exit(1);
57
- }
58
- const agentName = resolveAgentName(flags);
59
- const { platform, channelId } = parseUri(uri);
60
- const driver = requireDriver(platform);
61
- const { dir } = resolveAgent(agentName);
62
- const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName };
63
- try {
64
- const limit = flags.limit ?? 20;
65
- const output = await driver.read(env, channelId, limit);
66
- console.log(output);
67
- } catch (err) {
68
- console.error(err instanceof Error ? err.message : String(err));
69
- process.exit(1);
70
- }
71
- }
72
- async function sendChannel(args) {
73
- const { positional, flags } = parseArgs(args, {
74
- agent: { type: "string" }
75
- });
76
- const uri = positional[0];
77
- const message = positional[1] ?? await readStdin();
78
- if (!uri || !message) {
79
- console.error('Usage: volute channel send <channel-uri> "<message>" [--agent <name>]');
80
- console.error(' echo "message" | volute channel send <channel-uri> [--agent <name>]');
81
- process.exit(1);
82
- }
83
- const agentName = resolveAgentName(flags);
84
- const { platform, channelId } = parseUri(uri);
85
- const driver = requireDriver(platform);
86
- const { dir } = resolveAgent(agentName);
87
- const env = { ...loadMergedEnv(dir), VOLUTE_AGENT: agentName };
88
- try {
89
- await driver.send(env, channelId, message);
90
- } catch (err) {
91
- console.error(err instanceof Error ? err.message : String(err));
92
- process.exit(1);
93
- }
94
- }
95
- function parseUri(uri) {
96
- const colonIdx = uri.indexOf(":");
97
- if (colonIdx === -1) {
98
- console.error(`Invalid channel URI: ${uri} (expected format: platform:id)`);
99
- process.exit(1);
100
- }
101
- return { platform: uri.slice(0, colonIdx), channelId: uri.slice(colonIdx + 1) };
102
- }
103
- function requireDriver(platform) {
104
- const driver = getChannelDriver(platform);
105
- if (!driver) {
106
- console.error(`No channel driver for platform: ${platform}`);
107
- process.exit(1);
108
- }
109
- return driver;
110
- }
111
- export {
112
- run
113
- };
@@ -1,226 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- voluteHome
4
- } from "./chunk-UX25Z2ND.js";
5
- import {
6
- __export
7
- } from "./chunk-K3NQKI34.js";
8
-
9
- // src/lib/channels/discord.ts
10
- var discord_exports = {};
11
- __export(discord_exports, {
12
- read: () => read,
13
- send: () => send
14
- });
15
- var API_BASE = "https://discord.com/api/v10";
16
- function requireToken(env) {
17
- const token = env.DISCORD_TOKEN;
18
- if (!token) throw new Error("DISCORD_TOKEN not set");
19
- return token;
20
- }
21
- async function read(env, channelId, limit) {
22
- const token = requireToken(env);
23
- const res = await fetch(`${API_BASE}/channels/${channelId}/messages?limit=${limit}`, {
24
- headers: { Authorization: `Bot ${token}` }
25
- });
26
- if (!res.ok) {
27
- throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
28
- }
29
- const messages = await res.json();
30
- return messages.reverse().map((m) => `${m.author.username}: ${m.content}`).join("\n");
31
- }
32
- async function send(env, channelId, message) {
33
- const token = requireToken(env);
34
- const res = await fetch(`${API_BASE}/channels/${channelId}/messages`, {
35
- method: "POST",
36
- headers: {
37
- Authorization: `Bot ${token}`,
38
- "Content-Type": "application/json"
39
- },
40
- body: JSON.stringify({ content: message })
41
- });
42
- if (!res.ok) {
43
- throw new Error(`Discord API error: ${res.status} ${res.statusText}`);
44
- }
45
- }
46
-
47
- // src/lib/channels/slack.ts
48
- var slack_exports = {};
49
- __export(slack_exports, {
50
- read: () => read2,
51
- send: () => send2
52
- });
53
- var API_BASE2 = "https://slack.com/api";
54
- function requireToken2(env) {
55
- const token = env.SLACK_BOT_TOKEN;
56
- if (!token) throw new Error("SLACK_BOT_TOKEN not set");
57
- return token;
58
- }
59
- async function slackApi(token, method, body) {
60
- const res = await fetch(`${API_BASE2}/${method}`, {
61
- method: "POST",
62
- headers: {
63
- Authorization: `Bearer ${token}`,
64
- "Content-Type": "application/json"
65
- },
66
- body: JSON.stringify(body)
67
- });
68
- if (!res.ok) {
69
- throw new Error(`Slack API HTTP error: ${res.status} ${res.statusText}`);
70
- }
71
- const data = await res.json();
72
- if (!data.ok) {
73
- throw new Error(`Slack API error: ${data.error}`);
74
- }
75
- return data;
76
- }
77
- async function read2(env, channelId, limit) {
78
- const token = requireToken2(env);
79
- const data = await slackApi(token, "conversations.history", {
80
- channel: channelId,
81
- limit
82
- });
83
- return data.messages.reverse().map((m) => `${m.user ?? m.bot_id ?? "unknown"}: ${m.text}`).join("\n");
84
- }
85
- async function send2(env, channelId, message) {
86
- const token = requireToken2(env);
87
- await slackApi(token, "chat.postMessage", {
88
- channel: channelId,
89
- text: message
90
- });
91
- }
92
-
93
- // src/lib/channels/telegram.ts
94
- var telegram_exports = {};
95
- __export(telegram_exports, {
96
- read: () => read3,
97
- send: () => send3
98
- });
99
- var API_BASE3 = "https://api.telegram.org";
100
- function requireToken3(env) {
101
- const token = env.TELEGRAM_BOT_TOKEN;
102
- if (!token) throw new Error("TELEGRAM_BOT_TOKEN not set");
103
- return token;
104
- }
105
- async function read3(_env, _channelId, _limit) {
106
- throw new Error(
107
- "Telegram Bot API does not support reading chat history. Use volute channel send instead."
108
- );
109
- }
110
- async function send3(env, chatId, message) {
111
- const token = requireToken3(env);
112
- const res = await fetch(`${API_BASE3}/bot${token}/sendMessage`, {
113
- method: "POST",
114
- headers: { "Content-Type": "application/json" },
115
- body: JSON.stringify({ chat_id: chatId, text: message })
116
- });
117
- if (!res.ok) {
118
- const body = await res.text().catch(() => "");
119
- throw new Error(`Telegram API error: ${res.status} ${body}`);
120
- }
121
- }
122
-
123
- // src/lib/channels/volute.ts
124
- var volute_exports = {};
125
- __export(volute_exports, {
126
- read: () => read4,
127
- send: () => send4
128
- });
129
- import { existsSync, readFileSync } from "fs";
130
- import { resolve } from "path";
131
- function getDaemonConfig() {
132
- const configPath = resolve(voluteHome(), "daemon.json");
133
- if (!existsSync(configPath)) {
134
- throw new Error("Volute daemon is not running");
135
- }
136
- let config;
137
- try {
138
- config = JSON.parse(readFileSync(configPath, "utf-8"));
139
- } catch (err) {
140
- throw new Error(`Failed to parse ${configPath}: ${err}`);
141
- }
142
- if (typeof config.port !== "number") {
143
- throw new Error(`Invalid or missing port in ${configPath}`);
144
- }
145
- const url = new URL("http://localhost");
146
- url.hostname = config.hostname || "localhost";
147
- url.port = String(config.port);
148
- return { url: url.origin, token: config.token };
149
- }
150
- async function read4(env, conversationId, limit) {
151
- const agentName = env.VOLUTE_AGENT;
152
- if (!agentName) throw new Error("VOLUTE_AGENT not set");
153
- const { url, token } = getDaemonConfig();
154
- const headers = { Origin: url };
155
- if (token) headers.Authorization = `Bearer ${token}`;
156
- const res = await fetch(
157
- `${url}/api/agents/${encodeURIComponent(agentName)}/conversations/${encodeURIComponent(conversationId)}/messages`,
158
- { headers }
159
- );
160
- if (!res.ok) {
161
- throw new Error(`Failed to read conversation: ${res.status} ${res.statusText}`);
162
- }
163
- const messages = await res.json();
164
- return messages.slice(-limit).map((m) => {
165
- const text = Array.isArray(m.content) ? m.content.filter((b) => b.type === "text").map((b) => b.text).join("") : m.content;
166
- return `${m.sender_name ?? m.role}: ${text}`;
167
- }).join("\n");
168
- }
169
- async function send4(env, conversationId, message) {
170
- const agentName = env.VOLUTE_AGENT;
171
- if (!agentName) throw new Error("VOLUTE_AGENT not set");
172
- const { url, token } = getDaemonConfig();
173
- const headers = {
174
- "Content-Type": "application/json",
175
- Origin: url
176
- };
177
- if (token) headers.Authorization = `Bearer ${token}`;
178
- const res = await fetch(`${url}/api/agents/${encodeURIComponent(agentName)}/chat`, {
179
- method: "POST",
180
- headers,
181
- body: JSON.stringify({ message, conversationId, sender: agentName })
182
- });
183
- if (!res.ok) {
184
- const data = await res.json().catch(() => ({}));
185
- throw new Error(data.error ?? `Failed to send: ${res.status}`);
186
- }
187
- if (res.body) {
188
- const reader = res.body.getReader();
189
- while (true) {
190
- const { done } = await reader.read();
191
- if (done) break;
192
- }
193
- }
194
- }
195
-
196
- // src/lib/channels.ts
197
- var CHANNELS = {
198
- volute: { name: "volute", displayName: "Volute", showToolCalls: true, driver: volute_exports },
199
- discord: {
200
- name: "discord",
201
- displayName: "Discord",
202
- showToolCalls: false,
203
- driver: discord_exports
204
- },
205
- slack: {
206
- name: "slack",
207
- displayName: "Slack",
208
- showToolCalls: false,
209
- driver: slack_exports
210
- },
211
- telegram: {
212
- name: "telegram",
213
- displayName: "Telegram",
214
- showToolCalls: false,
215
- driver: telegram_exports
216
- },
217
- system: { name: "system", displayName: "System", showToolCalls: false }
218
- };
219
- function getChannelDriver(platform) {
220
- return CHANNELS[platform]?.driver ?? null;
221
- }
222
-
223
- export {
224
- CHANNELS,
225
- getChannelDriver
226
- };
@@ -1,163 +0,0 @@
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
- summarizeTool
10
- } from "./chunk-B3R6L2GW.js";
11
- import {
12
- parseArgs
13
- } from "./chunk-D424ZQGI.js";
14
- import {
15
- daemonFetch
16
- } from "./chunk-7L4AN5D4.js";
17
- import "./chunk-UX25Z2ND.js";
18
- import "./chunk-K3NQKI34.js";
19
-
20
- // src/commands/conversation.ts
21
- import { userInfo } from "os";
22
- async function run(args) {
23
- const subcommand = args[0];
24
- switch (subcommand) {
25
- case "create":
26
- await createConversation(args.slice(1));
27
- break;
28
- case "list":
29
- await listConversations(args.slice(1));
30
- break;
31
- case "send":
32
- await sendToConversation(args.slice(1));
33
- break;
34
- case "--help":
35
- case "-h":
36
- case void 0:
37
- printUsage();
38
- break;
39
- default:
40
- printUsage();
41
- process.exit(1);
42
- }
43
- }
44
- function printUsage() {
45
- console.log(`Usage:
46
- volute conversation create --participants user1,agent1 [--title "..."] [--agent <name>]
47
- volute conversation list [--agent <name>]
48
- volute conversation send <id> "<message>" [--agent <name>]
49
- echo "message" | volute conversation send <id> [--agent <name>]`);
50
- }
51
- async function createConversation(args) {
52
- const { flags } = parseArgs(args, {
53
- agent: { type: "string" },
54
- participants: { type: "string" },
55
- title: { type: "string" }
56
- });
57
- const agentName = resolveAgentName(flags);
58
- if (!flags.participants) {
59
- console.error("--participants is required (comma-separated usernames)");
60
- process.exit(1);
61
- }
62
- const participantNames = flags.participants.split(",").map((s) => s.trim());
63
- const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/conversations`, {
64
- method: "POST",
65
- headers: { "Content-Type": "application/json" },
66
- body: JSON.stringify({ participantNames, title: flags.title })
67
- });
68
- if (!res.ok) {
69
- const data = await res.json();
70
- console.error(data.error ?? `Failed to create conversation: ${res.status}`);
71
- process.exit(1);
72
- }
73
- const conv = await res.json();
74
- console.log(`Created conversation: ${conv.id}`);
75
- if (conv.title) console.log(`Title: ${conv.title}`);
76
- }
77
- async function listConversations(args) {
78
- const { flags } = parseArgs(args, {
79
- agent: { type: "string" }
80
- });
81
- const agentName = resolveAgentName(flags);
82
- const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/conversations`);
83
- if (!res.ok) {
84
- const data = await res.json();
85
- console.error(data.error ?? `Failed to list conversations: ${res.status}`);
86
- process.exit(1);
87
- }
88
- const convs = await res.json();
89
- if (convs.length === 0) {
90
- console.log("No conversations.");
91
- return;
92
- }
93
- for (const conv of convs) {
94
- const title = conv.title || "(untitled)";
95
- console.log(`${conv.id} ${title} ${conv.updated_at}`);
96
- }
97
- }
98
- async function sendToConversation(args) {
99
- const { positional, flags } = parseArgs(args, {
100
- agent: { type: "string" }
101
- });
102
- const conversationId = positional[0];
103
- const message = positional[1] ?? await readStdin();
104
- if (!conversationId || !message) {
105
- console.error('Usage: volute conversation send <id> "<message>" [--agent <name>]');
106
- console.error(' echo "message" | volute conversation send <id> [--agent <name>]');
107
- process.exit(1);
108
- }
109
- const agentName = resolveAgentName(flags);
110
- const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/chat`, {
111
- method: "POST",
112
- headers: { "Content-Type": "application/json" },
113
- body: JSON.stringify({
114
- message,
115
- conversationId,
116
- sender: process.env.VOLUTE_AGENT || userInfo().username
117
- })
118
- });
119
- if (!res.ok) {
120
- const data = await res.json();
121
- console.error(data.error ?? `Failed to send message: ${res.status}`);
122
- process.exit(1);
123
- }
124
- if (!res.body) {
125
- console.error("No response body");
126
- process.exit(1);
127
- }
128
- const reader = res.body.getReader();
129
- const decoder = new TextDecoder();
130
- let buffer = "";
131
- while (true) {
132
- const { done, value } = await reader.read();
133
- if (done) break;
134
- buffer += decoder.decode(value, { stream: true });
135
- const lines = buffer.split("\n");
136
- buffer = lines.pop() ?? "";
137
- for (const line of lines) {
138
- if (!line.startsWith("data:")) continue;
139
- const data = line.slice(5).trim();
140
- if (!data) continue;
141
- let event;
142
- try {
143
- event = JSON.parse(data);
144
- } catch {
145
- continue;
146
- }
147
- if (event.type === "text") {
148
- process.stdout.write(event.content);
149
- } else if (event.type === "tool_use") {
150
- process.stderr.write(`${summarizeTool(event.name, event.input)}
151
- `);
152
- }
153
- if (event.type === "done") {
154
- process.stdout.write("\n");
155
- return;
156
- }
157
- }
158
- }
159
- process.stdout.write("\n");
160
- }
161
- export {
162
- run
163
- };