volute 0.1.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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/channel-Q642YUZE.js +90 -0
- package/dist/chunk-5YW4B7CG.js +181 -0
- package/dist/chunk-A5ZJEMHT.js +40 -0
- package/dist/chunk-D424ZQGI.js +31 -0
- package/dist/chunk-GSPKUPKU.js +120 -0
- package/dist/chunk-H5XQARAP.js +48 -0
- package/dist/chunk-KSMIWOCN.js +84 -0
- package/dist/chunk-N4QN44LC.js +74 -0
- package/dist/chunk-XZN4WPNC.js +34 -0
- package/dist/cli.js +95 -0
- package/dist/connect-LW6G23AV.js +48 -0
- package/dist/connectors/discord.js +213 -0
- package/dist/create-3K6O2SDC.js +62 -0
- package/dist/daemon-client-ZTHW7ROS.js +10 -0
- package/dist/daemon.js +1731 -0
- package/dist/delete-JNGY7ZFH.js +54 -0
- package/dist/disconnect-ACVTKTRE.js +30 -0
- package/dist/down-FYCUYC5H.js +71 -0
- package/dist/env-7SLRN3MG.js +159 -0
- package/dist/fork-BB3DZ426.js +112 -0
- package/dist/import-W2AMTEV5.js +410 -0
- package/dist/logs-BUHRIQ2L.js +35 -0
- package/dist/merge-446QTE7Q.js +219 -0
- package/dist/schedule-KKSOVUDF.js +113 -0
- package/dist/send-WQSVSRDD.js +50 -0
- package/dist/start-LKMWS6ZE.js +29 -0
- package/dist/status-CIEKUI3V.js +50 -0
- package/dist/stop-YTOAGYE4.js +29 -0
- package/dist/up-AJJ4GCXY.js +111 -0
- package/dist/upgrade-JACA6YMO.js +211 -0
- package/dist/variants-HPY4DEWU.js +60 -0
- package/dist/web-assets/assets/index-DNNPoxMn.js +158 -0
- package/dist/web-assets/index.html +15 -0
- package/package.json +76 -0
- package/templates/_base/.init/MEMORY.md +2 -0
- package/templates/_base/.init/SOUL.md +2 -0
- package/templates/_base/.init/memory/.gitkeep +0 -0
- package/templates/_base/_skills/memory/SKILL.md +30 -0
- package/templates/_base/_skills/volute-agent/SKILL.md +53 -0
- package/templates/_base/biome.json.tmpl +21 -0
- package/templates/_base/home/VOLUTE.md +19 -0
- package/templates/_base/src/lib/auto-commit.ts +46 -0
- package/templates/_base/src/lib/logger.ts +47 -0
- package/templates/_base/src/lib/types.ts +24 -0
- package/templates/_base/src/lib/volute-server.ts +98 -0
- package/templates/_base/tsconfig.json +13 -0
- package/templates/_base/volute.json.tmpl +3 -0
- package/templates/agent-sdk/.init/CLAUDE.md +36 -0
- package/templates/agent-sdk/package.json.tmpl +20 -0
- package/templates/agent-sdk/src/lib/agent.ts +199 -0
- package/templates/agent-sdk/src/lib/hooks/auto-commit.ts +14 -0
- package/templates/agent-sdk/src/lib/hooks/identity-reload.ts +26 -0
- package/templates/agent-sdk/src/lib/hooks/pre-compact.ts +20 -0
- package/templates/agent-sdk/src/lib/message-channel.ts +37 -0
- package/templates/agent-sdk/src/server.ts +158 -0
- package/templates/agent-sdk/volute-template.json +9 -0
- package/templates/pi/.init/AGENTS.md +26 -0
- package/templates/pi/package.json.tmpl +20 -0
- package/templates/pi/src/lib/agent.ts +205 -0
- package/templates/pi/src/server.ts +121 -0
- package/templates/pi/volute-template.json +9 -0
- package/templates/pi/volute.json.tmpl +3 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
composeTemplate,
|
|
4
|
+
copyTemplateToDir,
|
|
5
|
+
findTemplatesRoot
|
|
6
|
+
} from "./chunk-GSPKUPKU.js";
|
|
7
|
+
import {
|
|
8
|
+
exec,
|
|
9
|
+
execInherit
|
|
10
|
+
} from "./chunk-XZN4WPNC.js";
|
|
11
|
+
import {
|
|
12
|
+
parseArgs
|
|
13
|
+
} from "./chunk-D424ZQGI.js";
|
|
14
|
+
import {
|
|
15
|
+
addAgent,
|
|
16
|
+
agentDir,
|
|
17
|
+
ensureVoluteHome,
|
|
18
|
+
nextPort
|
|
19
|
+
} from "./chunk-5YW4B7CG.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/import.ts
|
|
22
|
+
import {
|
|
23
|
+
cpSync,
|
|
24
|
+
existsSync,
|
|
25
|
+
mkdirSync as mkdirSync2,
|
|
26
|
+
readdirSync as readdirSync2,
|
|
27
|
+
readFileSync as readFileSync3,
|
|
28
|
+
rmSync,
|
|
29
|
+
writeFileSync as writeFileSync3
|
|
30
|
+
} from "fs";
|
|
31
|
+
import { resolve as resolve3 } from "path";
|
|
32
|
+
|
|
33
|
+
// src/lib/consolidate.ts
|
|
34
|
+
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
|
35
|
+
import { resolve } from "path";
|
|
36
|
+
async function consolidateMemory(agentDir2) {
|
|
37
|
+
const soulPath = resolve(agentDir2, "home/SOUL.md");
|
|
38
|
+
const memoryPath = resolve(agentDir2, "home/MEMORY.md");
|
|
39
|
+
const memoryDir = resolve(agentDir2, "home/memory");
|
|
40
|
+
const soul = readFileSync(soulPath, "utf-8");
|
|
41
|
+
const logs = [];
|
|
42
|
+
try {
|
|
43
|
+
const files = readdirSync(memoryDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
44
|
+
for (const filename of files) {
|
|
45
|
+
const date = filename.replace(".md", "");
|
|
46
|
+
const content2 = readFileSync(resolve(memoryDir, filename), "utf-8").trim();
|
|
47
|
+
if (content2) {
|
|
48
|
+
logs.push(`### ${date}
|
|
49
|
+
|
|
50
|
+
${content2}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
if (logs.length === 0) {
|
|
56
|
+
console.log("No daily logs found.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
60
|
+
if (!apiKey) {
|
|
61
|
+
console.error("ANTHROPIC_API_KEY not set, skipping memory consolidation.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log("Consolidating memory from daily logs...");
|
|
65
|
+
const userMessage = [
|
|
66
|
+
"You have daily logs from a previous environment but no long-term memory file yet.",
|
|
67
|
+
"Please review the daily logs below and produce consolidated MEMORY.md content.",
|
|
68
|
+
"Keep it concise and organized by topic. Output ONLY the markdown content for MEMORY.md, nothing else.",
|
|
69
|
+
"",
|
|
70
|
+
"## Daily logs",
|
|
71
|
+
"",
|
|
72
|
+
logs.join("\n\n")
|
|
73
|
+
].join("\n");
|
|
74
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
"x-api-key": apiKey,
|
|
79
|
+
"anthropic-version": "2023-06-01"
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
model: "claude-sonnet-4-20250514",
|
|
83
|
+
max_tokens: 4096,
|
|
84
|
+
system: soul,
|
|
85
|
+
messages: [{ role: "user", content: userMessage }]
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const body = await res.text();
|
|
90
|
+
console.error(`Anthropic API error (${res.status}): ${body}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const data = await res.json();
|
|
94
|
+
const content = data.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("").trim();
|
|
95
|
+
if (content) {
|
|
96
|
+
writeFileSync(memoryPath, `${content}
|
|
97
|
+
`);
|
|
98
|
+
console.log("MEMORY.md created successfully.");
|
|
99
|
+
} else {
|
|
100
|
+
console.warn("Warning: No content produced.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/lib/convert-session.ts
|
|
105
|
+
import { randomUUID } from "crypto";
|
|
106
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
107
|
+
import { homedir } from "os";
|
|
108
|
+
import { resolve as resolve2 } from "path";
|
|
109
|
+
function convertSession(opts) {
|
|
110
|
+
const lines = readFileSync2(opts.sessionPath, "utf-8").trim().split("\n");
|
|
111
|
+
const sessionId = randomUUID();
|
|
112
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
113
|
+
const messages = [];
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
const event = JSON.parse(line);
|
|
116
|
+
if (event.type === "message" && event.message) {
|
|
117
|
+
messages.push(event);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const sdkEvents = [];
|
|
121
|
+
let lastSdkUuid = null;
|
|
122
|
+
for (let i = 0; i < messages.length; i++) {
|
|
123
|
+
const event = messages[i];
|
|
124
|
+
const msg = event.message;
|
|
125
|
+
if (msg.role === "user") {
|
|
126
|
+
const uuid = randomUUID();
|
|
127
|
+
idMap.set(event.id, uuid);
|
|
128
|
+
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
129
|
+
const sdkEvent = {
|
|
130
|
+
uuid,
|
|
131
|
+
parentUuid,
|
|
132
|
+
sessionId,
|
|
133
|
+
timestamp: event.timestamp,
|
|
134
|
+
cwd: opts.projectDir,
|
|
135
|
+
version: "0.1.0",
|
|
136
|
+
gitBranch: "main",
|
|
137
|
+
isSidechain: false,
|
|
138
|
+
userType: "external",
|
|
139
|
+
type: "user",
|
|
140
|
+
message: {
|
|
141
|
+
role: "user",
|
|
142
|
+
content: msg.content
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
sdkEvents.push(JSON.stringify(sdkEvent));
|
|
146
|
+
lastSdkUuid = uuid;
|
|
147
|
+
} else if (msg.role === "assistant") {
|
|
148
|
+
const content = convertAssistantContent(msg.content);
|
|
149
|
+
if (content.length === 0) continue;
|
|
150
|
+
const uuid = randomUUID();
|
|
151
|
+
idMap.set(event.id, uuid);
|
|
152
|
+
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : null;
|
|
153
|
+
const stopReason = mapStopReason(msg.stopReason);
|
|
154
|
+
const sdkEvent = {
|
|
155
|
+
uuid,
|
|
156
|
+
parentUuid,
|
|
157
|
+
sessionId,
|
|
158
|
+
timestamp: event.timestamp,
|
|
159
|
+
cwd: opts.projectDir,
|
|
160
|
+
version: "0.1.0",
|
|
161
|
+
gitBranch: "main",
|
|
162
|
+
isSidechain: false,
|
|
163
|
+
userType: "external",
|
|
164
|
+
type: "assistant",
|
|
165
|
+
requestId: `req_imported_${randomUUID()}`,
|
|
166
|
+
message: {
|
|
167
|
+
role: "assistant",
|
|
168
|
+
content,
|
|
169
|
+
type: "message",
|
|
170
|
+
id: `msg_imported_${randomUUID()}`,
|
|
171
|
+
model: mapModel(msg.model),
|
|
172
|
+
stop_reason: stopReason,
|
|
173
|
+
stop_sequence: null,
|
|
174
|
+
usage: mapUsage(msg.usage)
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
sdkEvents.push(JSON.stringify(sdkEvent));
|
|
178
|
+
lastSdkUuid = uuid;
|
|
179
|
+
} else if (msg.role === "toolResult") {
|
|
180
|
+
const toolResults = [];
|
|
181
|
+
let lastToolResultId = event.id;
|
|
182
|
+
let lastTimestamp = event.timestamp;
|
|
183
|
+
let j = i;
|
|
184
|
+
while (j < messages.length && messages[j].message.role === "toolResult") {
|
|
185
|
+
const tr = messages[j];
|
|
186
|
+
const trMsg = tr.message;
|
|
187
|
+
lastToolResultId = tr.id;
|
|
188
|
+
lastTimestamp = tr.timestamp;
|
|
189
|
+
toolResults.push({
|
|
190
|
+
type: "tool_result",
|
|
191
|
+
tool_use_id: trMsg.toolCallId,
|
|
192
|
+
content: trMsg.content,
|
|
193
|
+
...trMsg.isError ? { is_error: true } : {}
|
|
194
|
+
});
|
|
195
|
+
j++;
|
|
196
|
+
}
|
|
197
|
+
i = j - 1;
|
|
198
|
+
const uuid = randomUUID();
|
|
199
|
+
idMap.set(lastToolResultId, uuid);
|
|
200
|
+
const parentUuid = event.parentId ? idMap.get(event.parentId) ?? null : lastSdkUuid;
|
|
201
|
+
const sdkEvent = {
|
|
202
|
+
uuid,
|
|
203
|
+
parentUuid,
|
|
204
|
+
sessionId,
|
|
205
|
+
timestamp: lastTimestamp,
|
|
206
|
+
cwd: opts.projectDir,
|
|
207
|
+
version: "0.1.0",
|
|
208
|
+
gitBranch: "main",
|
|
209
|
+
isSidechain: false,
|
|
210
|
+
userType: "external",
|
|
211
|
+
type: "user",
|
|
212
|
+
sourceToolAssistantUUID: lastSdkUuid ?? void 0,
|
|
213
|
+
toolUseResult: "imported",
|
|
214
|
+
message: {
|
|
215
|
+
role: "user",
|
|
216
|
+
content: toolResults
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
sdkEvents.push(JSON.stringify(sdkEvent));
|
|
220
|
+
lastSdkUuid = uuid;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const projectId = opts.projectDir.replace(/\//g, "-");
|
|
224
|
+
const sdkDir = resolve2(homedir(), ".claude", "projects", projectId);
|
|
225
|
+
mkdirSync(sdkDir, { recursive: true });
|
|
226
|
+
const sdkPath = resolve2(sdkDir, `${sessionId}.jsonl`);
|
|
227
|
+
writeFileSync2(sdkPath, `${sdkEvents.join("\n")}
|
|
228
|
+
`);
|
|
229
|
+
console.log(`Converted ${sdkEvents.length} messages \u2192 ${sdkPath}`);
|
|
230
|
+
return sessionId;
|
|
231
|
+
}
|
|
232
|
+
var MODEL_MAP = {
|
|
233
|
+
"claude-opus-4-5": "claude-opus-4-5-20251101",
|
|
234
|
+
"claude-sonnet-4": "claude-sonnet-4-20250514"
|
|
235
|
+
};
|
|
236
|
+
function mapModel(model) {
|
|
237
|
+
if (!model) return "claude-opus-4-5-20251101";
|
|
238
|
+
return MODEL_MAP[model] ?? model;
|
|
239
|
+
}
|
|
240
|
+
function mapStopReason(stopReason) {
|
|
241
|
+
if (!stopReason) return "end_turn";
|
|
242
|
+
const map = {
|
|
243
|
+
toolUse: "tool_use",
|
|
244
|
+
endTurn: "end_turn",
|
|
245
|
+
stop: "end_turn",
|
|
246
|
+
maxTokens: "max_tokens"
|
|
247
|
+
};
|
|
248
|
+
return map[stopReason] ?? stopReason;
|
|
249
|
+
}
|
|
250
|
+
function mapUsage(usage) {
|
|
251
|
+
if (!usage) return { input_tokens: 0, output_tokens: 0 };
|
|
252
|
+
return {
|
|
253
|
+
input_tokens: usage.input ?? usage.input_tokens ?? 0,
|
|
254
|
+
output_tokens: usage.output ?? usage.output_tokens ?? 0,
|
|
255
|
+
cache_read_input_tokens: usage.cacheRead ?? usage.cache_read_input_tokens ?? 0,
|
|
256
|
+
cache_creation_input_tokens: usage.cacheWrite ?? usage.cache_creation_input_tokens ?? 0
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function convertAssistantContent(content) {
|
|
260
|
+
const result = [];
|
|
261
|
+
for (const block of content) {
|
|
262
|
+
if (block.type === "thinking") {
|
|
263
|
+
} else if (block.type === "toolCall") {
|
|
264
|
+
result.push({
|
|
265
|
+
type: "tool_use",
|
|
266
|
+
id: block.id,
|
|
267
|
+
name: block.name,
|
|
268
|
+
input: block.arguments ?? block.input ?? {},
|
|
269
|
+
caller: { type: "direct" }
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
result.push(block);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/commands/import.ts
|
|
279
|
+
async function run(args) {
|
|
280
|
+
const { positional, flags } = parseArgs(args, {
|
|
281
|
+
name: { type: "string" },
|
|
282
|
+
session: { type: "string" },
|
|
283
|
+
template: { type: "string" }
|
|
284
|
+
});
|
|
285
|
+
const workspacePath = positional[0];
|
|
286
|
+
if (!workspacePath) {
|
|
287
|
+
console.error(
|
|
288
|
+
"Usage: volute import <openclaw-workspace-path> [--name <name>] [--session <session-jsonl-path>] [--template <name>]"
|
|
289
|
+
);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const wsDir = resolve3(workspacePath);
|
|
293
|
+
const soulPath = resolve3(wsDir, "SOUL.md");
|
|
294
|
+
const identityPath = resolve3(wsDir, "IDENTITY.md");
|
|
295
|
+
if (!existsSync(soulPath) || !existsSync(identityPath)) {
|
|
296
|
+
console.error("Not a valid OpenClaw workspace: missing SOUL.md or IDENTITY.md");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
const soul = readFileSync3(soulPath, "utf-8");
|
|
300
|
+
const identity = readFileSync3(identityPath, "utf-8");
|
|
301
|
+
const userPath = resolve3(wsDir, "USER.md");
|
|
302
|
+
const user = existsSync(userPath) ? readFileSync3(userPath, "utf-8") : "";
|
|
303
|
+
const name = flags.name ?? parseNameFromIdentity(identity) ?? "imported-agent";
|
|
304
|
+
const mergedSoul = `${soul.trimEnd()}
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
${identity.trimEnd()}
|
|
309
|
+
`;
|
|
310
|
+
const mergedMemoryExtra = user ? `
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
${user.trimEnd()}
|
|
315
|
+
` : "";
|
|
316
|
+
ensureVoluteHome();
|
|
317
|
+
const dest = agentDir(name);
|
|
318
|
+
if (existsSync(dest)) {
|
|
319
|
+
console.error(`Agent already exists: ${name}`);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
const template = flags.template ?? "agent-sdk";
|
|
323
|
+
const templatesRoot = findTemplatesRoot();
|
|
324
|
+
const { composedDir, manifest } = composeTemplate(templatesRoot, template);
|
|
325
|
+
try {
|
|
326
|
+
console.log(`Creating project: ${name}`);
|
|
327
|
+
copyTemplateToDir(composedDir, dest, name, manifest);
|
|
328
|
+
} finally {
|
|
329
|
+
rmSync(composedDir, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
const initDir = resolve3(dest, ".init");
|
|
332
|
+
if (existsSync(initDir)) {
|
|
333
|
+
cpSync(initDir, resolve3(dest, "home"), { recursive: true });
|
|
334
|
+
rmSync(initDir, { recursive: true, force: true });
|
|
335
|
+
}
|
|
336
|
+
writeFileSync3(resolve3(dest, "home/SOUL.md"), mergedSoul);
|
|
337
|
+
console.log("Wrote SOUL.md (merged with IDENTITY.md)");
|
|
338
|
+
const wsMemoryPath = resolve3(wsDir, "MEMORY.md");
|
|
339
|
+
const hasMemory = existsSync(wsMemoryPath);
|
|
340
|
+
if (hasMemory) {
|
|
341
|
+
const existingMemory = readFileSync3(wsMemoryPath, "utf-8");
|
|
342
|
+
writeFileSync3(
|
|
343
|
+
resolve3(dest, "home/MEMORY.md"),
|
|
344
|
+
`${existingMemory.trimEnd()}${mergedMemoryExtra}`
|
|
345
|
+
);
|
|
346
|
+
console.log(user ? "Wrote MEMORY.md (merged with USER.md)" : "Copied MEMORY.md");
|
|
347
|
+
} else if (user) {
|
|
348
|
+
writeFileSync3(resolve3(dest, "home/MEMORY.md"), `${user.trimEnd()}
|
|
349
|
+
`);
|
|
350
|
+
console.log("Wrote MEMORY.md (from USER.md)");
|
|
351
|
+
}
|
|
352
|
+
const wsMemoryDir = resolve3(wsDir, "memory");
|
|
353
|
+
let dailyLogCount = 0;
|
|
354
|
+
if (existsSync(wsMemoryDir)) {
|
|
355
|
+
const destMemoryDir = resolve3(dest, "home/memory");
|
|
356
|
+
const files = readdirSync2(wsMemoryDir).filter((f) => f.endsWith(".md"));
|
|
357
|
+
for (const file of files) {
|
|
358
|
+
cpSync(resolve3(wsMemoryDir, file), resolve3(destMemoryDir, file));
|
|
359
|
+
}
|
|
360
|
+
dailyLogCount = files.length;
|
|
361
|
+
if (dailyLogCount > 0) {
|
|
362
|
+
console.log(`Copied ${dailyLogCount} daily log(s)`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const port = nextPort();
|
|
366
|
+
addAgent(name, port);
|
|
367
|
+
console.log("Installing dependencies...");
|
|
368
|
+
await execInherit("npm", ["install"], { cwd: dest });
|
|
369
|
+
if (!hasMemory && dailyLogCount > 0) {
|
|
370
|
+
console.log("No MEMORY.md \u2014 running memory consolidation...");
|
|
371
|
+
await consolidateMemory(dest);
|
|
372
|
+
}
|
|
373
|
+
await exec("git", ["init"], { cwd: dest });
|
|
374
|
+
await exec("git", ["add", "-A"], { cwd: dest });
|
|
375
|
+
await exec("git", ["commit", "-m", "import from OpenClaw"], { cwd: dest });
|
|
376
|
+
if (flags.session && template !== "agent-sdk") {
|
|
377
|
+
console.warn(
|
|
378
|
+
"Warning: --session is only supported with the agent-sdk template, skipping session import"
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (flags.session && template === "agent-sdk") {
|
|
382
|
+
const sessionFile = resolve3(flags.session);
|
|
383
|
+
if (!existsSync(sessionFile)) {
|
|
384
|
+
console.error(`Session file not found: ${sessionFile}`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
console.log("Converting session...");
|
|
388
|
+
const sessionId = convertSession({
|
|
389
|
+
sessionPath: sessionFile,
|
|
390
|
+
projectDir: dest
|
|
391
|
+
});
|
|
392
|
+
const voluteDir = resolve3(dest, ".volute");
|
|
393
|
+
mkdirSync2(voluteDir, { recursive: true });
|
|
394
|
+
writeFileSync3(resolve3(voluteDir, "session.json"), JSON.stringify({ sessionId }));
|
|
395
|
+
}
|
|
396
|
+
console.log(`
|
|
397
|
+
Imported agent: ${name} (port ${port})`);
|
|
398
|
+
console.log(`
|
|
399
|
+
volute start ${name}`);
|
|
400
|
+
}
|
|
401
|
+
function parseNameFromIdentity(identity) {
|
|
402
|
+
const match = identity.match(/\*\*Name:\*\*\s*(.+)/);
|
|
403
|
+
if (match) {
|
|
404
|
+
return match[1].trim().toLowerCase().replace(/\s+/g, "-");
|
|
405
|
+
}
|
|
406
|
+
return void 0;
|
|
407
|
+
}
|
|
408
|
+
export {
|
|
409
|
+
run
|
|
410
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
parseArgs
|
|
4
|
+
} from "./chunk-D424ZQGI.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveAgent
|
|
7
|
+
} from "./chunk-5YW4B7CG.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/logs.ts
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { resolve } from "path";
|
|
13
|
+
async function run(args) {
|
|
14
|
+
const { positional, flags } = parseArgs(args, {
|
|
15
|
+
follow: { type: "boolean" },
|
|
16
|
+
n: { type: "number" }
|
|
17
|
+
});
|
|
18
|
+
const name = positional[0];
|
|
19
|
+
if (!name) {
|
|
20
|
+
console.error("Usage: volute logs <name> [--follow] [-n N]");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const { dir } = resolveAgent(name);
|
|
24
|
+
const logFile = resolve(dir, ".volute", "logs", "agent.log");
|
|
25
|
+
if (!existsSync(logFile)) {
|
|
26
|
+
console.error(`No log file found. Has ${name} been started?`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const tailArgs = [`-n`, String(flags.n ?? 50), ...flags.follow ? ["-f"] : [], logFile];
|
|
30
|
+
const child = spawn("tail", tailArgs, { stdio: "inherit" });
|
|
31
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
run
|
|
35
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
spawnServer
|
|
4
|
+
} from "./chunk-N4QN44LC.js";
|
|
5
|
+
import {
|
|
6
|
+
exec,
|
|
7
|
+
execInherit
|
|
8
|
+
} from "./chunk-XZN4WPNC.js";
|
|
9
|
+
import {
|
|
10
|
+
parseArgs
|
|
11
|
+
} from "./chunk-D424ZQGI.js";
|
|
12
|
+
import {
|
|
13
|
+
checkHealth,
|
|
14
|
+
findVariant,
|
|
15
|
+
removeVariant,
|
|
16
|
+
resolveAgent,
|
|
17
|
+
validateBranchName
|
|
18
|
+
} from "./chunk-5YW4B7CG.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/merge.ts
|
|
21
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
22
|
+
import { resolve } from "path";
|
|
23
|
+
|
|
24
|
+
// src/lib/verify.ts
|
|
25
|
+
async function verify(port) {
|
|
26
|
+
const health = await checkHealth(port);
|
|
27
|
+
if (!health.ok) {
|
|
28
|
+
console.error(" Health check: failed");
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
console.log(" Health check: OK");
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(`http://localhost:${port}/message`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
content: [{ type: "text", text: "ping" }],
|
|
38
|
+
channel: "system"
|
|
39
|
+
}),
|
|
40
|
+
signal: AbortSignal.timeout(6e4)
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok || !res.body) {
|
|
43
|
+
console.error(" Test message: failed to send");
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const reader = res.body.getReader();
|
|
47
|
+
const decoder = new TextDecoder();
|
|
48
|
+
let buffer = "";
|
|
49
|
+
let gotDone = false;
|
|
50
|
+
while (true) {
|
|
51
|
+
const { done, value } = await reader.read();
|
|
52
|
+
if (done) break;
|
|
53
|
+
buffer += decoder.decode(value, { stream: true });
|
|
54
|
+
const lines = buffer.split("\n");
|
|
55
|
+
buffer = lines.pop() || "";
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
if (!line.trim()) continue;
|
|
58
|
+
try {
|
|
59
|
+
const event = JSON.parse(line);
|
|
60
|
+
if (event.type === "done") {
|
|
61
|
+
gotDone = true;
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (gotDone) {
|
|
67
|
+
reader.cancel();
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (gotDone) {
|
|
72
|
+
console.log(" Test message: OK");
|
|
73
|
+
return true;
|
|
74
|
+
} else {
|
|
75
|
+
console.error(" Test message: no done event received");
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
80
|
+
console.error(` Test message: ${msg}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/commands/merge.ts
|
|
86
|
+
async function run(args) {
|
|
87
|
+
const { positional, flags } = parseArgs(args, {
|
|
88
|
+
summary: { type: "string" },
|
|
89
|
+
justification: { type: "string" },
|
|
90
|
+
memory: { type: "string" },
|
|
91
|
+
"skip-verify": { type: "boolean" }
|
|
92
|
+
});
|
|
93
|
+
const agentName = positional[0];
|
|
94
|
+
const variantName = positional[1];
|
|
95
|
+
if (!agentName || !variantName) {
|
|
96
|
+
console.error(
|
|
97
|
+
"Usage: volute merge <agent> <variant> [--summary '...'] [--justification '...'] [--memory '...'] [--skip-verify]"
|
|
98
|
+
);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const { dir: projectRoot } = resolveAgent(agentName);
|
|
102
|
+
const variant = findVariant(agentName, variantName);
|
|
103
|
+
if (!variant) {
|
|
104
|
+
console.error(`Unknown variant: ${variantName}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const branchErr = validateBranchName(variant.branch);
|
|
108
|
+
if (branchErr) {
|
|
109
|
+
console.error(branchErr);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
if (existsSync(variant.path)) {
|
|
113
|
+
const status = (await exec("git", ["status", "--porcelain"], { cwd: variant.path })).trim();
|
|
114
|
+
if (status) {
|
|
115
|
+
console.log("Committing uncommitted changes in variant...");
|
|
116
|
+
await exec("git", ["add", "-A"], { cwd: variant.path });
|
|
117
|
+
await exec("git", ["commit", "-m", "Auto-commit uncommitted changes before merge"], {
|
|
118
|
+
cwd: variant.path
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!flags["skip-verify"]) {
|
|
123
|
+
console.log("Verifying variant...");
|
|
124
|
+
let port = variant.port;
|
|
125
|
+
let tempServerPid;
|
|
126
|
+
let running = false;
|
|
127
|
+
if (variant.pid) {
|
|
128
|
+
try {
|
|
129
|
+
process.kill(variant.pid, 0);
|
|
130
|
+
running = true;
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!running) {
|
|
135
|
+
console.log("Starting temporary server for verification...");
|
|
136
|
+
const result = await spawnServer(variant.path, 0, { detached: true });
|
|
137
|
+
if (!result) {
|
|
138
|
+
console.error("Failed to start server for verification. Use --skip-verify to skip.");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
port = result.actualPort;
|
|
142
|
+
tempServerPid = result.child.pid;
|
|
143
|
+
}
|
|
144
|
+
const verified = await verify(port);
|
|
145
|
+
if (tempServerPid) {
|
|
146
|
+
try {
|
|
147
|
+
process.kill(tempServerPid);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!verified) {
|
|
152
|
+
console.error("Verification failed. Fix issues or use --skip-verify to proceed anyway.");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
console.log("Verification passed.");
|
|
156
|
+
}
|
|
157
|
+
if (variant.pid) {
|
|
158
|
+
try {
|
|
159
|
+
process.kill(variant.pid);
|
|
160
|
+
console.log(`Killed server (pid ${variant.pid})`);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log(`Merging branch: ${variant.branch}`);
|
|
165
|
+
try {
|
|
166
|
+
await execInherit("git", ["merge", variant.branch], { cwd: projectRoot });
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.error("Merge failed:", e);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
if (existsSync(variant.path)) {
|
|
172
|
+
try {
|
|
173
|
+
await exec("git", ["worktree", "remove", "--force", variant.path], { cwd: projectRoot });
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await exec("git", ["branch", "-D", variant.branch], { cwd: projectRoot });
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
removeVariant(agentName, variantName);
|
|
182
|
+
console.log("Reinstalling dependencies...");
|
|
183
|
+
try {
|
|
184
|
+
await execInherit("npm", ["install"], { cwd: projectRoot });
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.error("npm install failed:", e);
|
|
187
|
+
}
|
|
188
|
+
console.log(`Variant ${variantName} merged and cleaned up.`);
|
|
189
|
+
const voluteDir = resolve(projectRoot, ".volute");
|
|
190
|
+
if (!existsSync(voluteDir)) mkdirSync(voluteDir, { recursive: true });
|
|
191
|
+
writeFileSync(
|
|
192
|
+
resolve(voluteDir, "merged.json"),
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
name: variantName,
|
|
195
|
+
...flags.summary && { summary: flags.summary },
|
|
196
|
+
...flags.justification && { justification: flags.justification },
|
|
197
|
+
...flags.memory && { memory: flags.memory }
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
if (process.env.VOLUTE_SUPERVISOR) return;
|
|
201
|
+
try {
|
|
202
|
+
const { daemonFetch } = await import("./daemon-client-ZTHW7ROS.js");
|
|
203
|
+
console.log("Restarting agent via daemon...");
|
|
204
|
+
const res = await daemonFetch(`/api/agents/${encodeURIComponent(agentName)}/restart`, {
|
|
205
|
+
method: "POST"
|
|
206
|
+
});
|
|
207
|
+
if (res.ok) {
|
|
208
|
+
console.log(`${agentName} restarted.`);
|
|
209
|
+
} else {
|
|
210
|
+
const data = await res.json();
|
|
211
|
+
console.error(`Failed to restart: ${data.error ?? "unknown error"}`);
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
console.log(`Daemon not running. Start the agent manually: volute start ${agentName}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export {
|
|
218
|
+
run
|
|
219
|
+
};
|