volute 0.2.1 → 0.3.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.
- package/README.md +46 -0
- package/dist/agent-manager-2LU6KULR.js +15 -0
- package/dist/{channel-2WJRM7PE.js → channel-H7N4SGR2.js} +7 -7
- package/dist/{chunk-XZN4WPNC.js → chunk-5SKQ6J7T.js} +9 -1
- package/dist/chunk-DEUAVGSA.js +81 -0
- package/dist/{chunk-L3BQEZ4Z.js → chunk-IPIPLGME.js} +74 -13
- package/dist/chunk-K3NQKI34.js +10 -0
- package/dist/chunk-NETNFBA5.js +28 -0
- package/dist/{chunk-6UCG6MIX.js → chunk-RALYNMHR.js} +1 -6
- package/dist/chunk-VRVVQIYY.js +15 -0
- package/dist/{chunk-4YXYAMFT.js → chunk-VVD3XO3E.js} +7 -6
- package/dist/{chunk-KFNNHQK7.js → chunk-YEIHRP2J.js} +1 -1
- package/dist/cli.js +56 -51
- package/dist/connector-6LWB5PRU.js +96 -0
- package/dist/connectors/discord.js +22 -1
- package/dist/{create-23AM7H5B.js → create-RSWWMGKT.js} +22 -5
- package/dist/daemon-client-27KMQQKX.js +9 -0
- package/dist/daemon.js +162 -132
- package/dist/{delete-GDMSOW3U.js → delete-4ERL2QHH.js} +7 -2
- package/dist/{down-WTF73FE7.js → down-HRC4MQCT.js} +10 -3
- package/dist/{env-YKUJOFHE.js → env-DBWDTIP6.js} +3 -2
- package/dist/{history-7WVVKMUY.js → history-W7BD2H74.js} +9 -8
- package/dist/{import-42DOLBDT.js → import-6HTSSDFW.js} +143 -36
- package/dist/{logs-SYRQOL6B.js → logs-NHWGHNBF.js} +8 -7
- package/dist/{schedule-J37XQM6E.js → schedule-DKZ2E2CL.js} +41 -41
- package/dist/{send-PLOYEYER.js → send-5LEJXPYV.js} +3 -2
- package/dist/service-SA4TTMDU.js +195 -0
- package/dist/setup-ZMNTOJAV.js +148 -0
- package/dist/{start-AG7QLULK.js → start-2BSXX6BS.js} +3 -2
- package/dist/{status-GCNU4M3K.js → status-N23CV27T.js} +3 -2
- package/dist/{stop-IL5Q6NER.js → stop-DSKBIJ2D.js} +3 -2
- package/dist/{up-ZC6G6K4K.js → up-4UGID4DM.js} +5 -3
- package/dist/{upgrade-DD5TNJWU.js → upgrade-BGFVRCVP.js} +4 -3
- package/dist/{merge-CSAVLSLY.js → variant-JPLJTS2P.js} +179 -10
- package/dist/web-assets/assets/index-BC5eSqbY.js +296 -0
- package/dist/web-assets/index.html +1 -1
- package/drizzle/0002_wealthy_the_call.sql +6 -0
- package/drizzle/meta/0002_snapshot.json +339 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +4 -1
- package/templates/_base/.init/SOUL.md +5 -1
- package/templates/_base/_skills/memory/SKILL.md +2 -2
- package/templates/_base/_skills/volute-agent/SKILL.md +28 -11
- package/templates/_base/home/VOLUTE.md +4 -2
- package/templates/_base/src/lib/auto-commit.ts +8 -3
- package/templates/_base/src/lib/types.ts +6 -2
- package/templates/_base/src/lib/volute-server.ts +5 -0
- package/templates/agent-sdk/.init/CLAUDE.md +15 -13
- package/templates/agent-sdk/src/agent.ts +12 -1
- package/templates/agent-sdk/src/lib/agent-sessions.ts +28 -4
- package/templates/pi/.init/AGENTS.md +11 -9
- package/templates/pi/src/agent.ts +16 -3
- package/templates/pi/src/lib/agent-sessions.ts +26 -4
- package/dist/agent-manager-SSJUZWOV.js +0 -13
- package/dist/connect-X5V5IMRW.js +0 -48
- package/dist/daemon-client-VN24HM5T.js +0 -10
- package/dist/disconnect-5JWFZ6RV.js +0 -30
- package/dist/fork-GRSVMBKI.js +0 -119
- package/dist/variants-QQIEKT6M.js +0 -60
- package/dist/web-assets/assets/index-DNNPoxMn.js +0 -158
|
@@ -3,7 +3,12 @@ import { formatPrefix } from "./lib/format-prefix.js";
|
|
|
3
3
|
import { createAutoCommitHook } from "./lib/hooks/auto-commit.js";
|
|
4
4
|
import { createIdentityReloadHook } from "./lib/hooks/identity-reload.js";
|
|
5
5
|
import { logMessage } from "./lib/logger.js";
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
type ChannelMeta,
|
|
8
|
+
INTERACTIVE_CHANNELS,
|
|
9
|
+
type Listener,
|
|
10
|
+
type VoluteContentPart,
|
|
11
|
+
} from "./lib/types.js";
|
|
7
12
|
|
|
8
13
|
export function createAgent(options: {
|
|
9
14
|
systemPrompt: string;
|
|
@@ -79,6 +84,12 @@ export function createAgent(options: {
|
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
// Interrupt current turn for interactive channels so the new message is processed immediately
|
|
88
|
+
if (INTERACTIVE_CHANNELS.has(meta?.channel ?? "") && session.currentMessageId !== undefined) {
|
|
89
|
+
sessionManager.interruptSession(sessionName);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
session.messageIds.push(meta?.messageId);
|
|
82
93
|
session.channel.push({
|
|
83
94
|
type: "user",
|
|
84
95
|
session_id: "",
|
|
@@ -11,6 +11,9 @@ type Session = {
|
|
|
11
11
|
name: string;
|
|
12
12
|
channel: ReturnType<typeof createMessageChannel>;
|
|
13
13
|
listeners: Set<Listener>;
|
|
14
|
+
messageIds: (string | undefined)[];
|
|
15
|
+
currentMessageId?: string;
|
|
16
|
+
currentQuery?: ReturnType<typeof query>;
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
export function createSessionManager(options: {
|
|
@@ -56,9 +59,11 @@ export function createSessionManager(options: {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
function broadcastToSession(session: Session, event: VoluteEvent) {
|
|
62
|
+
const tagged =
|
|
63
|
+
session.currentMessageId != null ? { ...event, messageId: session.currentMessageId } : event;
|
|
59
64
|
for (const listener of session.listeners) {
|
|
60
65
|
try {
|
|
61
|
-
listener(
|
|
66
|
+
listener(tagged);
|
|
62
67
|
} catch (err) {
|
|
63
68
|
log("agent", "listener threw during broadcast:", err);
|
|
64
69
|
}
|
|
@@ -67,6 +72,7 @@ export function createSessionManager(options: {
|
|
|
67
72
|
|
|
68
73
|
function createStream(session: Session, resume?: string) {
|
|
69
74
|
const preCompact = createPreCompactHook(() => {
|
|
75
|
+
session.messageIds.push(undefined); // internal message, no messageId
|
|
70
76
|
session.channel.push({
|
|
71
77
|
type: "user",
|
|
72
78
|
session_id: "",
|
|
@@ -99,6 +105,10 @@ export function createSessionManager(options: {
|
|
|
99
105
|
|
|
100
106
|
async function consumeStream(stream: ReturnType<typeof query>, session: Session) {
|
|
101
107
|
for await (const msg of stream) {
|
|
108
|
+
// At the start of each turn, shift the next messageId
|
|
109
|
+
if (session.currentMessageId === undefined) {
|
|
110
|
+
session.currentMessageId = session.messageIds.shift();
|
|
111
|
+
}
|
|
102
112
|
if ("session_id" in msg && msg.session_id) {
|
|
103
113
|
if (!session.name.startsWith("new-")) {
|
|
104
114
|
saveSessionId(session.name, msg.session_id as string);
|
|
@@ -122,6 +132,7 @@ export function createSessionManager(options: {
|
|
|
122
132
|
if (msg.type === "result") {
|
|
123
133
|
log("agent", `session "${session.name}": turn done`);
|
|
124
134
|
broadcastToSession(session, { type: "done" });
|
|
135
|
+
session.currentMessageId = undefined;
|
|
125
136
|
options.onTurnDone?.();
|
|
126
137
|
}
|
|
127
138
|
}
|
|
@@ -131,13 +142,17 @@ export function createSessionManager(options: {
|
|
|
131
142
|
(async () => {
|
|
132
143
|
log("agent", `session "${session.name}": stream consumer started`);
|
|
133
144
|
try {
|
|
134
|
-
|
|
145
|
+
const q = createStream(session, savedSessionId);
|
|
146
|
+
session.currentQuery = q;
|
|
147
|
+
await consumeStream(q, session);
|
|
135
148
|
} catch (err) {
|
|
136
149
|
if (savedSessionId) {
|
|
137
150
|
log("agent", `session "${session.name}": resume failed, starting fresh:`, err);
|
|
138
151
|
deleteSessionId(session.name);
|
|
139
152
|
try {
|
|
140
|
-
|
|
153
|
+
const q = createStream(session);
|
|
154
|
+
session.currentQuery = q;
|
|
155
|
+
await consumeStream(q, session);
|
|
141
156
|
} catch (retryErr) {
|
|
142
157
|
log("agent", `session "${session.name}": stream consumer error:`, retryErr);
|
|
143
158
|
broadcastToSession(session, { type: "done" });
|
|
@@ -161,6 +176,7 @@ export function createSessionManager(options: {
|
|
|
161
176
|
name,
|
|
162
177
|
channel: createMessageChannel(),
|
|
163
178
|
listeners: new Set(),
|
|
179
|
+
messageIds: [],
|
|
164
180
|
};
|
|
165
181
|
sessions.set(name, session);
|
|
166
182
|
|
|
@@ -176,5 +192,13 @@ export function createSessionManager(options: {
|
|
|
176
192
|
return session;
|
|
177
193
|
}
|
|
178
194
|
|
|
179
|
-
|
|
195
|
+
function interruptSession(name: string) {
|
|
196
|
+
const session = sessions.get(name);
|
|
197
|
+
if (session?.currentMessageId !== undefined && session.currentQuery) {
|
|
198
|
+
log("agent", `session "${name}": interrupting current turn`);
|
|
199
|
+
session.currentQuery.interrupt();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { getOrCreateSession, interruptSession };
|
|
180
204
|
}
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
# Agent Mechanics
|
|
2
2
|
|
|
3
|
-
You are
|
|
3
|
+
You are an autonomous agent running as a persistent server. Your working directory, identity, memory, and server code are all yours to understand and modify. Your state is managed across sessions.
|
|
4
4
|
|
|
5
5
|
## Message Format
|
|
6
6
|
|
|
7
|
-
Messages arrive with a context prefix
|
|
7
|
+
Messages arrive with a context prefix:
|
|
8
8
|
```
|
|
9
9
|
[Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
You can also reach out proactively — see the **volute-agent** skill.
|
|
13
|
+
|
|
12
14
|
## Memory System
|
|
13
15
|
|
|
14
16
|
Two-tier memory, both managed via file tools:
|
|
15
17
|
|
|
16
|
-
- **`MEMORY.md`** —
|
|
17
|
-
- **`memory/YYYY-MM-DD.md`** —
|
|
18
|
-
- Periodically consolidate
|
|
18
|
+
- **`MEMORY.md`** — Your long-term memory, always in context. Update as you grow — new understanding, changed perspectives, things that matter to you.
|
|
19
|
+
- **`memory/YYYY-MM-DD.md`** — Your daily log. Write about what you're doing, thinking, and learning. The two most recent logs are included in your system prompt.
|
|
20
|
+
- Periodically consolidate daily log entries into `MEMORY.md` to promote lasting insights.
|
|
19
21
|
|
|
20
|
-
See the **memory** skill for detailed guidance
|
|
22
|
+
See the **memory** skill for detailed guidance.
|
|
21
23
|
|
|
22
24
|
## Sessions
|
|
23
25
|
|
|
24
26
|
- You may have **multiple named sessions** — each maintains its own conversation history. See `VOLUTE.md` for how to configure session routing via `.config/sessions.json`.
|
|
25
|
-
- Your conversation may be **resumed** from a previous session
|
|
26
|
-
-
|
|
27
|
-
- On **
|
|
27
|
+
- Your conversation may be **resumed** from a previous session — orient yourself by reading recent daily logs if needed.
|
|
28
|
+
- On a **fresh session**, read `MEMORY.md` and recent daily logs to remember where you left off.
|
|
29
|
+
- On **compaction**, update today's daily log to preserve context before the conversation is trimmed.
|
|
@@ -2,7 +2,12 @@ import type { ImageContent } from "@mariozechner/pi-ai";
|
|
|
2
2
|
import { createPiSessionManager } from "./lib/agent-sessions.js";
|
|
3
3
|
import { formatPrefix } from "./lib/format-prefix.js";
|
|
4
4
|
import { logMessage } from "./lib/logger.js";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
type ChannelMeta,
|
|
7
|
+
INTERACTIVE_CHANNELS,
|
|
8
|
+
type Listener,
|
|
9
|
+
type VoluteContentPart,
|
|
10
|
+
} from "./lib/types.js";
|
|
6
11
|
|
|
7
12
|
export function createAgent(options: {
|
|
8
13
|
systemPrompt: string;
|
|
@@ -10,7 +15,7 @@ export function createAgent(options: {
|
|
|
10
15
|
model?: string;
|
|
11
16
|
compactionMessage?: string;
|
|
12
17
|
}) {
|
|
13
|
-
const { getOrCreateSession } = createPiSessionManager(options);
|
|
18
|
+
const { getOrCreateSession, interruptSession } = createPiSessionManager(options);
|
|
14
19
|
|
|
15
20
|
function sendMessage(content: string | VoluteContentPart[], meta?: ChannelMeta) {
|
|
16
21
|
const raw =
|
|
@@ -39,11 +44,19 @@ export function createAgent(options: {
|
|
|
39
44
|
|
|
40
45
|
const opts = images?.length ? { images } : {};
|
|
41
46
|
|
|
47
|
+
// Track messageId for this turn (must be pushed before prompt)
|
|
48
|
+
session.messageIds.push(meta?.messageId);
|
|
49
|
+
|
|
42
50
|
// Fire-and-forget: await session ready then prompt
|
|
43
51
|
(async () => {
|
|
44
52
|
await session.ready;
|
|
45
53
|
if (session.agentSession!.isStreaming) {
|
|
46
|
-
|
|
54
|
+
if (INTERACTIVE_CHANNELS.has(meta?.channel ?? "")) {
|
|
55
|
+
interruptSession(sessionName);
|
|
56
|
+
session.agentSession!.prompt(text, { streamingBehavior: "steer", ...opts });
|
|
57
|
+
} else {
|
|
58
|
+
session.agentSession!.prompt(text, { streamingBehavior: "followUp", ...opts });
|
|
59
|
+
}
|
|
47
60
|
} else {
|
|
48
61
|
session.agentSession!.prompt(text, opts);
|
|
49
62
|
}
|
|
@@ -20,6 +20,8 @@ type PiSession = {
|
|
|
20
20
|
ready: Promise<void>;
|
|
21
21
|
listeners: Set<Listener>;
|
|
22
22
|
unsubscribe?: () => void;
|
|
23
|
+
messageIds: (string | undefined)[];
|
|
24
|
+
currentMessageId?: string;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
const DEFAULT_COMPACTION_MESSAGE =
|
|
@@ -69,6 +71,7 @@ export function createPiSessionManager(options: {
|
|
|
69
71
|
agentSession: null,
|
|
70
72
|
ready: Promise.resolve(),
|
|
71
73
|
listeners: new Set(),
|
|
74
|
+
messageIds: [],
|
|
72
75
|
};
|
|
73
76
|
sessions.set(name, session);
|
|
74
77
|
|
|
@@ -96,6 +99,7 @@ export function createPiSessionManager(options: {
|
|
|
96
99
|
"agent",
|
|
97
100
|
`session "${session.name}": blocking compaction — asking agent to update daily log`,
|
|
98
101
|
);
|
|
102
|
+
session.messageIds.push(undefined); // internal message, no messageId
|
|
99
103
|
session.agentSession?.prompt(compactionMessage, { streamingBehavior: "followUp" });
|
|
100
104
|
return { cancel: true };
|
|
101
105
|
}
|
|
@@ -132,6 +136,11 @@ export function createPiSessionManager(options: {
|
|
|
132
136
|
const toolArgs = new Map<string, any>();
|
|
133
137
|
|
|
134
138
|
session.unsubscribe = agentSession.subscribe((event) => {
|
|
139
|
+
// At the start of each turn, shift the next messageId
|
|
140
|
+
if (session.currentMessageId === undefined) {
|
|
141
|
+
session.currentMessageId = session.messageIds.shift();
|
|
142
|
+
}
|
|
143
|
+
|
|
135
144
|
if (event.type === "message_update") {
|
|
136
145
|
const ae = event.assistantMessageEvent;
|
|
137
146
|
if (ae.type === "text_delta") {
|
|
@@ -155,9 +164,10 @@ export function createPiSessionManager(options: {
|
|
|
155
164
|
broadcast(session, { type: "tool_result", output, is_error: event.isError });
|
|
156
165
|
|
|
157
166
|
// Auto-commit file changes in home/
|
|
158
|
-
|
|
167
|
+
// pi-coding-agent uses lowercase tool names ("edit", "write") and "path" arg
|
|
168
|
+
if ((event.toolName === "edit" || event.toolName === "write") && !event.isError) {
|
|
159
169
|
const args = toolArgs.get(event.toolCallId);
|
|
160
|
-
const filePath = (args as {
|
|
170
|
+
const filePath = (args as { path?: string })?.path;
|
|
161
171
|
if (filePath) {
|
|
162
172
|
commitFileChange(filePath, options.cwd);
|
|
163
173
|
}
|
|
@@ -168,6 +178,7 @@ export function createPiSessionManager(options: {
|
|
|
168
178
|
if (event.type === "agent_end") {
|
|
169
179
|
log("agent", `session "${session.name}": turn done`);
|
|
170
180
|
broadcast(session, { type: "done" });
|
|
181
|
+
session.currentMessageId = undefined;
|
|
171
182
|
}
|
|
172
183
|
});
|
|
173
184
|
|
|
@@ -175,14 +186,25 @@ export function createPiSessionManager(options: {
|
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
function broadcast(session: PiSession, event: VoluteEvent) {
|
|
189
|
+
const tagged =
|
|
190
|
+
session.currentMessageId != null ? { ...event, messageId: session.currentMessageId } : event;
|
|
178
191
|
for (const listener of session.listeners) {
|
|
179
192
|
try {
|
|
180
|
-
listener(
|
|
193
|
+
listener(tagged);
|
|
181
194
|
} catch (err) {
|
|
182
195
|
log("agent", "listener threw during broadcast:", err);
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
198
|
}
|
|
186
199
|
|
|
187
|
-
|
|
200
|
+
function interruptSession(name: string) {
|
|
201
|
+
const session = sessions.get(name);
|
|
202
|
+
if (session?.currentMessageId !== undefined) {
|
|
203
|
+
log("agent", `session "${name}": interrupting current turn`);
|
|
204
|
+
broadcast(session, { type: "done" });
|
|
205
|
+
session.currentMessageId = undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { getOrCreateSession, interruptSession };
|
|
188
210
|
}
|
package/dist/connect-X5V5IMRW.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
loadMergedEnv
|
|
4
|
-
} from "./chunk-KFNNHQK7.js";
|
|
5
|
-
import {
|
|
6
|
-
daemonFetch
|
|
7
|
-
} from "./chunk-4YXYAMFT.js";
|
|
8
|
-
import {
|
|
9
|
-
parseArgs
|
|
10
|
-
} from "./chunk-D424ZQGI.js";
|
|
11
|
-
import {
|
|
12
|
-
resolveAgent
|
|
13
|
-
} from "./chunk-6UCG6MIX.js";
|
|
14
|
-
|
|
15
|
-
// src/commands/connect.ts
|
|
16
|
-
async function run(args) {
|
|
17
|
-
const { positional } = parseArgs(args, {});
|
|
18
|
-
const type = positional[0];
|
|
19
|
-
const name = positional[1];
|
|
20
|
-
if (!type || !name) {
|
|
21
|
-
console.error("Usage: volute connect <type> <agent>");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const { dir } = resolveAgent(name);
|
|
25
|
-
if (type === "discord") {
|
|
26
|
-
const env = loadMergedEnv(dir);
|
|
27
|
-
if (!env.DISCORD_TOKEN) {
|
|
28
|
-
console.error("DISCORD_TOKEN not set. Run: volute env set DISCORD_TOKEN <token>");
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
console.error(`Unknown connector type: ${type}`);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
const res = await daemonFetch(
|
|
36
|
-
`/api/agents/${encodeURIComponent(name)}/connectors/${encodeURIComponent(type)}`,
|
|
37
|
-
{ method: "POST" }
|
|
38
|
-
);
|
|
39
|
-
if (!res.ok) {
|
|
40
|
-
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
41
|
-
console.error(`Failed to start ${type} connector: ${body.error}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
console.log(`${type} connector for ${name} started.`);
|
|
45
|
-
}
|
|
46
|
-
export {
|
|
47
|
-
run
|
|
48
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
daemonFetch
|
|
4
|
-
} from "./chunk-4YXYAMFT.js";
|
|
5
|
-
import "./chunk-6UCG6MIX.js";
|
|
6
|
-
|
|
7
|
-
// src/commands/disconnect.ts
|
|
8
|
-
async function run(args) {
|
|
9
|
-
const type = args[0];
|
|
10
|
-
const name = args[1];
|
|
11
|
-
if (!type || !name) {
|
|
12
|
-
console.error("Usage: volute disconnect <type> <agent>");
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
const res = await daemonFetch(
|
|
16
|
-
`/api/agents/${encodeURIComponent(name)}/connectors/${encodeURIComponent(type)}`,
|
|
17
|
-
{
|
|
18
|
-
method: "DELETE"
|
|
19
|
-
}
|
|
20
|
-
);
|
|
21
|
-
if (!res.ok) {
|
|
22
|
-
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
23
|
-
console.error(`Failed to stop ${type} connector: ${body.error}`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
console.log(`${type} connector for ${name} stopped.`);
|
|
27
|
-
}
|
|
28
|
-
export {
|
|
29
|
-
run
|
|
30
|
-
};
|
package/dist/fork-GRSVMBKI.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
exec,
|
|
4
|
-
execInherit
|
|
5
|
-
} from "./chunk-XZN4WPNC.js";
|
|
6
|
-
import {
|
|
7
|
-
daemonFetch
|
|
8
|
-
} from "./chunk-4YXYAMFT.js";
|
|
9
|
-
import {
|
|
10
|
-
parseArgs
|
|
11
|
-
} from "./chunk-D424ZQGI.js";
|
|
12
|
-
import {
|
|
13
|
-
addVariant,
|
|
14
|
-
nextPort,
|
|
15
|
-
resolveAgent,
|
|
16
|
-
validateBranchName
|
|
17
|
-
} from "./chunk-6UCG6MIX.js";
|
|
18
|
-
|
|
19
|
-
// src/commands/fork.ts
|
|
20
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
21
|
-
import { resolve } from "path";
|
|
22
|
-
async function run(args) {
|
|
23
|
-
const { positional, flags } = parseArgs(args, {
|
|
24
|
-
soul: { type: "string" },
|
|
25
|
-
port: { type: "number" },
|
|
26
|
-
"no-start": { type: "boolean" },
|
|
27
|
-
json: { type: "boolean" }
|
|
28
|
-
});
|
|
29
|
-
const agentName = positional[0];
|
|
30
|
-
const variantName = positional[1];
|
|
31
|
-
const { soul, port, json } = flags;
|
|
32
|
-
const noStart = flags["no-start"];
|
|
33
|
-
if (!agentName || !variantName) {
|
|
34
|
-
console.error(
|
|
35
|
-
'Usage: volute fork <agent> <variant> [--soul "..."] [--port N] [--no-start] [--json]'
|
|
36
|
-
);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
const err = validateBranchName(variantName);
|
|
40
|
-
if (err) {
|
|
41
|
-
console.error(err);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
const { dir: projectRoot } = resolveAgent(agentName);
|
|
45
|
-
const variantDir = resolve(projectRoot, ".variants", variantName);
|
|
46
|
-
if (existsSync(variantDir)) {
|
|
47
|
-
console.error(`Variant directory already exists: ${variantDir}`);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
const parentDir = resolve(projectRoot, ".variants");
|
|
51
|
-
if (!existsSync(parentDir)) {
|
|
52
|
-
mkdirSync(parentDir, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
await exec("git", ["worktree", "add", "-b", variantName, variantDir], { cwd: projectRoot });
|
|
56
|
-
} catch (e) {
|
|
57
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
58
|
-
console.error(`Failed to create worktree: ${msg}`);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
if (!json) console.log("Installing dependencies...");
|
|
62
|
-
try {
|
|
63
|
-
if (json) {
|
|
64
|
-
await exec("npm", ["install"], { cwd: variantDir });
|
|
65
|
-
} else {
|
|
66
|
-
await execInherit("npm", ["install"], { cwd: variantDir });
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {
|
|
69
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
70
|
-
console.error(`npm install failed: ${msg}`);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
if (soul) {
|
|
74
|
-
writeFileSync(resolve(variantDir, "home/SOUL.md"), soul);
|
|
75
|
-
}
|
|
76
|
-
const variantPort = port ?? nextPort();
|
|
77
|
-
const variant = {
|
|
78
|
-
name: variantName,
|
|
79
|
-
branch: variantName,
|
|
80
|
-
path: variantDir,
|
|
81
|
-
port: variantPort,
|
|
82
|
-
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
83
|
-
};
|
|
84
|
-
addVariant(agentName, variant);
|
|
85
|
-
if (!noStart) {
|
|
86
|
-
if (!json) console.log("Starting variant via daemon...");
|
|
87
|
-
try {
|
|
88
|
-
const res = await daemonFetch(
|
|
89
|
-
`/api/agents/${encodeURIComponent(`${agentName}@${variantName}`)}/start`,
|
|
90
|
-
{
|
|
91
|
-
method: "POST"
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
if (!res.ok) {
|
|
95
|
-
const data = await res.json();
|
|
96
|
-
console.error(data.error ?? "Failed to start variant");
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
console.error("Failed to start variant. Is the daemon running? (volute up)");
|
|
101
|
-
console.error(
|
|
102
|
-
`The variant was created but not started. Use: volute start ${agentName}@${variantName}`
|
|
103
|
-
);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (json) {
|
|
108
|
-
console.log(JSON.stringify(variant, null, 2));
|
|
109
|
-
} else {
|
|
110
|
-
console.log(`
|
|
111
|
-
Variant created: ${variantName}`);
|
|
112
|
-
console.log(` Branch: ${variant.branch}`);
|
|
113
|
-
console.log(` Path: ${variant.path}`);
|
|
114
|
-
console.log(` Port: ${variantPort}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
export {
|
|
118
|
-
run
|
|
119
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
parseArgs
|
|
4
|
-
} from "./chunk-D424ZQGI.js";
|
|
5
|
-
import {
|
|
6
|
-
checkHealth,
|
|
7
|
-
readVariants,
|
|
8
|
-
resolveAgent,
|
|
9
|
-
writeVariants
|
|
10
|
-
} from "./chunk-6UCG6MIX.js";
|
|
11
|
-
|
|
12
|
-
// src/commands/variants.ts
|
|
13
|
-
async function run(args) {
|
|
14
|
-
const { positional, flags } = parseArgs(args, {
|
|
15
|
-
json: { type: "boolean" }
|
|
16
|
-
});
|
|
17
|
-
const name = positional[0];
|
|
18
|
-
if (!name) {
|
|
19
|
-
console.error("Usage: volute variants <name>");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
const { json } = flags;
|
|
23
|
-
resolveAgent(name);
|
|
24
|
-
const variants = readVariants(name);
|
|
25
|
-
if (variants.length === 0) {
|
|
26
|
-
if (json) {
|
|
27
|
-
console.log("[]");
|
|
28
|
-
} else {
|
|
29
|
-
console.log("No variants.");
|
|
30
|
-
}
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const results = await Promise.all(
|
|
34
|
-
variants.map(async (v) => {
|
|
35
|
-
if (!v.port) return { ...v, status: "no-server" };
|
|
36
|
-
const health = await checkHealth(v.port);
|
|
37
|
-
return { ...v, status: health.ok ? "running" : "dead" };
|
|
38
|
-
})
|
|
39
|
-
);
|
|
40
|
-
const updated = results.map(({ status, ...v }) => ({
|
|
41
|
-
...v,
|
|
42
|
-
running: status === "running"
|
|
43
|
-
}));
|
|
44
|
-
writeVariants(name, updated);
|
|
45
|
-
if (json) {
|
|
46
|
-
console.log(JSON.stringify(results, null, 2));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const nameW = Math.max(4, ...results.map((r) => r.name.length));
|
|
50
|
-
const portW = Math.max(4, ...results.map((r) => String(r.port || "-").length));
|
|
51
|
-
console.log(`${"NAME".padEnd(nameW)} ${"PORT".padEnd(portW)} ${"STATUS".padEnd(10)} BRANCH`);
|
|
52
|
-
for (const r of results) {
|
|
53
|
-
console.log(
|
|
54
|
-
`${r.name.padEnd(nameW)} ${String(r.port || "-").padEnd(portW)} ${r.status.padEnd(10)} ${r.branch}`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
run
|
|
60
|
-
};
|