volute 0.25.0 → 0.27.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 +28 -33
- package/dist/{activity-events-4O37J7PD.js → activity-events-BBIEA2F4.js} +2 -3
- package/dist/api.d.ts +886 -220
- package/dist/{archive-4ZQYK5MN.js → archive-UA4BDFXQ.js} +2 -2
- package/dist/{auth-HM2RSPY7.js → auth-D3OT2ARB.js} +3 -3
- package/dist/bridge-FQHZL3MC.js +206 -0
- package/dist/chat-MHJ3L6JQ.js +58 -0
- package/dist/{chunk-PHU4DEAJ.js → chunk-2WPW7OT6.js} +3 -3
- package/dist/{chunk-BOTQ25QT.js → chunk-2YP2TVDT.js} +138 -56
- package/dist/{chunk-DG7TO7EE.js → chunk-4WXYUOAK.js} +5 -7
- package/dist/{chunk-JTDFJWI2.js → chunk-AW7PFDVN.js} +5 -5
- package/dist/{chunk-2767L2RZ.js → chunk-EHYDTZTF.js} +6 -6
- package/dist/{chunk-ZSH4G2P5.js → chunk-GIE6CSN5.js} +17 -17
- package/dist/chunk-H7OZRFJB.js +432 -0
- package/dist/{chunk-ON3FF5JA.js → chunk-HDN7MNGD.js} +3 -3
- package/dist/chunk-IAYBDWVG.js +477 -0
- package/dist/chunk-IKRVFPWU.js +83 -0
- package/dist/{chunk-TRQEV3CD.js → chunk-JGFVMROS.js} +32 -6
- package/dist/{chunk-PHHKNGA3.js → chunk-JKOWNZ4P.js} +3 -3
- package/dist/{chunk-E7GOKNOT.js → chunk-K5NAC55T.js} +1 -1
- package/dist/{chunk-HFCBO2GL.js → chunk-KDGS53OS.js} +4 -4
- package/dist/chunk-KTLFDYPT.js +61 -0
- package/dist/{chunk-3AIBT4TW.js → chunk-LAC664WU.js} +30 -4
- package/dist/{chunk-PMX4EIJK.js → chunk-OQZH4PBB.js} +467 -1054
- package/dist/{chunk-SHSWYG2J.js → chunk-PHSAT7YL.js} +71 -58
- package/dist/chunk-RKQEHRBB.js +177 -0
- package/dist/{chunk-RVKR2R7F.js → chunk-SSI47XP2.js} +10 -2
- package/dist/chunk-T6HKBWXZ.js +23 -0
- package/dist/chunk-USUXRNVD.js +113 -0
- package/dist/{chunk-BFK6SOEJ.js → chunk-VIVMW2H2.js} +4 -4
- package/dist/{chunk-KTJGZ7M7.js → chunk-XBLSAVJF.js} +1 -1
- package/dist/chunk-ZYGKG6VC.js +22 -0
- package/dist/cli.js +51 -32
- package/dist/{cloud-sync-PPBBJDY6.js → cloud-sync-T7M3ESC3.js} +15 -12
- package/dist/connectors/discord-bridge.js +158 -0
- package/dist/connectors/slack-bridge.js +119 -0
- package/dist/connectors/telegram-bridge.js +133 -0
- package/dist/conversations-M2K4253F.js +55 -0
- package/dist/create-D7J73A6H.js +45 -0
- package/dist/{create-VDQJER52.js → create-QWV73WXD.js} +1 -1
- package/dist/{daemon-client-JOVQZ52X.js → daemon-client-I42FK2BF.js} +2 -2
- package/dist/{daemon-restart-FDNOZEAD.js → daemon-restart-M2QTYMEG.js} +7 -6
- package/dist/daemon.js +2247 -1085
- package/dist/db-IC4J52XQ.js +8 -0
- package/dist/{delete-2MRR4JX5.js → delete-4JYGD4VN.js} +1 -1
- package/dist/down-LVBXEULC.js +14 -0
- package/dist/{env-2FPOZK37.js → env-YJMUMFIY.js} +5 -5
- package/dist/{export-IKFAPRAO.js → export-BOJQWBMA.js} +4 -4
- package/dist/{file-KT3UIQM3.js → file-CR36YUPD.js} +4 -4
- package/dist/{history-46WZN5CN.js → history-XKRTAFS2.js} +7 -7
- package/dist/{import-TH26J76F.js → import-SRTQXBGH.js} +4 -4
- package/dist/join-J4QU42DL.js +66 -0
- package/dist/list-R73GENNL.js +40 -0
- package/dist/{log-6SGSSR3D.js → log-ABYNVYJ3.js} +4 -4
- package/dist/login-3QZNR2DF.js +46 -0
- package/dist/{login-UO6AOVEA.js → login-XX37I52P.js} +3 -3
- package/dist/logout-T53VKCPU.js +39 -0
- package/dist/{logout-UKD5LA37.js → logout-W4KOOBIT.js} +2 -2
- package/dist/{logs-HRBONI5I.js → logs-U35JR2KE.js} +7 -7
- package/dist/{merge-KSFJKX6T.js → merge-LNSMSAOF.js} +4 -4
- package/dist/message-delivery-LDXLGERA.js +25 -0
- package/dist/migrate-registry-to-db-XC7T5B7P.js +110 -0
- package/dist/{mind-YVWAHL2A.js → mind-DI33C74K.js} +25 -25
- package/dist/{mind-activity-tracker-NMDDEV3K.js → mind-activity-tracker-EN6XNXPF.js} +3 -4
- package/dist/{mind-manager-4NDNAYAB.js → mind-manager-M6EMUW5I.js} +6 -5
- package/dist/{mind-sleep-GHPTSAYN.js → mind-sleep-BTSWQNAC.js} +4 -4
- package/dist/{mind-wake-BJDJFMDF.js → mind-wake-SBAKIDVP.js} +4 -4
- package/dist/notes-XCER3I7M.js +220 -0
- package/dist/{package-3HF5MXU2.js → package-7WY6VKU3.js} +2 -1
- package/dist/{pages-Y6DRWUOJ.js → pages-6EBS6CBR.js} +2 -2
- package/dist/{publish-EEKTZBHW.js → publish-66UB2ZFY.js} +5 -5
- package/dist/{pull-D32SPFVU.js → pull-XCHJTM5M.js} +4 -4
- package/dist/read-36UFXN3G.js +46 -0
- package/dist/{register-U2UO6TC4.js → register-6B2CXTYM.js} +3 -3
- package/dist/{registry-D2BSQ2X5.js → registry-NDNOOYG4.js} +15 -9
- package/dist/{restart-5BMNV7KU.js → restart-6ESL3NBO.js} +6 -6
- package/dist/sandbox-TGBX22DS.js +19 -0
- package/dist/{schedule-YEFDLVMJ.js → schedule-QTJMFATP.js} +7 -7
- package/dist/{seed-6FEKB3YC.js → seed-SSUCYYDF.js} +2 -2
- package/dist/{send-IISDYFCL.js → send-ZNCJDSRP.js} +28 -36
- package/dist/service-6LIN3F3K.js +122 -0
- package/dist/setup-JG4QAEBV.js +371 -0
- package/dist/setup-JHL5ZEST.js +17 -0
- package/dist/{shared-LWMNTTZN.js → shared-ML5I4Q2A.js} +4 -4
- package/dist/{skill-T3EMR6IR.js → skill-AUAQTSP5.js} +7 -7
- package/dist/skills/dreaming/SKILL.md +68 -0
- package/dist/skills/dreaming/references/INSTALL.md +56 -0
- package/dist/skills/dreaming/scripts/dream.ts +289 -0
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +30 -0
- package/dist/skills/notes/SKILL.md +34 -0
- package/dist/skills/orientation/SKILL.md +3 -3
- package/dist/skills/volute-mind/SKILL.md +32 -30
- package/dist/sleep-manager-MWYHM5HV.js +29 -0
- package/dist/split-TKJ5OT3P.js +63 -0
- package/dist/{sprout-QJVGJDSH.js → sprout-IJVVKSJ2.js} +6 -7
- package/dist/{start-C7XITZ5O.js → start-EUJSS5R4.js} +4 -4
- package/dist/{status-SIRPLEZC.js → status-77YEPHMW.js} +5 -5
- package/dist/{status-LYS4NUOZ.js → status-7GA4SM4Y.js} +4 -4
- package/dist/{status-LV34BG6G.js → status-THLOBLWG.js} +2 -2
- package/dist/{stop-CVKBSLXY.js → stop-3XAITBBF.js} +6 -6
- package/dist/{tailscale-AJ4VL5XK.js → tailscale-NY5MUMY3.js} +1 -1
- package/dist/up-NKSMXBWR.js +17 -0
- package/dist/{update-7XCZMYBT.js → update-PTSH22AZ.js} +11 -11
- package/dist/{update-check-F5Z3ALXX.js → update-check-64FWC4Y2.js} +2 -2
- package/dist/{upgrade-7RUIXGOO.js → upgrade-HA47CS4C.js} +12 -5
- package/dist/variant-7TGZHOU3.js +41 -0
- package/dist/{version-notify-AZQMC32A.js → version-notify-5Z4MNR6M.js} +26 -28
- package/dist/web-assets/assets/index-CI5wgghI.css +1 -0
- package/dist/web-assets/assets/index-is5CvJWH.js +75 -0
- package/dist/web-assets/favicon.png +0 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0015_notes.sql +23 -0
- package/drizzle/0016_note_reactions_and_replies.sql +15 -0
- package/drizzle/0017_minds.sql +16 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +2 -1
- package/templates/_base/.init/.config/hooks/wake-context.sh +7 -0
- package/templates/_base/.init/.config/prompts.json +2 -2
- package/templates/_base/home/VOLUTE.md +5 -5
- package/templates/_base/src/lib/startup.ts +10 -2
- package/templates/claude/src/agent.ts +51 -1
- package/templates/claude/src/server.ts +1 -0
- package/templates/pi/package.json.tmpl +1 -0
- package/templates/pi/src/agent.ts +48 -1
- package/templates/pi/src/lib/subagents.ts +150 -0
- package/templates/pi/src/server.ts +1 -0
- package/dist/channel-HZOSHGNF.js +0 -260
- package/dist/chunk-33XAVCS4.js +0 -203
- package/dist/chunk-B2CPS4QU.js +0 -283
- package/dist/chunk-NWPT4ASZ.js +0 -89
- package/dist/chunk-SIAG3QMM.js +0 -42
- package/dist/chunk-WSLPZF72.js +0 -173
- package/dist/connector-M6XFI6GM.js +0 -147
- package/dist/connectors/discord.js +0 -177
- package/dist/connectors/slack.js +0 -181
- package/dist/connectors/telegram.js +0 -187
- package/dist/down-674SX2IZ.js +0 -14
- package/dist/message-delivery-XMGV3FUM.js +0 -23
- package/dist/service-FASYWLTC.js +0 -247
- package/dist/setup-BMLM2UTK.js +0 -230
- package/dist/sleep-manager-RKTFZPD3.js +0 -27
- package/dist/up-CJ26KQLN.js +0 -15
- package/dist/variant-UGREB4G5.js +0 -207
- package/dist/web-assets/assets/index-CGPSVu19.js +0 -69
- package/dist/web-assets/assets/index-V_rNDsM8.css +0 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
isSandboxEnabled,
|
|
4
|
+
wrapForSandbox
|
|
5
|
+
} from "./chunk-USUXRNVD.js";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
systemPrompts
|
|
9
|
-
} from "./chunk-33XAVCS4.js";
|
|
7
|
+
loadMergedEnv
|
|
8
|
+
} from "./chunk-2WPW7OT6.js";
|
|
10
9
|
import {
|
|
11
10
|
logger_default
|
|
12
11
|
} from "./chunk-YUIHSKR6.js";
|
|
@@ -14,16 +13,17 @@ import {
|
|
|
14
13
|
chownMindDir,
|
|
15
14
|
isIsolationEnabled,
|
|
16
15
|
wrapForIsolation
|
|
17
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RKQEHRBB.js";
|
|
18
17
|
import {
|
|
19
18
|
findMind,
|
|
20
|
-
|
|
19
|
+
getDb,
|
|
21
20
|
mindDir,
|
|
21
|
+
mindHistory,
|
|
22
22
|
setMindRunning,
|
|
23
|
-
setVariantRunning,
|
|
24
23
|
stateDir,
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
systemPrompts,
|
|
25
|
+
voluteSystemDir
|
|
26
|
+
} from "./chunk-H7OZRFJB.js";
|
|
27
27
|
|
|
28
28
|
// src/lib/daemon/mind-manager.ts
|
|
29
29
|
import { execFile, spawn } from "child_process";
|
|
@@ -82,8 +82,7 @@ var PROMPT_KEYS = [
|
|
|
82
82
|
"reply_instructions",
|
|
83
83
|
"channel_invite",
|
|
84
84
|
"pre_sleep",
|
|
85
|
-
"wake_summary"
|
|
86
|
-
"wake_trigger_summary"
|
|
85
|
+
"wake_summary"
|
|
87
86
|
];
|
|
88
87
|
var PROMPT_DEFAULTS = {
|
|
89
88
|
seed_soul: {
|
|
@@ -141,7 +140,7 @@ Have a conversation with the human. Explore what kind of mind you want to be. Wh
|
|
|
141
140
|
category: "mind"
|
|
142
141
|
},
|
|
143
142
|
reply_instructions: {
|
|
144
|
-
content: 'To reply to this message, use: volute send ${channel} "your message"',
|
|
143
|
+
content: 'To reply to this message, use: volute chat send ${channel} "your message"',
|
|
145
144
|
description: "First-message reply hint injected via hook",
|
|
146
145
|
variables: ["channel"],
|
|
147
146
|
category: "mind"
|
|
@@ -157,7 +156,7 @@ Further messages will be saved to \${filePath}
|
|
|
157
156
|
|
|
158
157
|
To accept, add to .config/routes.json:
|
|
159
158
|
Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
|
|
160
|
-
\${batchRecommendation}To respond, use: volute send \${channel} "your message"
|
|
159
|
+
\${batchRecommendation}To respond, use: volute chat send \${channel} "your message"
|
|
161
160
|
To reject, delete \${filePath}`,
|
|
162
161
|
description: "New channel notification template",
|
|
163
162
|
variables: [
|
|
@@ -179,15 +178,9 @@ To reject, delete \${filePath}`,
|
|
|
179
178
|
category: "system"
|
|
180
179
|
},
|
|
181
180
|
wake_summary: {
|
|
182
|
-
content: "Good morning \u2014 it's ${currentDate}. You slept from ${sleepTime} to now (${duration}).\n\n${
|
|
181
|
+
content: "Good morning \u2014 it's ${currentDate}. You slept from ${sleepTime} to now (${duration}).\n\n${sleepActivity}",
|
|
183
182
|
description: "Wake-up summary after scheduled sleep",
|
|
184
|
-
variables: ["currentDate", "sleepTime", "duration", "
|
|
185
|
-
category: "system"
|
|
186
|
-
},
|
|
187
|
-
wake_trigger_summary: {
|
|
188
|
-
content: "You were woken during sleep by a message on ${triggerChannel}. It's ${currentDate} \u2014 you've been asleep since ${sleepTime} (${duration}).\n\nYou have this full turn to respond and handle anything else. You'll return to sleep when you go idle.\n\n${queuedSummary}",
|
|
189
|
-
description: "Wake-up summary when woken by a trigger message",
|
|
190
|
-
variables: ["currentDate", "triggerChannel", "sleepTime", "duration", "queuedSummary"],
|
|
183
|
+
variables: ["currentDate", "sleepTime", "duration", "sleepActivity"],
|
|
191
184
|
category: "system"
|
|
192
185
|
}
|
|
193
186
|
};
|
|
@@ -295,6 +288,28 @@ var RotatingLog = class extends Writable {
|
|
|
295
288
|
}
|
|
296
289
|
};
|
|
297
290
|
|
|
291
|
+
// src/lib/daemon/mind-tokens.ts
|
|
292
|
+
import { randomUUID } from "crypto";
|
|
293
|
+
var tokenToMind = /* @__PURE__ */ new Map();
|
|
294
|
+
var mindToToken = /* @__PURE__ */ new Map();
|
|
295
|
+
function generateMindToken(mindName) {
|
|
296
|
+
revokeMindToken(mindName);
|
|
297
|
+
const token = randomUUID();
|
|
298
|
+
tokenToMind.set(token, mindName);
|
|
299
|
+
mindToToken.set(mindName, token);
|
|
300
|
+
return token;
|
|
301
|
+
}
|
|
302
|
+
function revokeMindToken(mindName) {
|
|
303
|
+
const token = mindToToken.get(mindName);
|
|
304
|
+
if (token) {
|
|
305
|
+
tokenToMind.delete(token);
|
|
306
|
+
mindToToken.delete(mindName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function resolveMindToken(token) {
|
|
310
|
+
return tokenToMind.get(token) ?? null;
|
|
311
|
+
}
|
|
312
|
+
|
|
298
313
|
// src/lib/daemon/restart-tracker.ts
|
|
299
314
|
var DEFAULT_MAX_ATTEMPTS = 5;
|
|
300
315
|
var DEFAULT_BASE_DELAY = 3e3;
|
|
@@ -352,25 +367,23 @@ var MindManager = class {
|
|
|
352
367
|
shuttingDown = false;
|
|
353
368
|
restartTracker = new RestartTracker();
|
|
354
369
|
pendingContext = /* @__PURE__ */ new Map();
|
|
355
|
-
resolveTarget(name) {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (!variant) throw new Error(`Unknown variant: ${variantName} (mind: ${baseName})`);
|
|
362
|
-
return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
|
|
370
|
+
async resolveTarget(name) {
|
|
371
|
+
const entry = await findMind(name);
|
|
372
|
+
if (!entry) throw new Error(`Unknown mind: ${name}`);
|
|
373
|
+
if (entry.parent) {
|
|
374
|
+
if (!entry.dir) throw new Error(`Variant ${name} has no directory`);
|
|
375
|
+
return { dir: entry.dir, port: entry.port, baseName: entry.parent };
|
|
363
376
|
}
|
|
364
|
-
const dir = mindDir(
|
|
377
|
+
const dir = mindDir(name);
|
|
365
378
|
if (!existsSync3(dir)) throw new Error(`Mind directory missing: ${dir}`);
|
|
366
|
-
return { dir, port: entry.port,
|
|
379
|
+
return { dir, port: entry.port, baseName: name };
|
|
367
380
|
}
|
|
368
381
|
async startMind(name) {
|
|
369
382
|
if (this.minds.has(name)) {
|
|
370
383
|
throw new Error(`Mind ${name} is already running`);
|
|
371
384
|
}
|
|
372
|
-
const target = this.resolveTarget(name);
|
|
373
|
-
const { dir,
|
|
385
|
+
const target = await this.resolveTarget(name);
|
|
386
|
+
const { dir, baseName } = target;
|
|
374
387
|
const port = target.port;
|
|
375
388
|
const pidFile = mindPidPath(name);
|
|
376
389
|
try {
|
|
@@ -420,6 +433,7 @@ var MindManager = class {
|
|
|
420
433
|
}
|
|
421
434
|
}
|
|
422
435
|
const logStream = new RotatingLog(resolve(logsDir, "mind.log"));
|
|
436
|
+
const mindToken = generateMindToken(name);
|
|
423
437
|
const mindEnv = loadMergedEnv(name);
|
|
424
438
|
const env = {
|
|
425
439
|
...process.env,
|
|
@@ -428,6 +442,7 @@ var MindManager = class {
|
|
|
428
442
|
VOLUTE_STATE_DIR: stateDir(name),
|
|
429
443
|
VOLUTE_MIND_DIR: dir,
|
|
430
444
|
VOLUTE_MIND_PORT: String(port),
|
|
445
|
+
VOLUTE_DAEMON_TOKEN: mindToken,
|
|
431
446
|
// Strip CLAUDECODE so the Agent SDK can spawn Claude Code subprocesses
|
|
432
447
|
CLAUDECODE: void 0
|
|
433
448
|
};
|
|
@@ -436,7 +451,16 @@ var MindManager = class {
|
|
|
436
451
|
}
|
|
437
452
|
const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
|
|
438
453
|
const tsxArgs = ["src/server.ts", "--port", String(port)];
|
|
439
|
-
|
|
454
|
+
let spawnCmd;
|
|
455
|
+
let spawnArgs;
|
|
456
|
+
if (isIsolationEnabled()) {
|
|
457
|
+
[spawnCmd, spawnArgs] = await wrapForIsolation(tsxBin, tsxArgs, name);
|
|
458
|
+
} else if (isSandboxEnabled()) {
|
|
459
|
+
[spawnCmd, spawnArgs] = await wrapForSandbox(tsxBin, tsxArgs, dir, name);
|
|
460
|
+
} else {
|
|
461
|
+
spawnCmd = tsxBin;
|
|
462
|
+
spawnArgs = tsxArgs;
|
|
463
|
+
}
|
|
440
464
|
const spawnOpts = {
|
|
441
465
|
cwd: dir,
|
|
442
466
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -486,11 +510,7 @@ var MindManager = class {
|
|
|
486
510
|
}
|
|
487
511
|
if (this.restartTracker.reset(name)) this.saveCrashAttempts();
|
|
488
512
|
this.setupCrashRecovery(name, child);
|
|
489
|
-
|
|
490
|
-
setVariantRunning(baseName, variantName, true);
|
|
491
|
-
} else {
|
|
492
|
-
setMindRunning(name, true);
|
|
493
|
-
}
|
|
513
|
+
await setMindRunning(name, true);
|
|
494
514
|
mlog.info(`started mind ${name} on port ${port}`);
|
|
495
515
|
await this.deliverPendingContext(name);
|
|
496
516
|
}
|
|
@@ -548,28 +568,24 @@ var MindManager = class {
|
|
|
548
568
|
if (this.shuttingDown || this.stopping.has(name)) return;
|
|
549
569
|
mlog.error(`mind ${name} exited with code ${code}`);
|
|
550
570
|
try {
|
|
551
|
-
const { getSleepManagerIfReady } = await import("./sleep-manager-
|
|
552
|
-
|
|
571
|
+
const { getSleepManagerIfReady } = await import("./sleep-manager-MWYHM5HV.js");
|
|
572
|
+
const sleepState = getSleepManagerIfReady()?.getState(name);
|
|
573
|
+
if (sleepState?.sleeping) {
|
|
553
574
|
mlog.info(`${name} is sleeping \u2014 skipping crash recovery`);
|
|
554
575
|
return;
|
|
555
576
|
}
|
|
556
577
|
} catch (err) {
|
|
557
578
|
mlog.warn(`failed to check sleep state for ${name}`, logger_default.errorData(err));
|
|
558
579
|
}
|
|
559
|
-
import("./mind-activity-tracker-
|
|
560
|
-
import("./activity-events-
|
|
580
|
+
import("./mind-activity-tracker-EN6XNXPF.js").then(({ markIdle }) => markIdle(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
|
|
581
|
+
import("./activity-events-BBIEA2F4.js").then(
|
|
561
582
|
({ publish }) => publish({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
|
|
562
583
|
).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
|
|
563
584
|
const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
|
|
564
585
|
this.saveCrashAttempts();
|
|
565
586
|
if (!shouldRestart) {
|
|
566
587
|
mlog.error(`${name} crashed ${attempt} times \u2014 giving up on restart`);
|
|
567
|
-
|
|
568
|
-
if (variant) {
|
|
569
|
-
setVariantRunning(base, variant, false);
|
|
570
|
-
} else {
|
|
571
|
-
setMindRunning(name, false);
|
|
572
|
-
}
|
|
588
|
+
await setMindRunning(name, false);
|
|
573
589
|
return;
|
|
574
590
|
}
|
|
575
591
|
mlog.info(
|
|
@@ -605,15 +621,11 @@ var MindManager = class {
|
|
|
605
621
|
}, 5e3);
|
|
606
622
|
});
|
|
607
623
|
this.stopping.delete(name);
|
|
624
|
+
revokeMindToken(name);
|
|
608
625
|
if (this.restartTracker.reset(name)) this.saveCrashAttempts();
|
|
609
626
|
rmSync2(mindPidPath(name), { force: true });
|
|
610
627
|
if (!this.shuttingDown) {
|
|
611
|
-
|
|
612
|
-
if (variantName) {
|
|
613
|
-
setVariantRunning(baseName, variantName, false);
|
|
614
|
-
} else {
|
|
615
|
-
setMindRunning(name, false);
|
|
616
|
-
}
|
|
628
|
+
await setMindRunning(name, false);
|
|
617
629
|
}
|
|
618
630
|
mlog.info(`stopped mind ${name}`);
|
|
619
631
|
}
|
|
@@ -633,7 +645,7 @@ var MindManager = class {
|
|
|
633
645
|
return [...this.minds.keys()];
|
|
634
646
|
}
|
|
635
647
|
get crashAttemptsPath() {
|
|
636
|
-
return resolve(
|
|
648
|
+
return resolve(voluteSystemDir(), "crash-attempts.json");
|
|
637
649
|
}
|
|
638
650
|
loadCrashAttempts() {
|
|
639
651
|
this.restartTracker.load(loadJsonMap(this.crashAttemptsPath));
|
|
@@ -696,6 +708,7 @@ export {
|
|
|
696
708
|
loadJsonMap,
|
|
697
709
|
saveJsonMap,
|
|
698
710
|
clearJsonMap,
|
|
711
|
+
resolveMindToken,
|
|
699
712
|
MindManager,
|
|
700
713
|
initMindManager,
|
|
701
714
|
getMindManager
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getBaseName,
|
|
4
|
+
validateMindName
|
|
5
|
+
} from "./chunk-H7OZRFJB.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/isolation.ts
|
|
8
|
+
import { execFileSync } from "child_process";
|
|
9
|
+
function isIsolationEnabled() {
|
|
10
|
+
return process.env.VOLUTE_ISOLATION === "user";
|
|
11
|
+
}
|
|
12
|
+
function mindUserName(mindName) {
|
|
13
|
+
const err = validateMindName(mindName);
|
|
14
|
+
if (err) throw new Error(`Invalid mind name for isolation: ${err}`);
|
|
15
|
+
const prefix = process.env.VOLUTE_USER_PREFIX ?? "mind-";
|
|
16
|
+
return `${prefix}${mindName}`;
|
|
17
|
+
}
|
|
18
|
+
function findNextMacId(type) {
|
|
19
|
+
const idField = type === "Users" ? "UniqueID" : "PrimaryGroupID";
|
|
20
|
+
let output;
|
|
21
|
+
try {
|
|
22
|
+
output = execFileSync("dscl", [".", "-list", `/${type}`, idField], { encoding: "utf-8" });
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Failed to query ${type} via dscl: ${err instanceof Error ? err.message : err}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const ids = /* @__PURE__ */ new Set();
|
|
29
|
+
for (const line of output.split("\n")) {
|
|
30
|
+
const parts = line.trim().split(/\s+/);
|
|
31
|
+
const id = parseInt(parts[parts.length - 1], 10);
|
|
32
|
+
if (!Number.isNaN(id)) ids.add(id);
|
|
33
|
+
}
|
|
34
|
+
let next = 401;
|
|
35
|
+
while (ids.has(next)) next++;
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
function getVoluteGroupGid() {
|
|
39
|
+
if (process.platform === "darwin") {
|
|
40
|
+
const output2 = execFileSync("dscl", [".", "-read", "/Groups/volute", "PrimaryGroupID"], {
|
|
41
|
+
encoding: "utf-8"
|
|
42
|
+
});
|
|
43
|
+
const match = output2.match(/PrimaryGroupID:\s*(\d+)/);
|
|
44
|
+
if (!match) throw new Error("Could not read volute group GID");
|
|
45
|
+
return parseInt(match[1], 10);
|
|
46
|
+
}
|
|
47
|
+
const output = execFileSync("getent", ["group", "volute"], { encoding: "utf-8" });
|
|
48
|
+
const gid = parseInt(output.split(":")[2], 10);
|
|
49
|
+
if (Number.isNaN(gid)) throw new Error("Could not read volute group GID");
|
|
50
|
+
return gid;
|
|
51
|
+
}
|
|
52
|
+
function ensureVoluteGroup(opts) {
|
|
53
|
+
if (!opts?.force && !isIsolationEnabled()) return;
|
|
54
|
+
if (process.platform === "darwin") {
|
|
55
|
+
try {
|
|
56
|
+
execFileSync("dscl", [".", "-read", "/Groups/volute"], { stdio: "ignore" });
|
|
57
|
+
return;
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
const gid = findNextMacId("Groups");
|
|
61
|
+
try {
|
|
62
|
+
execFileSync("dscl", [".", "-create", "/Groups/volute"]);
|
|
63
|
+
execFileSync("dscl", [".", "-create", "/Groups/volute", "PrimaryGroupID", String(gid)]);
|
|
64
|
+
execFileSync("dscl", [".", "-create", "/Groups/volute", "Password", "*"]);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67
|
+
throw new Error(`Failed to create volute group on macOS: ${msg}`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
execFileSync("getent", ["group", "volute"], { stdio: "ignore" });
|
|
73
|
+
} catch {
|
|
74
|
+
try {
|
|
75
|
+
execFileSync("groupadd", ["volute"], { stdio: ["ignore", "ignore", "pipe"] });
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const stderr = err?.stderr?.toString().trim();
|
|
78
|
+
throw new Error(`Failed to create volute group${stderr ? `: ${stderr}` : ""}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function createMindUser(name, homeDir) {
|
|
83
|
+
if (!isIsolationEnabled()) return;
|
|
84
|
+
const user = mindUserName(name);
|
|
85
|
+
try {
|
|
86
|
+
execFileSync("id", [user], { stdio: "ignore" });
|
|
87
|
+
return;
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
if (process.platform === "darwin") {
|
|
91
|
+
const uid = findNextMacId("Users");
|
|
92
|
+
const gid = getVoluteGroupGid();
|
|
93
|
+
const home = homeDir ?? "/var/empty";
|
|
94
|
+
try {
|
|
95
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`]);
|
|
96
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "UniqueID", String(uid)]);
|
|
97
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "PrimaryGroupID", String(gid)]);
|
|
98
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "UserShell", "/usr/bin/false"]);
|
|
99
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "NFSHomeDirectory", home]);
|
|
100
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "RealName", `Volute Mind: ${name}`]);
|
|
101
|
+
execFileSync("dscl", [".", "-create", `/Users/${user}`, "IsHidden", "1"]);
|
|
102
|
+
execFileSync("dscl", [".", "-append", "/Groups/volute", "GroupMembership", user]);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
+
throw new Error(`Failed to create user ${user} on macOS: ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const args = ["-r", "-M", "-G", "volute", "-s", "/usr/sbin/nologin"];
|
|
111
|
+
if (homeDir) args.push("-d", homeDir);
|
|
112
|
+
args.push(user);
|
|
113
|
+
execFileSync("useradd", args, {
|
|
114
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const stderr = err?.stderr?.toString().trim();
|
|
118
|
+
throw new Error(`Failed to create user ${user}${stderr ? `: ${stderr}` : ""}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function deleteMindUser(name) {
|
|
122
|
+
if (!isIsolationEnabled()) return;
|
|
123
|
+
const user = mindUserName(name);
|
|
124
|
+
if (process.platform === "darwin") {
|
|
125
|
+
try {
|
|
126
|
+
execFileSync("dscl", [".", "-delete", `/Users/${user}`], { stdio: "ignore" });
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
execFileSync("dscl", [".", "-delete", "/Groups/volute", "GroupMembership", user], {
|
|
131
|
+
stdio: "ignore"
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
execFileSync("userdel", [user], { stdio: "ignore" });
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function wrapForIsolation(cmd, args, mindName) {
|
|
143
|
+
if (!isIsolationEnabled()) return [cmd, args];
|
|
144
|
+
const baseName = await getBaseName(mindName);
|
|
145
|
+
const user = mindUserName(baseName);
|
|
146
|
+
if (process.platform === "darwin") {
|
|
147
|
+
return ["sudo", ["-u", user, "--", cmd, ...args]];
|
|
148
|
+
}
|
|
149
|
+
return ["runuser", ["-u", user, "--", cmd, ...args]];
|
|
150
|
+
}
|
|
151
|
+
function chownMindDir(dir, name) {
|
|
152
|
+
if (!isIsolationEnabled()) return;
|
|
153
|
+
const user = mindUserName(name);
|
|
154
|
+
const group = process.platform === "darwin" ? "volute" : user;
|
|
155
|
+
try {
|
|
156
|
+
execFileSync("chown", ["-R", `${user}:${group}`, dir], { stdio: ["ignore", "ignore", "pipe"] });
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const stderr = err?.stderr?.toString().trim();
|
|
159
|
+
throw new Error(`Failed to chown ${dir} to ${user}:${group}${stderr ? `: ${stderr}` : ""}`);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
execFileSync("chmod", ["700", dir], { stdio: ["ignore", "ignore", "pipe"] });
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const stderr = err?.stderr?.toString().trim();
|
|
165
|
+
throw new Error(`Failed to chmod ${dir}${stderr ? `: ${stderr}` : ""}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
isIsolationEnabled,
|
|
171
|
+
mindUserName,
|
|
172
|
+
ensureVoluteGroup,
|
|
173
|
+
createMindUser,
|
|
174
|
+
deleteMindUser,
|
|
175
|
+
wrapForIsolation,
|
|
176
|
+
chownMindDir
|
|
177
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/lib/prompt.ts
|
|
4
|
-
function
|
|
4
|
+
function rawPrompt(prompt, echo) {
|
|
5
5
|
process.stderr.write(prompt);
|
|
6
6
|
return new Promise((resolve) => {
|
|
7
7
|
let value = "";
|
|
@@ -23,6 +23,7 @@ function promptLine(prompt) {
|
|
|
23
23
|
value = value.slice(0, -1);
|
|
24
24
|
} else {
|
|
25
25
|
value += String.fromCharCode(byte);
|
|
26
|
+
if (echo) process.stderr.write(String.fromCharCode(byte));
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
};
|
|
@@ -31,7 +32,14 @@ function promptLine(prompt) {
|
|
|
31
32
|
process.stdin.on("data", onData);
|
|
32
33
|
});
|
|
33
34
|
}
|
|
35
|
+
function promptLine(prompt) {
|
|
36
|
+
return rawPrompt(prompt, true);
|
|
37
|
+
}
|
|
38
|
+
function promptPassword(prompt) {
|
|
39
|
+
return rawPrompt(prompt, false);
|
|
40
|
+
}
|
|
34
41
|
|
|
35
42
|
export {
|
|
36
|
-
promptLine
|
|
43
|
+
promptLine,
|
|
44
|
+
promptPassword
|
|
37
45
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/slugify.ts
|
|
4
|
+
function slugify(text) {
|
|
5
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
6
|
+
}
|
|
7
|
+
function buildVoluteSlug(opts) {
|
|
8
|
+
if (opts.convType === "channel" && opts.convName) {
|
|
9
|
+
return `volute:#${opts.convName}`;
|
|
10
|
+
}
|
|
11
|
+
const isDM = opts.participants.length === 2;
|
|
12
|
+
if (isDM) {
|
|
13
|
+
const other = opts.participants.find((p) => p.username !== opts.mindUsername);
|
|
14
|
+
const otherSlug = other ? slugify(other.username) : "";
|
|
15
|
+
return otherSlug ? `volute:@${otherSlug}` : `volute:${opts.conversationId}`;
|
|
16
|
+
}
|
|
17
|
+
return opts.convTitle ? `volute:${slugify(opts.convTitle)}` : `volute:${opts.conversationId}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
slugify,
|
|
22
|
+
buildVoluteSlug
|
|
23
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger_default
|
|
4
|
+
} from "./chunk-YUIHSKR6.js";
|
|
5
|
+
import {
|
|
6
|
+
readGlobalConfig
|
|
7
|
+
} from "./chunk-IKRVFPWU.js";
|
|
8
|
+
import {
|
|
9
|
+
getBaseName,
|
|
10
|
+
readRegistry,
|
|
11
|
+
voluteHome,
|
|
12
|
+
voluteSystemDir,
|
|
13
|
+
voluteUserHome
|
|
14
|
+
} from "./chunk-H7OZRFJB.js";
|
|
15
|
+
|
|
16
|
+
// src/lib/sandbox.ts
|
|
17
|
+
import { resolve } from "path";
|
|
18
|
+
var slog = logger_default.child("sandbox");
|
|
19
|
+
var sandboxManager = null;
|
|
20
|
+
function isSandboxEnabled() {
|
|
21
|
+
if (process.env.VOLUTE_SANDBOX === "0") return false;
|
|
22
|
+
return readGlobalConfig().setup?.isolation === "sandbox";
|
|
23
|
+
}
|
|
24
|
+
async function initSandbox() {
|
|
25
|
+
if (!isSandboxEnabled()) return;
|
|
26
|
+
try {
|
|
27
|
+
const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
|
|
28
|
+
const config = {
|
|
29
|
+
network: {
|
|
30
|
+
allowedDomains: ["*"],
|
|
31
|
+
deniedDomains: [],
|
|
32
|
+
allowLocalBinding: true
|
|
33
|
+
},
|
|
34
|
+
filesystem: {
|
|
35
|
+
denyRead: [],
|
|
36
|
+
allowWrite: [],
|
|
37
|
+
denyWrite: []
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
await SandboxManager.initialize(config);
|
|
41
|
+
sandboxManager = SandboxManager;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
slog.error(
|
|
44
|
+
"sandbox runtime not available \u2014 minds will run without sandbox isolation",
|
|
45
|
+
logger_default.errorData(err)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function buildDenyRead(mindName, mindDir) {
|
|
50
|
+
const home = voluteHome();
|
|
51
|
+
const userHome = process.env.HOME || "";
|
|
52
|
+
const mindsDir = process.env.VOLUTE_MINDS_DIR || resolve(home, "minds");
|
|
53
|
+
const deny = [];
|
|
54
|
+
deny.push(voluteSystemDir());
|
|
55
|
+
const userVoluteHome = voluteUserHome();
|
|
56
|
+
if (userVoluteHome !== home) {
|
|
57
|
+
deny.push(userVoluteHome);
|
|
58
|
+
} else {
|
|
59
|
+
deny.push(resolve(home, "systems.json"));
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const entries = await readRegistry();
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.name === await getBaseName(mindName)) continue;
|
|
65
|
+
const otherDir = resolve(mindsDir, entry.name);
|
|
66
|
+
if (otherDir !== mindDir) {
|
|
67
|
+
deny.push(otherDir);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
slog.warn("failed to read minds registry for deny-read list", logger_default.errorData(err));
|
|
72
|
+
}
|
|
73
|
+
if (userHome) {
|
|
74
|
+
deny.push(resolve(userHome, ".ssh"));
|
|
75
|
+
deny.push(resolve(userHome, ".aws"));
|
|
76
|
+
deny.push(resolve(userHome, ".gnupg"));
|
|
77
|
+
deny.push(resolve(userHome, ".config"));
|
|
78
|
+
}
|
|
79
|
+
return deny;
|
|
80
|
+
}
|
|
81
|
+
function shellEscape(s) {
|
|
82
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
83
|
+
}
|
|
84
|
+
async function wrapForSandbox(cmd, args, mindDir, mindName, allowWrite) {
|
|
85
|
+
if (!sandboxManager) return [cmd, args];
|
|
86
|
+
const denyRead = await buildDenyRead(mindName, mindDir);
|
|
87
|
+
const customConfig = {
|
|
88
|
+
filesystem: {
|
|
89
|
+
denyRead,
|
|
90
|
+
allowWrite: allowWrite ?? [mindDir],
|
|
91
|
+
denyWrite: []
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
const shellCmd = [cmd, ...args].map(shellEscape).join(" ");
|
|
96
|
+
const wrapped = await sandboxManager.wrapWithSandbox(shellCmd, void 0, customConfig);
|
|
97
|
+
return ["bash", ["-c", wrapped]];
|
|
98
|
+
} catch (err) {
|
|
99
|
+
slog.error(
|
|
100
|
+
`failed to sandbox mind ${mindName} \u2014 running without isolation`,
|
|
101
|
+
logger_default.errorData(err)
|
|
102
|
+
);
|
|
103
|
+
return [cmd, args];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
isSandboxEnabled,
|
|
109
|
+
initSandbox,
|
|
110
|
+
buildDenyRead,
|
|
111
|
+
shellEscape,
|
|
112
|
+
wrapForSandbox
|
|
113
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
activity,
|
|
4
|
-
getDb
|
|
5
|
-
} from "./chunk-33XAVCS4.js";
|
|
6
2
|
import {
|
|
7
3
|
logger_default
|
|
8
4
|
} from "./chunk-YUIHSKR6.js";
|
|
5
|
+
import {
|
|
6
|
+
activity,
|
|
7
|
+
getDb
|
|
8
|
+
} from "./chunk-H7OZRFJB.js";
|
|
9
9
|
|
|
10
10
|
// src/lib/events/activity-events.ts
|
|
11
11
|
var subscribers = /* @__PURE__ */ new Set();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/read-stdin.ts
|
|
4
|
+
import { isatty } from "tty";
|
|
5
|
+
async function readStdin() {
|
|
6
|
+
if (isatty(0)) return void 0;
|
|
7
|
+
const chunks = [];
|
|
8
|
+
try {
|
|
9
|
+
for await (const chunk of process.stdin) {
|
|
10
|
+
chunks.push(chunk);
|
|
11
|
+
}
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error(`Failed to read from stdin: ${err instanceof Error ? err.message : String(err)}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const text = Buffer.concat(chunks).toString().replace(/\r?\n$/, "");
|
|
17
|
+
return text || void 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
readStdin
|
|
22
|
+
};
|