volute 0.13.1 → 0.14.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/dist/{channel-JZJJRRWT.js → channel-SLURLIRV.js} +28 -28
- package/dist/{chunk-KRJ6KCBI.js → chunk-2Y77MCFG.js} +3 -3
- package/dist/{chunk-AA5TDLXB.js → chunk-3FC42ZBM.js} +24 -24
- package/dist/{chunk-YYUSXARD.js → chunk-6BDNWYKG.js} +2 -2
- package/dist/{chunk-DCZZFBCX.js → chunk-GR3OG4QK.js} +2 -2
- package/dist/{chunk-FE5O5RSL.js → chunk-GSPWIM5E.js} +25 -25
- package/dist/{chunk-K2ZT3YQ3.js → chunk-J52CJCVI.js} +72 -75
- package/dist/{chunk-LGSW7T7K.js → chunk-M77QBTEH.js} +60 -57
- package/dist/{chunk-KXOFPDO6.js → chunk-MVSXRMJJ.js} +1 -1
- package/dist/chunk-NAOW2CLO.js +15 -0
- package/dist/{chunk-VQIJUR43.js → chunk-OJQ47SCA.js} +1 -1
- package/dist/{chunk-O4BN3ZIY.js → chunk-OYSZNX5I.js} +7 -7
- package/dist/{chunk-AOSGW3MX.js → chunk-PDLAZJGC.js} +28 -28
- package/dist/{chunk-XUA3JUFK.js → chunk-PO5Q2AYN.js} +2 -2
- package/dist/{chunk-6BQHEIDO.js → chunk-QJIIHU32.js} +2 -2
- package/dist/{chunk-NXT67PPK.js → chunk-ZCEYUUID.js} +19 -19
- package/dist/cli.js +42 -42
- package/dist/{connector-WFT5KK67.js → connector-JFAHYFQX.js} +21 -21
- package/dist/connectors/discord.js +7 -7
- package/dist/connectors/slack.js +7 -7
- package/dist/connectors/telegram.js +9 -9
- package/dist/{create-HT47ZH5T.js → create-ZWHCRT5F.js} +7 -7
- package/dist/{daemon-client-DEF7IFEJ.js → daemon-client-ODKDUYDE.js} +2 -2
- package/dist/{daemon-restart-43RQBPF2.js → daemon-restart-KNJHZ7FP.js} +6 -6
- package/dist/daemon.js +2281 -1998
- package/dist/{delete-YG3RVURA.js → delete-6G6WEX4F.js} +8 -8
- package/dist/down-A56B5JLK.js +14 -0
- package/dist/{env-BQYYF4YL.js → env-6LXDUZDA.js} +25 -25
- package/dist/{history-I4KIKIUX.js → history-LKCJJMUV.js} +7 -7
- package/dist/{import-UHCK6PRC.js → import-EDGRLIGO.js} +3 -3
- package/dist/{logs-2DWFES6A.js → logs-GYOR3L2L.js} +8 -8
- package/dist/mind-OJN6RBZW.js +79 -0
- package/dist/mind-manager-PN5SUDJ4.js +15 -0
- package/dist/{package-GUQVVO3V.js → package-EYUA3AMC.js} +4 -4
- package/dist/{restart-6PE3GWYZ.js → restart-YFAWFS5T.js} +9 -9
- package/dist/{schedule-5AYTQM3N.js → schedule-AGYLDMNS.js} +17 -17
- package/dist/{seed-3QQVFMBU.js → seed-AP4Q7RZ7.js} +9 -9
- package/dist/{send-FPFW7J5Q.js → send-SV4K2TDE.js} +32 -24
- package/dist/{service-5X5EKPVM.js → service-U7MZ2H7F.js} +4 -4
- package/dist/{setup-Y6P7RFQ4.js → setup-DJKIZKGW.js} +22 -27
- package/dist/{sprout-VOUJ4Y3I.js → sprout-2V3MWONK.js} +18 -18
- package/dist/{start-ICPSQ2ZK.js → start-3YYRXBKP.js} +7 -7
- package/dist/{status-JBT7ENQN.js → status-VSFZYX7S.js} +14 -14
- package/dist/{stop-IXJGAG4T.js → stop-AA5K5LYG.js} +9 -9
- package/dist/{up-PR7SGODX.js → up-SUZ6C5PY.js} +5 -5
- package/dist/{update-GU6JYDSN.js → update-YAGN5ODG.js} +5 -5
- package/dist/{update-check-MUPZYTW4.js → update-check-APLTH4IN.js} +2 -2
- package/dist/{upgrade-275LKIEG.js → upgrade-KXZCQSZN.js} +8 -10
- package/dist/{variant-RE45F2IY.js → variant-X5QFG6KK.js} +30 -30
- package/dist/web-assets/assets/index-CeFLp8DZ.js +307 -0
- package/dist/web-assets/index.html +1 -1
- package/drizzle/0005_rename_agents_to_minds.sql +11 -0
- package/drizzle/meta/0005_snapshot.json +410 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/templates/_base/.init/.config/scripts/session-reader.ts +1 -1
- package/templates/_base/.init/SOUL.md +1 -1
- package/templates/_base/_skills/memory/SKILL.md +1 -1
- package/templates/_base/_skills/orientation/SKILL.md +6 -6
- package/templates/_base/_skills/sessions/SKILL.md +1 -1
- package/templates/_base/_skills/{volute-agent → volute-mind}/SKILL.md +21 -21
- package/templates/_base/home/VOLUTE.md +7 -7
- package/templates/_base/src/lib/auto-commit.ts +1 -1
- package/templates/_base/src/lib/auto-reply.ts +1 -1
- package/templates/_base/src/lib/daemon-client.ts +8 -8
- package/templates/_base/src/lib/router.ts +6 -6
- package/templates/_base/src/lib/routing.ts +9 -6
- package/templates/_base/src/lib/startup.ts +1 -1
- package/templates/_base/src/lib/volute-server.ts +1 -1
- package/templates/{agent-sdk → claude}/.init/CLAUDE.md +3 -3
- package/templates/{agent-sdk → claude}/src/agent.ts +10 -10
- package/templates/{agent-sdk → claude}/src/lib/hooks/pre-compact.ts +2 -2
- package/templates/{agent-sdk → claude}/src/lib/session-store.ts +2 -2
- package/templates/{agent-sdk → claude}/src/lib/stream-consumer.ts +1 -1
- package/templates/{agent-sdk → claude}/src/server.ts +4 -4
- package/templates/pi/.init/{AGENTS.md → MINDS.md} +3 -3
- package/templates/pi/home/.config/config.json.tmpl +1 -1
- package/templates/pi/src/agent.ts +12 -12
- package/templates/pi/src/lib/event-handler.ts +39 -4
- package/templates/pi/src/server.ts +3 -3
- package/dist/agent-IUSETOXJ.js +0 -79
- package/dist/agent-manager-BLAMP6YV.js +0 -15
- package/dist/chunk-AZEL2IEK.js +0 -15
- package/dist/down-36YCOZ7V.js +0 -14
- package/dist/web-assets/assets/index-TqXd1QOX.js +0 -307
- /package/templates/{agent-sdk → claude}/.init/.claude/settings.json +0 -0
- /package/templates/{agent-sdk → claude}/.init/.config/routes.json +0 -0
- /package/templates/{agent-sdk → claude}/package.json.tmpl +0 -0
- /package/templates/{agent-sdk → claude}/src/lib/content.ts +0 -0
- /package/templates/{agent-sdk → claude}/src/lib/hooks/auto-commit.ts +0 -0
- /package/templates/{agent-sdk → claude}/src/lib/hooks/identity-reload.ts +0 -0
- /package/templates/{agent-sdk → claude}/src/lib/hooks/session-context.ts +0 -0
- /package/templates/{agent-sdk → claude}/src/lib/message-channel.ts +0 -0
- /package/templates/{agent-sdk → claude}/volute-template.json +0 -0
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadMergedEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OYSZNX5I.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
chownMindDir,
|
|
7
7
|
isIsolationEnabled,
|
|
8
8
|
wrapForIsolation
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ZCEYUUID.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
findAgent,
|
|
11
|
+
findMind,
|
|
13
12
|
findVariant,
|
|
14
|
-
|
|
13
|
+
mindDir,
|
|
14
|
+
setMindRunning,
|
|
15
15
|
setVariantRunning,
|
|
16
16
|
stateDir,
|
|
17
17
|
voluteHome
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-M77QBTEH.js";
|
|
19
19
|
|
|
20
|
-
// src/lib/
|
|
20
|
+
// src/lib/mind-manager.ts
|
|
21
21
|
import { execFile, spawn } from "child_process";
|
|
22
|
-
import {
|
|
22
|
+
import { 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
|
|
|
@@ -113,41 +113,41 @@ var RotatingLog = class extends Writable {
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
// src/lib/
|
|
116
|
+
// src/lib/mind-manager.ts
|
|
117
117
|
var execFileAsync = promisify(execFile);
|
|
118
|
-
function
|
|
119
|
-
return resolve(stateDir(name), "
|
|
118
|
+
function mindPidPath(name) {
|
|
119
|
+
return resolve(stateDir(name), "mind.pid");
|
|
120
120
|
}
|
|
121
121
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
122
122
|
var BASE_RESTART_DELAY = 3e3;
|
|
123
123
|
var MAX_RESTART_DELAY = 6e4;
|
|
124
|
-
var
|
|
125
|
-
|
|
124
|
+
var MindManager = class {
|
|
125
|
+
minds = /* @__PURE__ */ new Map();
|
|
126
126
|
stopping = /* @__PURE__ */ new Set();
|
|
127
127
|
shuttingDown = false;
|
|
128
128
|
restartAttempts = /* @__PURE__ */ new Map();
|
|
129
129
|
pendingContext = /* @__PURE__ */ new Map();
|
|
130
130
|
resolveTarget(name) {
|
|
131
131
|
const [baseName, variantName] = name.split("@", 2);
|
|
132
|
-
const entry =
|
|
133
|
-
if (!entry) throw new Error(`Unknown
|
|
132
|
+
const entry = findMind(baseName);
|
|
133
|
+
if (!entry) throw new Error(`Unknown mind: ${baseName}`);
|
|
134
134
|
if (variantName) {
|
|
135
135
|
const variant = findVariant(baseName, variantName);
|
|
136
|
-
if (!variant) throw new Error(`Unknown variant: ${variantName} (
|
|
136
|
+
if (!variant) throw new Error(`Unknown variant: ${variantName} (mind: ${baseName})`);
|
|
137
137
|
return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
|
|
138
138
|
}
|
|
139
|
-
const dir =
|
|
140
|
-
if (!existsSync3(dir)) throw new Error(`
|
|
139
|
+
const dir = mindDir(baseName);
|
|
140
|
+
if (!existsSync3(dir)) throw new Error(`Mind directory missing: ${dir}`);
|
|
141
141
|
return { dir, port: entry.port, isVariant: false, baseName };
|
|
142
142
|
}
|
|
143
|
-
async
|
|
144
|
-
if (this.
|
|
145
|
-
throw new Error(`
|
|
143
|
+
async startMind(name) {
|
|
144
|
+
if (this.minds.has(name)) {
|
|
145
|
+
throw new Error(`Mind ${name} is already running`);
|
|
146
146
|
}
|
|
147
147
|
const target = this.resolveTarget(name);
|
|
148
148
|
const { dir, isVariant, baseName, variantName } = target;
|
|
149
149
|
const port = target.port;
|
|
150
|
-
const pidFile =
|
|
150
|
+
const pidFile = mindPidPath(name);
|
|
151
151
|
try {
|
|
152
152
|
if (existsSync3(pidFile)) {
|
|
153
153
|
const stalePid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
|
|
@@ -156,12 +156,12 @@ var AgentManager = class {
|
|
|
156
156
|
process.kill(stalePid, 0);
|
|
157
157
|
const { stdout } = await execFileAsync("ps", ["-p", String(stalePid), "-o", "args="]);
|
|
158
158
|
if (stdout.includes("server.ts")) {
|
|
159
|
-
console.error(`[daemon] killing stale
|
|
159
|
+
console.error(`[daemon] killing stale mind process ${stalePid} for ${name}`);
|
|
160
160
|
process.kill(-stalePid, "SIGTERM");
|
|
161
161
|
await new Promise((r) => setTimeout(r, 500));
|
|
162
162
|
} else {
|
|
163
163
|
console.error(
|
|
164
|
-
`[daemon] stale PID ${stalePid} for ${name} is not
|
|
164
|
+
`[daemon] stale PID ${stalePid} for ${name} is not a mind process, skipping`
|
|
165
165
|
);
|
|
166
166
|
}
|
|
167
167
|
} catch (err) {
|
|
@@ -184,35 +184,32 @@ var AgentManager = class {
|
|
|
184
184
|
}
|
|
185
185
|
} catch {
|
|
186
186
|
}
|
|
187
|
-
const
|
|
188
|
-
const logsDir = resolve(
|
|
187
|
+
const mindStateDir = stateDir(name);
|
|
188
|
+
const logsDir = resolve(mindStateDir, "logs");
|
|
189
189
|
mkdirSync(logsDir, { recursive: true });
|
|
190
190
|
if (isIsolationEnabled()) {
|
|
191
191
|
try {
|
|
192
|
-
|
|
192
|
+
chownMindDir(mindStateDir, baseName);
|
|
193
193
|
} catch (err) {
|
|
194
194
|
throw new Error(
|
|
195
|
-
`Cannot start
|
|
195
|
+
`Cannot start mind ${name}: failed to set ownership on state directory ${mindStateDir}: ${err instanceof Error ? err.message : err}`
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
const logStream = new RotatingLog(resolve(logsDir, "
|
|
200
|
-
const
|
|
199
|
+
const logStream = new RotatingLog(resolve(logsDir, "mind.log"));
|
|
200
|
+
const mindEnv = loadMergedEnv(name);
|
|
201
201
|
const env = {
|
|
202
202
|
...process.env,
|
|
203
|
-
...
|
|
204
|
-
|
|
203
|
+
...mindEnv,
|
|
204
|
+
VOLUTE_MIND: name,
|
|
205
205
|
VOLUTE_STATE_DIR: stateDir(name),
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
VOLUTE_MIND_DIR: dir,
|
|
207
|
+
VOLUTE_MIND_PORT: String(port),
|
|
208
|
+
// Strip CLAUDECODE so the Agent SDK can spawn Claude Code subprocesses
|
|
209
|
+
CLAUDECODE: void 0
|
|
208
210
|
};
|
|
209
211
|
if (isIsolationEnabled()) {
|
|
210
212
|
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);
|
|
215
|
-
}
|
|
216
213
|
}
|
|
217
214
|
const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
|
|
218
215
|
const tsxArgs = ["src/server.ts", "--port", String(port)];
|
|
@@ -224,13 +221,13 @@ var AgentManager = class {
|
|
|
224
221
|
env
|
|
225
222
|
};
|
|
226
223
|
const child = spawn(spawnCmd, spawnArgs, spawnOpts);
|
|
227
|
-
this.
|
|
224
|
+
this.minds.set(name, { child, port });
|
|
228
225
|
child.stdout?.pipe(logStream);
|
|
229
226
|
child.stderr?.pipe(logStream);
|
|
230
227
|
try {
|
|
231
228
|
await new Promise((resolve2, reject) => {
|
|
232
229
|
const timeout = setTimeout(() => {
|
|
233
|
-
reject(new Error(`
|
|
230
|
+
reject(new Error(`Mind ${name} did not start within 30s`));
|
|
234
231
|
}, 3e4);
|
|
235
232
|
function checkOutput(data) {
|
|
236
233
|
if (data.toString().match(/listening on :\d+/)) {
|
|
@@ -246,11 +243,11 @@ var AgentManager = class {
|
|
|
246
243
|
});
|
|
247
244
|
child.on("exit", (code) => {
|
|
248
245
|
clearTimeout(timeout);
|
|
249
|
-
reject(new Error(`
|
|
246
|
+
reject(new Error(`Mind ${name} exited with code ${code} during startup`));
|
|
250
247
|
});
|
|
251
248
|
});
|
|
252
249
|
} catch (err) {
|
|
253
|
-
this.
|
|
250
|
+
this.minds.delete(name);
|
|
254
251
|
try {
|
|
255
252
|
child.kill();
|
|
256
253
|
} catch {
|
|
@@ -269,9 +266,9 @@ var AgentManager = class {
|
|
|
269
266
|
if (isVariant) {
|
|
270
267
|
setVariantRunning(baseName, variantName, true);
|
|
271
268
|
} else {
|
|
272
|
-
|
|
269
|
+
setMindRunning(name, true);
|
|
273
270
|
}
|
|
274
|
-
console.error(`[daemon] started
|
|
271
|
+
console.error(`[daemon] started mind ${name} on port ${port}`);
|
|
275
272
|
await this.deliverPendingContext(name);
|
|
276
273
|
}
|
|
277
274
|
setPendingContext(name, context) {
|
|
@@ -280,7 +277,7 @@ var AgentManager = class {
|
|
|
280
277
|
async deliverPendingContext(name) {
|
|
281
278
|
const context = this.pendingContext.get(name);
|
|
282
279
|
if (!context) return;
|
|
283
|
-
const tracked = this.
|
|
280
|
+
const tracked = this.minds.get(name);
|
|
284
281
|
if (!tracked) return;
|
|
285
282
|
this.pendingContext.delete(name);
|
|
286
283
|
const parts = [];
|
|
@@ -288,7 +285,7 @@ var AgentManager = class {
|
|
|
288
285
|
parts.push(`[system] Variant "${context.name}" has been merged and you have been restarted.`);
|
|
289
286
|
} else if (context.type === "sprouted") {
|
|
290
287
|
parts.push(
|
|
291
|
-
"[system] You've sprouted. You now have full
|
|
288
|
+
"[system] You've sprouted. You now have full capabilities \u2014 connectors, schedules, variants, and the complete volute CLI. Check your new skills for details."
|
|
292
289
|
);
|
|
293
290
|
} else {
|
|
294
291
|
parts.push("[system] You have been restarted.");
|
|
@@ -311,9 +308,9 @@ var AgentManager = class {
|
|
|
311
308
|
}
|
|
312
309
|
setupCrashRecovery(name, child) {
|
|
313
310
|
child.on("exit", async (code) => {
|
|
314
|
-
this.
|
|
311
|
+
this.minds.delete(name);
|
|
315
312
|
if (this.shuttingDown || this.stopping.has(name)) return;
|
|
316
|
-
console.error(`[daemon]
|
|
313
|
+
console.error(`[daemon] mind ${name} exited with code ${code}`);
|
|
317
314
|
const attempts = this.restartAttempts.get(name) ?? 0;
|
|
318
315
|
if (attempts >= MAX_RESTART_ATTEMPTS) {
|
|
319
316
|
console.error(`[daemon] ${name} crashed ${attempts} times \u2014 giving up on restart`);
|
|
@@ -321,7 +318,7 @@ var AgentManager = class {
|
|
|
321
318
|
if (variant) {
|
|
322
319
|
setVariantRunning(base, variant, false);
|
|
323
320
|
} else {
|
|
324
|
-
|
|
321
|
+
setMindRunning(name, false);
|
|
325
322
|
}
|
|
326
323
|
return;
|
|
327
324
|
}
|
|
@@ -333,18 +330,18 @@ var AgentManager = class {
|
|
|
333
330
|
);
|
|
334
331
|
setTimeout(() => {
|
|
335
332
|
if (this.shuttingDown) return;
|
|
336
|
-
this.
|
|
333
|
+
this.startMind(name).catch((err) => {
|
|
337
334
|
console.error(`[daemon] failed to restart ${name}:`, err);
|
|
338
335
|
});
|
|
339
336
|
}, delay);
|
|
340
337
|
});
|
|
341
338
|
}
|
|
342
|
-
async
|
|
343
|
-
const tracked = this.
|
|
339
|
+
async stopMind(name) {
|
|
340
|
+
const tracked = this.minds.get(name);
|
|
344
341
|
if (!tracked) return;
|
|
345
342
|
this.stopping.add(name);
|
|
346
343
|
const { child } = tracked;
|
|
347
|
-
this.
|
|
344
|
+
this.minds.delete(name);
|
|
348
345
|
await new Promise((resolve2) => {
|
|
349
346
|
child.on("exit", () => resolve2());
|
|
350
347
|
try {
|
|
@@ -362,31 +359,31 @@ var AgentManager = class {
|
|
|
362
359
|
});
|
|
363
360
|
this.stopping.delete(name);
|
|
364
361
|
if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
|
|
365
|
-
rmSync2(
|
|
362
|
+
rmSync2(mindPidPath(name), { force: true });
|
|
366
363
|
if (!this.shuttingDown) {
|
|
367
364
|
const [baseName, variantName] = name.split("@", 2);
|
|
368
365
|
if (variantName) {
|
|
369
366
|
setVariantRunning(baseName, variantName, false);
|
|
370
367
|
} else {
|
|
371
|
-
|
|
368
|
+
setMindRunning(name, false);
|
|
372
369
|
}
|
|
373
370
|
}
|
|
374
|
-
console.error(`[daemon] stopped
|
|
371
|
+
console.error(`[daemon] stopped mind ${name}`);
|
|
375
372
|
}
|
|
376
|
-
async
|
|
377
|
-
await this.
|
|
378
|
-
await this.
|
|
373
|
+
async restartMind(name) {
|
|
374
|
+
await this.stopMind(name);
|
|
375
|
+
await this.startMind(name);
|
|
379
376
|
}
|
|
380
377
|
async stopAll() {
|
|
381
378
|
this.shuttingDown = true;
|
|
382
|
-
const names = [...this.
|
|
383
|
-
await Promise.all(names.map((name) => this.
|
|
379
|
+
const names = [...this.minds.keys()];
|
|
380
|
+
await Promise.all(names.map((name) => this.stopMind(name)));
|
|
384
381
|
}
|
|
385
382
|
isRunning(name) {
|
|
386
|
-
return this.
|
|
383
|
+
return this.minds.has(name);
|
|
387
384
|
}
|
|
388
|
-
|
|
389
|
-
return [...this.
|
|
385
|
+
getRunningMinds() {
|
|
386
|
+
return [...this.minds.keys()];
|
|
390
387
|
}
|
|
391
388
|
get crashAttemptsPath() {
|
|
392
389
|
return resolve(voluteHome(), "crash-attempts.json");
|
|
@@ -429,22 +426,22 @@ async function killProcessOnPort(port) {
|
|
|
429
426
|
}
|
|
430
427
|
}
|
|
431
428
|
var instance = null;
|
|
432
|
-
function
|
|
433
|
-
if (instance) throw new Error("
|
|
434
|
-
instance = new
|
|
429
|
+
function initMindManager() {
|
|
430
|
+
if (instance) throw new Error("MindManager already initialized");
|
|
431
|
+
instance = new MindManager();
|
|
435
432
|
return instance;
|
|
436
433
|
}
|
|
437
|
-
function
|
|
438
|
-
if (!instance) instance = new
|
|
434
|
+
function getMindManager() {
|
|
435
|
+
if (!instance) instance = new MindManager();
|
|
439
436
|
return instance;
|
|
440
437
|
}
|
|
441
438
|
|
|
442
439
|
export {
|
|
440
|
+
RotatingLog,
|
|
443
441
|
loadJsonMap,
|
|
444
442
|
saveJsonMap,
|
|
445
443
|
clearJsonMap,
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
getAgentManager
|
|
444
|
+
MindManager,
|
|
445
|
+
initMindManager,
|
|
446
|
+
getMindManager
|
|
450
447
|
};
|
|
@@ -20,51 +20,54 @@ function voluteHome() {
|
|
|
20
20
|
return resolve(homedir(), ".volute");
|
|
21
21
|
}
|
|
22
22
|
function ensureVoluteHome() {
|
|
23
|
-
const
|
|
24
|
-
mkdirSync(
|
|
23
|
+
const mindsBase = process.env.VOLUTE_MINDS_DIR ?? resolve(voluteHome(), "minds");
|
|
24
|
+
mkdirSync(mindsBase, { recursive: true });
|
|
25
25
|
}
|
|
26
26
|
function readRegistry() {
|
|
27
|
-
const registryPath = resolve(voluteHome(), "
|
|
27
|
+
const registryPath = resolve(voluteHome(), "minds.json");
|
|
28
28
|
if (!existsSync(registryPath)) return [];
|
|
29
29
|
try {
|
|
30
30
|
const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
31
|
-
return entries.map((e) => ({
|
|
31
|
+
return entries.map((e) => ({
|
|
32
|
+
...e,
|
|
33
|
+
running: e.running ?? false,
|
|
34
|
+
stage: e.stage ?? "sprouted"
|
|
35
|
+
}));
|
|
32
36
|
} catch {
|
|
33
37
|
return [];
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
function writeRegistry(entries) {
|
|
37
41
|
ensureVoluteHome();
|
|
38
|
-
const registryPath = resolve(voluteHome(), "
|
|
42
|
+
const registryPath = resolve(voluteHome(), "minds.json");
|
|
39
43
|
const tmpPath = `${registryPath}.tmp`;
|
|
40
44
|
writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}
|
|
41
45
|
`);
|
|
42
46
|
renameSync(tmpPath, registryPath);
|
|
43
47
|
}
|
|
44
|
-
var
|
|
45
|
-
var
|
|
46
|
-
function
|
|
47
|
-
if (!name) return "
|
|
48
|
-
if (name.length >
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return "Agent name must start with alphanumeric and contain only alphanumeric, dots, dashes, or underscores";
|
|
48
|
+
var MIND_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
49
|
+
var MIND_NAME_MAX = 64;
|
|
50
|
+
function validateMindName(name) {
|
|
51
|
+
if (!name) return "Mind name is required";
|
|
52
|
+
if (name.length > MIND_NAME_MAX) return `Mind name must be at most ${MIND_NAME_MAX} characters`;
|
|
53
|
+
if (!MIND_NAME_RE.test(name)) {
|
|
54
|
+
return "Mind name must start with alphanumeric and contain only alphanumeric, dots, dashes, or underscores";
|
|
52
55
|
}
|
|
53
56
|
return null;
|
|
54
57
|
}
|
|
55
|
-
function
|
|
56
|
-
const err =
|
|
58
|
+
function addMind(name, port, stage) {
|
|
59
|
+
const err = validateMindName(name);
|
|
57
60
|
if (err) throw new Error(err);
|
|
58
61
|
const entries = readRegistry();
|
|
59
62
|
const filtered = entries.filter((e) => e.name !== name);
|
|
60
63
|
filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false, stage });
|
|
61
64
|
writeRegistry(filtered);
|
|
62
65
|
}
|
|
63
|
-
function
|
|
66
|
+
function removeMind(name) {
|
|
64
67
|
const entries = readRegistry();
|
|
65
68
|
writeRegistry(entries.filter((e) => e.name !== name));
|
|
66
69
|
}
|
|
67
|
-
function
|
|
70
|
+
function setMindRunning(name, running) {
|
|
68
71
|
const entries = readRegistry();
|
|
69
72
|
const entry = entries.find((e) => e.name === name);
|
|
70
73
|
if (entry) {
|
|
@@ -72,7 +75,7 @@ function setAgentRunning(name, running) {
|
|
|
72
75
|
writeRegistry(entries);
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
function
|
|
78
|
+
function setMindStage(name, stage) {
|
|
76
79
|
const entries = readRegistry();
|
|
77
80
|
const entry = entries.find((e) => e.name === name);
|
|
78
81
|
if (entry) {
|
|
@@ -80,14 +83,14 @@ function setAgentStage(name, stage) {
|
|
|
80
83
|
writeRegistry(entries);
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
|
-
function
|
|
86
|
+
function findMind(name) {
|
|
84
87
|
return readRegistry().find((e) => e.name === name);
|
|
85
88
|
}
|
|
86
|
-
function
|
|
87
|
-
if (process.env.
|
|
88
|
-
return resolve(process.env.
|
|
89
|
+
function mindDir(name) {
|
|
90
|
+
if (process.env.VOLUTE_MINDS_DIR) {
|
|
91
|
+
return resolve(process.env.VOLUTE_MINDS_DIR, name);
|
|
89
92
|
}
|
|
90
|
-
return resolve(voluteHome(), "
|
|
93
|
+
return resolve(voluteHome(), "minds", name);
|
|
91
94
|
}
|
|
92
95
|
function stateDir(name) {
|
|
93
96
|
return resolve(voluteHome(), "state", name);
|
|
@@ -112,20 +115,20 @@ function daemonLoopback() {
|
|
|
112
115
|
if (host === "::") return "[::1]";
|
|
113
116
|
return host;
|
|
114
117
|
}
|
|
115
|
-
function
|
|
118
|
+
function resolveMind(name) {
|
|
116
119
|
const [baseName, variantName] = name.split("@", 2);
|
|
117
|
-
const entry =
|
|
120
|
+
const entry = findMind(baseName);
|
|
118
121
|
if (!entry) {
|
|
119
|
-
throw new Error(`Unknown
|
|
122
|
+
throw new Error(`Unknown mind: ${baseName}`);
|
|
120
123
|
}
|
|
121
|
-
const dir =
|
|
124
|
+
const dir = mindDir(baseName);
|
|
122
125
|
if (!existsSync(dir)) {
|
|
123
|
-
throw new Error(`
|
|
126
|
+
throw new Error(`Mind directory missing: ${dir}`);
|
|
124
127
|
}
|
|
125
128
|
if (variantName) {
|
|
126
129
|
const variant = findVariant(baseName, variantName);
|
|
127
130
|
if (!variant) {
|
|
128
|
-
throw new Error(`Unknown variant: ${variantName} (
|
|
131
|
+
throw new Error(`Unknown variant: ${variantName} (mind: ${baseName})`);
|
|
129
132
|
}
|
|
130
133
|
return { entry: { ...entry, port: variant.port }, dir: variant.path };
|
|
131
134
|
}
|
|
@@ -150,59 +153,59 @@ function writeAllVariants(all) {
|
|
|
150
153
|
writeFileSync2(variantsPath(), `${JSON.stringify(all, null, 2)}
|
|
151
154
|
`);
|
|
152
155
|
}
|
|
153
|
-
function readVariants(
|
|
154
|
-
return readAllVariants()[
|
|
156
|
+
function readVariants(mindName) {
|
|
157
|
+
return readAllVariants()[mindName] ?? [];
|
|
155
158
|
}
|
|
156
|
-
function writeVariants(
|
|
159
|
+
function writeVariants(mindName, variants) {
|
|
157
160
|
const all = readAllVariants();
|
|
158
161
|
if (variants.length === 0) {
|
|
159
|
-
delete all[
|
|
162
|
+
delete all[mindName];
|
|
160
163
|
} else {
|
|
161
|
-
all[
|
|
164
|
+
all[mindName] = variants;
|
|
162
165
|
}
|
|
163
166
|
writeAllVariants(all);
|
|
164
167
|
}
|
|
165
|
-
function addVariant(
|
|
166
|
-
const variants = readVariants(
|
|
168
|
+
function addVariant(mindName, variant) {
|
|
169
|
+
const variants = readVariants(mindName);
|
|
167
170
|
const filtered = variants.filter((v) => v.name !== variant.name);
|
|
168
171
|
filtered.push(variant);
|
|
169
|
-
writeVariants(
|
|
172
|
+
writeVariants(mindName, filtered);
|
|
170
173
|
}
|
|
171
|
-
function removeVariant(
|
|
172
|
-
const variants = readVariants(
|
|
174
|
+
function removeVariant(mindName, name) {
|
|
175
|
+
const variants = readVariants(mindName);
|
|
173
176
|
writeVariants(
|
|
174
|
-
|
|
177
|
+
mindName,
|
|
175
178
|
variants.filter((v) => v.name !== name)
|
|
176
179
|
);
|
|
177
180
|
}
|
|
178
|
-
function findVariant(
|
|
179
|
-
return readVariants(
|
|
181
|
+
function findVariant(mindName, name) {
|
|
182
|
+
return readVariants(mindName).find((v) => v.name === name);
|
|
180
183
|
}
|
|
181
|
-
function setVariantRunning(
|
|
184
|
+
function setVariantRunning(mindName, variantName, running) {
|
|
182
185
|
const all = readAllVariants();
|
|
183
|
-
const variants = all[
|
|
186
|
+
const variants = all[mindName] ?? [];
|
|
184
187
|
const variant = variants.find((v) => v.name === variantName);
|
|
185
188
|
if (variant) {
|
|
186
189
|
variant.running = running;
|
|
187
|
-
all[
|
|
190
|
+
all[mindName] = variants;
|
|
188
191
|
writeAllVariants(all);
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
function getAllRunningVariants() {
|
|
192
195
|
const all = readAllVariants();
|
|
193
196
|
const result = [];
|
|
194
|
-
for (const [
|
|
197
|
+
for (const [mindName, variants] of Object.entries(all)) {
|
|
195
198
|
for (const variant of variants) {
|
|
196
199
|
if (variant.running) {
|
|
197
|
-
result.push({
|
|
200
|
+
result.push({ mindName, variant });
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
}
|
|
201
204
|
return result;
|
|
202
205
|
}
|
|
203
|
-
function removeAllVariants(
|
|
206
|
+
function removeAllVariants(mindName) {
|
|
204
207
|
const all = readAllVariants();
|
|
205
|
-
delete all[
|
|
208
|
+
delete all[mindName];
|
|
206
209
|
writeAllVariants(all);
|
|
207
210
|
}
|
|
208
211
|
async function checkHealth(port) {
|
|
@@ -242,15 +245,15 @@ export {
|
|
|
242
245
|
voluteHome,
|
|
243
246
|
ensureVoluteHome,
|
|
244
247
|
readRegistry,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
validateMindName,
|
|
249
|
+
addMind,
|
|
250
|
+
removeMind,
|
|
251
|
+
setMindRunning,
|
|
252
|
+
setMindStage,
|
|
253
|
+
findMind,
|
|
254
|
+
mindDir,
|
|
252
255
|
stateDir,
|
|
253
256
|
nextPort,
|
|
254
257
|
daemonLoopback,
|
|
255
|
-
|
|
258
|
+
resolveMind
|
|
256
259
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/resolve-mind-name.ts
|
|
4
|
+
function resolveMindName(flags) {
|
|
5
|
+
const name = flags.mind || process.env.VOLUTE_MIND;
|
|
6
|
+
if (!name) {
|
|
7
|
+
console.error("No mind specified. Provide a name or set VOLUTE_MIND.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
return name;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
resolveMindName
|
|
15
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
stateDir,
|
|
4
4
|
voluteHome
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-M77QBTEH.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/env.ts
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -10,8 +10,8 @@ import { dirname, resolve } from "path";
|
|
|
10
10
|
function sharedEnvPath() {
|
|
11
11
|
return resolve(voluteHome(), "env.json");
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
return resolve(stateDir(
|
|
13
|
+
function mindEnvPath(mindName) {
|
|
14
|
+
return resolve(stateDir(mindName), "env.json");
|
|
15
15
|
}
|
|
16
16
|
function readEnv(path) {
|
|
17
17
|
if (!existsSync(path)) return {};
|
|
@@ -26,15 +26,15 @@ function writeEnv(path, env) {
|
|
|
26
26
|
writeFileSync(path, `${JSON.stringify(env, null, 2)}
|
|
27
27
|
`, { mode: 384 });
|
|
28
28
|
}
|
|
29
|
-
function loadMergedEnv(
|
|
29
|
+
function loadMergedEnv(mindName) {
|
|
30
30
|
const shared = readEnv(sharedEnvPath());
|
|
31
|
-
const
|
|
32
|
-
return { ...shared, ...
|
|
31
|
+
const mind = readEnv(mindEnvPath(mindName));
|
|
32
|
+
return { ...shared, ...mind };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export {
|
|
36
36
|
sharedEnvPath,
|
|
37
|
-
|
|
37
|
+
mindEnvPath,
|
|
38
38
|
readEnv,
|
|
39
39
|
writeEnv,
|
|
40
40
|
loadMergedEnv
|