openvole 0.3.0 → 0.3.2
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 +63 -20
- package/dist/cli.js +63 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/vole.png" alt="OpenVole" width="200">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/openvole/openvole/main/assets/vole.png" alt="OpenVole" width="200">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">OpenVole</h1>
|
|
@@ -26,8 +26,11 @@ A fresh OpenVole installation has zero tools, zero skills, zero opinions. This i
|
|
|
26
26
|
```bash
|
|
27
27
|
mkdir my-agent && cd my-agent
|
|
28
28
|
npm init -y
|
|
29
|
-
npm install openvole
|
|
29
|
+
npm install openvole
|
|
30
30
|
npx vole init
|
|
31
|
+
npx vole paw add @openvole/paw-ollama
|
|
32
|
+
npx vole paw add @openvole/paw-memory
|
|
33
|
+
npx vole paw add @openvole/paw-dashboard
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
Edit `vole.config.json`:
|
|
@@ -91,23 +94,23 @@ curl -fsSL https://raw.githubusercontent.com/openvole/openvole/main/presets/full
|
|
|
91
94
|
|
|
|
92
95
|
v
|
|
93
96
|
┌─────────────────────────────────────────────────────────────┐
|
|
94
|
-
│ VoleEngine
|
|
95
|
-
│
|
|
96
|
-
│ Tool Registry ──── Skill Registry ──── Paw Registry
|
|
97
|
-
│ | | |
|
|
97
|
+
│ VoleEngine │
|
|
98
|
+
│ │
|
|
99
|
+
│ Tool Registry ──── Skill Registry ──── Paw Registry │
|
|
100
|
+
│ | | | │
|
|
98
101
|
│ ┌────────────────────────────────────────────────┐ │
|
|
99
|
-
│ │ Agent Loop (per task)
|
|
100
|
-
│ │
|
|
102
|
+
│ │ Agent Loop (per task) │ │
|
|
103
|
+
│ │ │ │
|
|
101
104
|
│ │ PERCEIVE ─── THINK ─── ACT ─── OBSERVE │ │
|
|
102
|
-
│ │ | | | |
|
|
103
|
-
│ │ Enrich Brain Execute Process
|
|
104
|
-
│ │ context plans tools results
|
|
105
|
-
│ │
|
|
106
|
-
│
|
|
107
|
-
│
|
|
108
|
-
│ Task Queue ──── Scheduler ──── Message Bus
|
|
109
|
-
│
|
|
110
|
-
|
|
105
|
+
│ │ | | | | │ │
|
|
106
|
+
│ │ Enrich Brain Execute Process │ │
|
|
107
|
+
│ │ context plans tools results │ │
|
|
108
|
+
│ │ │ │
|
|
109
|
+
│ └────────────────────────────────────────────────┘ │
|
|
110
|
+
│ │
|
|
111
|
+
│ Task Queue ──── Scheduler ──── Message Bus │
|
|
112
|
+
│ │
|
|
113
|
+
└──────┬──────────┬──────────┬──────────┬─────────────────────┘
|
|
111
114
|
| | | |
|
|
112
115
|
[Brain Paw] [Channel] [Tools] [In-Process]
|
|
113
116
|
Ollama Telegram Browser Compact
|
|
@@ -141,7 +144,7 @@ Perceive → Think → Act → Observe → loop
|
|
|
141
144
|
**Paws are tool providers.** They connect OpenVole to the outside world — APIs, databases, browsers, messaging platforms. Each Paw runs in an isolated subprocess with capability-based permissions.
|
|
142
145
|
|
|
143
146
|
```bash
|
|
144
|
-
|
|
147
|
+
npx vole paw add @openvole/paw-telegram
|
|
145
148
|
```
|
|
146
149
|
|
|
147
150
|
### Skills
|
|
@@ -312,7 +315,15 @@ When context grows too large, `paw-compact` summarizes old messages while preser
|
|
|
312
315
|
|
|
313
316
|
### Dashboard
|
|
314
317
|
|
|
315
|
-
Real-time web UI
|
|
318
|
+
Real-time web UI — powered by `paw-dashboard`, another Paw you install like any other. Shows paws, tools, skills, tasks, schedules, and live events.
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npx vole paw add @openvole/paw-dashboard
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
<p align="center">
|
|
325
|
+
<img src="https://raw.githubusercontent.com/openvole/openvole/main/assets/example/paw-dashboard/paw-dashboard.png" alt="OpenVole Dashboard" width="800">
|
|
326
|
+
</p>
|
|
316
327
|
|
|
317
328
|
## Official Paws (22)
|
|
318
329
|
|
|
@@ -362,7 +373,8 @@ All paws live in [PawHub](https://github.com/openvole/pawhub) and are installed
|
|
|
362
373
|
Install from npm:
|
|
363
374
|
|
|
364
375
|
```bash
|
|
365
|
-
|
|
376
|
+
npx vole paw add @openvole/paw-telegram
|
|
377
|
+
npx vole paw add @openvole/paw-browser
|
|
366
378
|
```
|
|
367
379
|
|
|
368
380
|
## CLI
|
|
@@ -444,6 +456,37 @@ npx vole clawhub install summarize
|
|
|
444
456
|
└── HEARTBEAT.md ← recurring job definitions
|
|
445
457
|
```
|
|
446
458
|
|
|
459
|
+
## OpenVole vs OpenClaw
|
|
460
|
+
|
|
461
|
+
Both are open-source AI agent frameworks. Different philosophies, many shared concepts.
|
|
462
|
+
|
|
463
|
+
| | OpenVole | OpenClaw |
|
|
464
|
+
|---|---|---|
|
|
465
|
+
| **Philosophy** | Microkernel — empty core, everything is a plugin | Batteries-included — 25 built-in tools |
|
|
466
|
+
| **Core size** | ~60KB | ~8MB |
|
|
467
|
+
| **Skills** | SKILL.md (same format, compatible) | SKILL.md |
|
|
468
|
+
| **Skill marketplace** | ClawHub-compatible (`vole clawhub install`) | ClawHub (13K+ skills) |
|
|
469
|
+
| **Skill loading** | Progressive on-demand | Progressive on-demand |
|
|
470
|
+
| **Brain/LLM** | External Paw — core is LLM-ignorant | Configurable provider in core |
|
|
471
|
+
| **Brain options** | 5 (Ollama, Claude, OpenAI, Gemini, xAI) | Multi-provider with fallback chains |
|
|
472
|
+
| **Heartbeat** | HEARTBEAT.md + cron | HEARTBEAT.md + cron |
|
|
473
|
+
| **Memory** | Source-isolated (user/paw/heartbeat scoped) | Shared (no source isolation) |
|
|
474
|
+
| **Identity files** | BRAIN.md, SOUL.md, USER.md, AGENT.md | SOUL.md, USER.md, AGENTS.md |
|
|
475
|
+
| **MCP support** | Via Paw with auto-discovery + late registration | Native in core |
|
|
476
|
+
| **Channels** | 4 (Telegram, Slack, Discord, WhatsApp) | 20+ (WhatsApp, iMessage, Signal, etc.) |
|
|
477
|
+
| **Plugin isolation** | Subprocess sandbox + capability permissions | Optional Docker sandbox |
|
|
478
|
+
| **Tool profiles** | Per-source deny/allow lists | Channel sandboxing |
|
|
479
|
+
| **Scheduling** | Cron-based, persistent, Brain-initiated | Cron + heartbeat |
|
|
480
|
+
| **Sessions** | Per-session transcripts with auto-expiry | Built-in session keys |
|
|
481
|
+
| **Vault** | AES-256 encrypted, write-once, metadata | N/A (env vars) |
|
|
482
|
+
| **Dashboard** | Real-time web UI | Gateway web UI |
|
|
483
|
+
| **CLI** | `vole` (start/run/tool call/clawhub/skill) | `openclaw` |
|
|
484
|
+
| **Config** | Single JSON file | Single JSON file |
|
|
485
|
+
|
|
486
|
+
OpenVole is a newborn — a tiny vole just getting started. We share the same skill format, the same heartbeat pattern, and the same MCP ecosystem as OpenClaw. Skills written for one work on the other.
|
|
487
|
+
|
|
488
|
+
We're building something small, modular, and community-driven. If you like the microkernel approach — where every piece is a Paw you can swap, extend, or build yourself — come join us. Try it out, build a Paw, write a Skill, break things, and help this little vole grow.
|
|
489
|
+
|
|
447
490
|
## Philosophy
|
|
448
491
|
|
|
449
492
|
> **If it connects to something, it's a Paw.**
|
package/dist/cli.js
CHANGED
|
@@ -485,6 +485,19 @@ var init_task = __esm({
|
|
|
485
485
|
}
|
|
486
486
|
return false;
|
|
487
487
|
}
|
|
488
|
+
/** Cancel all queued tasks (for shutdown) */
|
|
489
|
+
cancelAll() {
|
|
490
|
+
while (this.queue.length > 0) {
|
|
491
|
+
const task = this.queue.shift();
|
|
492
|
+
task.status = "cancelled";
|
|
493
|
+
task.completedAt = Date.now();
|
|
494
|
+
this.completed.push(task);
|
|
495
|
+
this.bus.emit("task:cancelled", { taskId: task.id });
|
|
496
|
+
}
|
|
497
|
+
for (const task of this.running.values()) {
|
|
498
|
+
task.status = "cancelled";
|
|
499
|
+
}
|
|
500
|
+
}
|
|
488
501
|
/** Get all tasks (queued + running + completed) */
|
|
489
502
|
list() {
|
|
490
503
|
return [
|
|
@@ -612,7 +625,8 @@ var init_scheduler = __esm({
|
|
|
612
625
|
if (persisted.length > 0) {
|
|
613
626
|
logger4.info(`Restored ${persisted.length} schedule(s) from disk`);
|
|
614
627
|
}
|
|
615
|
-
} catch {
|
|
628
|
+
} catch (err) {
|
|
629
|
+
logger4.warn(`Could not restore schedules: ${err}`);
|
|
616
630
|
}
|
|
617
631
|
}
|
|
618
632
|
/** Create or replace a recurring schedule */
|
|
@@ -655,8 +669,9 @@ var init_scheduler = __esm({
|
|
|
655
669
|
createdAt
|
|
656
670
|
}));
|
|
657
671
|
}
|
|
658
|
-
/** Clear all schedules (for shutdown) */
|
|
672
|
+
/** Clear all schedules (for shutdown). Disables persistence so the file is never overwritten. */
|
|
659
673
|
clearAll() {
|
|
674
|
+
this.savePath = void 0;
|
|
660
675
|
for (const entry of this.schedules.values()) {
|
|
661
676
|
entry.job.stop();
|
|
662
677
|
}
|
|
@@ -665,16 +680,29 @@ var init_scheduler = __esm({
|
|
|
665
680
|
}
|
|
666
681
|
/** Save schedules to disk */
|
|
667
682
|
async persist() {
|
|
668
|
-
|
|
683
|
+
const targetPath = this.savePath;
|
|
684
|
+
if (!targetPath) return;
|
|
669
685
|
const toSave = Array.from(this.schedules.values()).filter((s) => s.id !== "__heartbeat__").map(({ id, input, cron, createdAt }) => ({
|
|
670
686
|
id,
|
|
671
687
|
input,
|
|
672
688
|
cron,
|
|
673
689
|
createdAt
|
|
674
690
|
}));
|
|
691
|
+
if (toSave.length === 0) {
|
|
692
|
+
try {
|
|
693
|
+
const existing = await fs3.readFile(targetPath, "utf-8");
|
|
694
|
+
const parsed = JSON.parse(existing);
|
|
695
|
+
if (parsed.length > 0) {
|
|
696
|
+
logger4.warn(`Refusing to overwrite ${parsed.length} persisted schedule(s) with empty list`);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
} catch {
|
|
700
|
+
}
|
|
701
|
+
}
|
|
675
702
|
try {
|
|
676
|
-
await fs3.mkdir(path3.dirname(
|
|
677
|
-
await fs3.writeFile(
|
|
703
|
+
await fs3.mkdir(path3.dirname(targetPath), { recursive: true });
|
|
704
|
+
await fs3.writeFile(targetPath, JSON.stringify(toSave, null, 2) + "\n", "utf-8");
|
|
705
|
+
logger4.debug(`Persisted ${toSave.length} schedule(s) to disk`);
|
|
678
706
|
} catch (err) {
|
|
679
707
|
logger4.error(`Failed to persist schedules: ${err}`);
|
|
680
708
|
}
|
|
@@ -2042,8 +2070,14 @@ var IpcTransport = class {
|
|
|
2042
2070
|
this.handlers.clear();
|
|
2043
2071
|
}
|
|
2044
2072
|
send(message) {
|
|
2073
|
+
if (this.disposed) return;
|
|
2045
2074
|
if (this.type === "ipc") {
|
|
2046
|
-
|
|
2075
|
+
try {
|
|
2076
|
+
if (this.childProcess.connected) {
|
|
2077
|
+
this.childProcess.send?.(message);
|
|
2078
|
+
}
|
|
2079
|
+
} catch {
|
|
2080
|
+
}
|
|
2047
2081
|
} else {
|
|
2048
2082
|
const json = JSON.stringify(message);
|
|
2049
2083
|
const header = `Content-Length: ${Buffer.byteLength(json)}\r
|
|
@@ -2876,6 +2910,7 @@ async function createEngine(projectRoot, options) {
|
|
|
2876
2910
|
rateLimiter
|
|
2877
2911
|
});
|
|
2878
2912
|
});
|
|
2913
|
+
let shuttingDown = false;
|
|
2879
2914
|
const engine = {
|
|
2880
2915
|
bus,
|
|
2881
2916
|
toolRegistry,
|
|
@@ -2886,10 +2921,12 @@ async function createEngine(projectRoot, options) {
|
|
|
2886
2921
|
config,
|
|
2887
2922
|
async start() {
|
|
2888
2923
|
engineLogger.info("Starting OpenVole...");
|
|
2924
|
+
const headless = options?.headless ?? false;
|
|
2889
2925
|
if (config.brain) {
|
|
2890
2926
|
} else {
|
|
2891
2927
|
engineLogger.info("No Brain Paw configured \u2014 Think step will be a no-op");
|
|
2892
2928
|
}
|
|
2929
|
+
const headlessSkipPatterns = ["paw-dashboard", "paw-telegram", "paw-slack", "paw-discord", "paw-whatsapp"];
|
|
2893
2930
|
const pawConfigs = config.paws.map(normalizePawConfig);
|
|
2894
2931
|
const brainConfig = pawConfigs.find((p) => p.name === config.brain);
|
|
2895
2932
|
const subprocessPaws = pawConfigs.filter(
|
|
@@ -2907,15 +2944,18 @@ async function createEngine(projectRoot, options) {
|
|
|
2907
2944
|
pawRegistry.setBrain("");
|
|
2908
2945
|
}
|
|
2909
2946
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
}
|
|
2947
|
+
const pawsToLoad = headless ? subprocessPaws.filter((p) => !headlessSkipPatterns.some((pat) => p.name.includes(pat))) : subprocessPaws;
|
|
2948
|
+
await Promise.all(pawsToLoad.map((pawConfig) => pawRegistry.load(pawConfig)));
|
|
2913
2949
|
for (const skillName of config.skills) {
|
|
2914
2950
|
await skillRegistry.load(skillName);
|
|
2915
2951
|
}
|
|
2916
2952
|
skillRegistry.resolve();
|
|
2917
|
-
|
|
2918
|
-
|
|
2953
|
+
if (!headless) {
|
|
2954
|
+
await scheduler.restore();
|
|
2955
|
+
} else {
|
|
2956
|
+
await scheduler.loadFromDisk();
|
|
2957
|
+
}
|
|
2958
|
+
if (config.heartbeat.enabled && !headless) {
|
|
2919
2959
|
const heartbeatMdPath = path10.resolve(projectRoot, ".openvole", "HEARTBEAT.md");
|
|
2920
2960
|
const heartbeatCron = `*/${config.heartbeat.intervalMinutes} * * * *`;
|
|
2921
2961
|
scheduler.add(
|
|
@@ -2946,8 +2986,11 @@ ${heartbeatContent}` : "Heartbeat wake-up. Check active skills and decide if any
|
|
|
2946
2986
|
taskQueue.enqueue(input, source, sessionId ? { sessionId } : void 0);
|
|
2947
2987
|
},
|
|
2948
2988
|
async shutdown() {
|
|
2989
|
+
if (shuttingDown) return;
|
|
2990
|
+
shuttingDown = true;
|
|
2949
2991
|
engineLogger.info("Shutting down...");
|
|
2950
2992
|
scheduler.clearAll();
|
|
2993
|
+
taskQueue.cancelAll();
|
|
2951
2994
|
for (const paw of pawRegistry.list()) {
|
|
2952
2995
|
await pawRegistry.unload(paw.name);
|
|
2953
2996
|
}
|
|
@@ -3097,7 +3140,7 @@ async function startInteractive(projectRoot) {
|
|
|
3097
3140
|
process.on("SIGTERM", gracefulShutdown);
|
|
3098
3141
|
}
|
|
3099
3142
|
async function runSingle(projectRoot, input) {
|
|
3100
|
-
const engine = await createEngine(projectRoot);
|
|
3143
|
+
const engine = await createEngine(projectRoot, { headless: true });
|
|
3101
3144
|
await engine.start();
|
|
3102
3145
|
engine.run(input);
|
|
3103
3146
|
return new Promise((resolve7) => {
|
|
@@ -3837,7 +3880,14 @@ async function handleClawHubCommand(args, projectRoot) {
|
|
|
3837
3880
|
logger15.info(` requires env: ${definition.requires.env.join(", ")}`);
|
|
3838
3881
|
}
|
|
3839
3882
|
} else {
|
|
3840
|
-
|
|
3883
|
+
const fsCheck = await import("fs/promises");
|
|
3884
|
+
const pluginJsonPath = path11.resolve(projectRoot, ".openvole", "skills", "clawhub", skillDir ?? skillName, "openclaw.plugin.json");
|
|
3885
|
+
try {
|
|
3886
|
+
await fsCheck.access(pluginJsonPath);
|
|
3887
|
+
logger15.error(`"${skillName}" is an OpenClaw plugin, not a skill. OpenVole does not support OpenClaw plugins \u2014 use Paws instead.`);
|
|
3888
|
+
} catch {
|
|
3889
|
+
logger15.warn(`Installed "${skillName}" but could not parse SKILL.md \u2014 add to vole.config.json manually`);
|
|
3890
|
+
}
|
|
3841
3891
|
}
|
|
3842
3892
|
break;
|
|
3843
3893
|
}
|