zubo 0.1.24 → 0.1.26

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.25 - 2026-02-17
4
+
5
+ - Added `zubo eval` reliability command with deterministic checks for slash commands, memory explainability, and dry-run safety.
6
+ - Added unified slash command write-actions:
7
+ - `/model set <provider/model>`
8
+ - `/permissions set <tool> <auto|confirm|deny>`
9
+ - `/budget pause|resume`
10
+ - Added configurable memory retrieval tuning:
11
+ - `memoryRetrieval.contextTopK`
12
+ - `memoryRetrieval.minConfidence`
13
+ - Added configurable runtime tool policy controls:
14
+ - `toolScopes.allowed`
15
+ - `toolScopes.dryRunByDefault`
16
+ - `toolPermissions.<tool>`
17
+ - Updated dashboard settings UI with memory retrieval and tool safety controls, including preset buttons and inline guidance.
18
+ - Improved memory explainability display in dashboard and memory search outputs (match type, confidence, reasons).
19
+ - Updated front-facing docs (`README`, CLI, config, memory docs) for new commands and settings.
20
+ - Added CI gate for `zubo eval`.
package/README.md CHANGED
@@ -28,7 +28,8 @@
28
28
 
29
29
  - **11+ LLM providers** — Anthropic, OpenAI, Google Gemini, Ollama, Groq, Together, OpenRouter, DeepSeek, xAI, Fireworks, LM Studio, and any OpenAI-compatible endpoint. Smart routing sends simple queries to fast models automatically.
30
30
  - **7 channels** — Telegram, Discord, Slack, WhatsApp, Signal, Email, Web Chat
31
- - **Persistent memory** — Vector + full-text hybrid search with ONNX embeddings and FTS5. Remembers every conversation, preference, and fact — forever.
31
+ - **Persistent memory** — Vector + full-text hybrid search with ONNX embeddings and FTS5. Remembers every conversation, preference, and fact — forever.
32
+ - **Memory explainability** — Memory matches include confidence and why they were selected (keyword, semantic, or hybrid match).
32
33
  - **25+ built-in tools** — Web search (Brave + DuckDuckGo), file ops, code execution, APIs, sub-agent delegation, knowledge graph, memory pruning, reminders, and automatic failover between providers.
33
34
  - **Extensible skills** — Build custom skills in TypeScript. Share them on the registry. Install community skills with one command.
34
35
  - **9 integrations** — GitHub, Google (Gmail, Calendar, Docs, Drive, Sheets), Notion, Linear, Jira, Slack, Twitter + Claude Code and MCP
@@ -36,7 +37,8 @@
36
37
  - **Natural language scheduling** — "Every weekday at 9am" just works. Cron jobs, heartbeat, proactive tasks.
37
38
  - **Voice** — Speech-to-text (Whisper, local whisper.cpp), text-to-speech (OpenAI, ElevenLabs), and continuous voice conversation mode
38
39
  - **Personal tools** — Todos, notes, preferences, topics, and follow-ups — all manageable from the dashboard or via chat
39
- - **Dashboard** — Built-in web UI with analytics, memory management, Ollama model manager, personal tools, and settings
40
+ - **Dashboard** — Built-in web UI with analytics, memory management, Ollama model manager, personal tools, and settings
41
+ - **Safety controls** — Tool scope allowlists and dry-run-by-default mode for risky tools, configurable in the dashboard
40
42
  - **Document ingestion** — Upload PDF, DOCX, XLSX, PPTX, TXT, CSV, JSON, and more
41
43
  - **Budget controls** — Daily/monthly spending limits with per-model cost tracking
42
44
  - **100% local** — SQLite database, local vector store. Your data never leaves your machine.
@@ -128,12 +130,28 @@ zubo model [provider/model] Show or switch LLM
128
130
  zubo skills Manage skills
129
131
  zubo install <name> Install from registry
130
132
  zubo search <query> Search the registry
131
- zubo voice Continuous voice conversation mode
132
- zubo auth create-key Create an API key
133
- zubo export / import Backup and restore
133
+ zubo voice Continuous voice conversation mode
134
+ zubo eval Run reliability + safety checks
135
+ zubo auth create-key Create an API key
136
+ zubo export / import Backup and restore
134
137
  ```
135
138
 
136
- Full reference at [zubo.bot/docs/cli.html](https://zubo.bot/docs/cli.html).
139
+ Full reference at [zubo.bot/docs/cli.html](https://zubo.bot/docs/cli.html).
140
+
141
+ ## Unified Slash Commands
142
+
143
+ Across WebChat, Telegram, Discord, Slack, and other channels:
144
+
145
+ - `/help` — list available commands
146
+ - `/status` — runtime status
147
+ - `/memory <query>` — search saved memory with confidence metadata
148
+ - `/model` — show current provider/model
149
+ - `/model set <provider/model>` — switch active model at runtime
150
+ - `/tools [filter]` — list available tools
151
+ - `/permissions <tool>` — view tool permission + scopes
152
+ - `/permissions set <tool> <auto|confirm|deny>` — override tool permission
153
+ - `/budget` — view budget usage and limits
154
+ - `/budget pause|resume` — pause/resume budget enforcement
137
155
 
138
156
  ## Contributing
139
157
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zubo",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Your AI agent that never forgets. Persistent memory, 25+ tools, 7 channels, 11+ LLM providers — runs entirely on your machine.",
5
5
  "license": "MIT",
6
6
  "author": "thomaskanze",
@@ -32,6 +32,7 @@
32
32
  "logs": "bun run src/index.ts logs",
33
33
  "logs:follow": "bun run src/index.ts logs --follow",
34
34
  "model": "bun run src/index.ts model",
35
+ "eval": "bun run src/index.ts eval",
35
36
  "skills": "bun run src/index.ts skills",
36
37
  "dev": "bun run --watch src/index.ts start",
37
38
  "desktop:dev": "cd desktop && npm run dev",
@@ -1525,8 +1525,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1525
1525
  </div>
1526
1526
  </div>
1527
1527
 
1528
- <div class="settings-section">
1529
- <h3 class="settings-title" data-tooltip="How often Zubo checks for tasks">Background Check Interval</h3>
1528
+ <div class="settings-section">
1529
+ <h3 class="settings-title" data-tooltip="How often Zubo checks for tasks">Background Check Interval</h3>
1530
1530
  <p class="settings-desc">How often Zubo checks for reminders, scheduled tasks, and updates. Default: every 30 minutes.</p>
1531
1531
  <div class="settings-grid">
1532
1532
  <div class="settings-field">
@@ -1537,11 +1537,58 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
1537
1537
  <div style="margin-top: 16px; display: flex; gap: 10px; align-items: center;">
1538
1538
  <button class="btn btn-primary" onclick="saveHeartbeat()">Save</button>
1539
1539
  <span id="heartbeat-status" class="status-text"></span>
1540
- </div>
1541
- </div>
1542
-
1543
- <div class="settings-section">
1544
- <h3 class="settings-title">Configuration</h3>
1540
+ </div>
1541
+ </div>
1542
+
1543
+ <div class="settings-section">
1544
+ <h3 class="settings-title">Memory Retrieval</h3>
1545
+ <p class="settings-desc">Control how many memory chunks are injected into chat context and the minimum confidence threshold.</p>
1546
+ <div class="settings-grid">
1547
+ <div class="settings-field">
1548
+ <label class="settings-label" for="memory-context-topk">Context Top-K</label>
1549
+ <input id="memory-context-topk" type="number" class="settings-input" min="1" max="10" step="1" placeholder="3">
1550
+ </div>
1551
+ <div class="settings-field">
1552
+ <label class="settings-label" for="memory-min-confidence">Min Confidence (0-1)</label>
1553
+ <input id="memory-min-confidence" type="number" class="settings-input" min="0" max="1" step="0.05" placeholder="0">
1554
+ </div>
1555
+ </div>
1556
+ <div style="margin-top: 16px; display: flex; gap: 10px; align-items: center;">
1557
+ <button class="btn btn-primary" onclick="saveMemoryRetrievalSettings()">Save</button>
1558
+ <button class="btn btn-ghost" onclick="applyMemoryPreset('balanced')">Balanced</button>
1559
+ <button class="btn btn-ghost" onclick="applyMemoryPreset('strict')">Strict</button>
1560
+ <span id="memory-retrieval-status" class="status-text"></span>
1561
+ </div>
1562
+ <p class="settings-desc" style="margin-top:10px;margin-bottom:0;">Recommended: <code>Top-K 3-5</code> and <code>min confidence 0.2-0.35</code>.</p>
1563
+ </div>
1564
+
1565
+ <div class="settings-section">
1566
+ <h3 class="settings-title">Tool Safety</h3>
1567
+ <p class="settings-desc">Limit tool scopes and optionally force dry-run mode by default for risky tools.</p>
1568
+ <div class="settings-grid">
1569
+ <div class="settings-field">
1570
+ <label class="settings-label" for="tool-scopes-allowed">Allowed Scopes (comma-separated)</label>
1571
+ <input id="tool-scopes-allowed" type="text" class="settings-input" placeholder="memory,network_read,filesystem_read">
1572
+ </div>
1573
+ <div class="settings-field">
1574
+ <label class="settings-label" for="tool-scopes-dry-run">Dry-Run By Default</label>
1575
+ <select id="tool-scopes-dry-run" class="settings-select">
1576
+ <option value="false">No</option>
1577
+ <option value="true">Yes</option>
1578
+ </select>
1579
+ </div>
1580
+ </div>
1581
+ <div style="margin-top: 16px; display: flex; gap: 10px; align-items: center;">
1582
+ <button class="btn btn-primary" onclick="saveToolScopeSettings()">Save</button>
1583
+ <button class="btn btn-ghost" onclick="applyToolScopePreset('safe')">Safe</button>
1584
+ <button class="btn btn-ghost" onclick="applyToolScopePreset('balanced')">Balanced</button>
1585
+ <span id="tool-scopes-status" class="status-text"></span>
1586
+ </div>
1587
+ <p class="settings-desc" style="margin-top:10px;margin-bottom:0;">Leave blank to allow all scopes. Use presets to start with least privilege.</p>
1588
+ </div>
1589
+
1590
+ <div class="settings-section">
1591
+ <h3 class="settings-title">Configuration</h3>
1545
1592
  <p class="settings-desc">Manage your full config by editing <code>~/.zubo/config.json</code> directly, or re-run <code>zubo setup</code>.</p>
1546
1593
  </div>
1547
1594
  </div>
@@ -2841,20 +2888,30 @@ function renderMemoryItems(results, container) {
2841
2888
  return;
2842
2889
  }
2843
2890
  document.getElementById('memory-count').textContent = String(results.length);
2844
- results.forEach(function(r) {
2845
- var item = document.createElement('div');
2846
- item.className = 'memory-item';
2847
- var src = document.createElement('div');
2848
- src.className = 'source';
2849
- src.textContent = r.source || '';
2850
- var cnt = document.createElement('div');
2851
- cnt.className = 'content';
2852
- cnt.textContent = r.content;
2853
- item.appendChild(src);
2854
- item.appendChild(cnt);
2855
- container.appendChild(item);
2856
- });
2857
- }
2891
+ results.forEach(function(r) {
2892
+ var item = document.createElement('div');
2893
+ item.className = 'memory-item';
2894
+ var src = document.createElement('div');
2895
+ src.className = 'source';
2896
+ var sourceBits = [r.source || ''];
2897
+ if (r.matchType) sourceBits.push(String(r.matchType));
2898
+ if (typeof r.confidence === 'number') sourceBits.push('conf ' + Math.round(r.confidence * 100) + '%');
2899
+ src.textContent = sourceBits.filter(Boolean).join(' • ');
2900
+ var cnt = document.createElement('div');
2901
+ cnt.className = 'content';
2902
+ cnt.textContent = r.content;
2903
+ item.appendChild(src);
2904
+ if (r.reasons && r.reasons.length) {
2905
+ var why = document.createElement('div');
2906
+ why.className = 'source';
2907
+ why.style.marginTop = '6px';
2908
+ why.textContent = 'Reason: ' + r.reasons.join(', ');
2909
+ item.appendChild(why);
2910
+ }
2911
+ item.appendChild(cnt);
2912
+ container.appendChild(item);
2913
+ });
2914
+ }
2858
2915
 
2859
2916
  function loadRecentMemories() {
2860
2917
  api('/memory/recent').then(function(data) {
@@ -3501,7 +3558,7 @@ function wipeData(type) {
3501
3558
  // --- SETTINGS ---
3502
3559
  var settingsProviders = [];
3503
3560
 
3504
- function loadSettings() {
3561
+ function loadSettings() {
3505
3562
  api('/config').then(function(data) {
3506
3563
  settingsProviders = data.providers || [];
3507
3564
  var sel = document.getElementById('settings-provider');
@@ -3520,11 +3577,13 @@ function loadSettings() {
3520
3577
  document.getElementById('settings-heartbeat').value = data.minutes || 30;
3521
3578
  document.getElementById('heartbeat-status').textContent = '';
3522
3579
  });
3523
- loadChannelStatus();
3524
- loadDbStats();
3525
- loadSecrets();
3526
- loadSmartRouting();
3527
- }
3580
+ loadChannelStatus();
3581
+ loadDbStats();
3582
+ loadSecrets();
3583
+ loadSmartRouting();
3584
+ loadMemoryRetrievalSettings();
3585
+ loadToolScopeSettings();
3586
+ }
3528
3587
 
3529
3588
  function onProviderChange() {
3530
3589
  var sel = document.getElementById('settings-provider');
@@ -3572,7 +3631,7 @@ function testLlm() {
3572
3631
  });
3573
3632
  }
3574
3633
 
3575
- function saveHeartbeat() {
3634
+ function saveHeartbeat() {
3576
3635
  var mins = parseInt(document.getElementById('settings-heartbeat').value, 10);
3577
3636
  if (!mins || mins < 1 || mins > 1440) {
3578
3637
  document.getElementById('heartbeat-status').textContent = 'Must be 1\u20131440 minutes';
@@ -3590,7 +3649,90 @@ function saveHeartbeat() {
3590
3649
  document.getElementById('heartbeat-status').textContent = data.error || 'Error';
3591
3650
  }
3592
3651
  });
3593
- }
3652
+ }
3653
+
3654
+ function loadMemoryRetrievalSettings() {
3655
+ api('/settings/memory-retrieval').then(function(data) {
3656
+ document.getElementById('memory-context-topk').value = data.contextTopK || 3;
3657
+ document.getElementById('memory-min-confidence').value = data.minConfidence || 0;
3658
+ document.getElementById('memory-retrieval-status').textContent = '';
3659
+ });
3660
+ }
3661
+
3662
+ function saveMemoryRetrievalSettings() {
3663
+ var contextTopK = parseInt(document.getElementById('memory-context-topk').value, 10);
3664
+ var minConfidence = parseFloat(document.getElementById('memory-min-confidence').value);
3665
+ if (isNaN(contextTopK) || contextTopK < 1 || contextTopK > 10) {
3666
+ document.getElementById('memory-retrieval-status').textContent = 'Top-K must be 1-10';
3667
+ return;
3668
+ }
3669
+ if (isNaN(minConfidence) || minConfidence < 0 || minConfidence > 1) {
3670
+ document.getElementById('memory-retrieval-status').textContent = 'Confidence must be 0-1';
3671
+ return;
3672
+ }
3673
+ api('/settings/memory-retrieval', {
3674
+ method: 'PUT',
3675
+ headers: {'Content-Type':'application/json'},
3676
+ body: JSON.stringify({ contextTopK: contextTopK, minConfidence: minConfidence })
3677
+ }).then(function(data) {
3678
+ if (data.ok) {
3679
+ document.getElementById('memory-retrieval-status').textContent = 'Saved';
3680
+ toast('Memory retrieval settings updated');
3681
+ } else {
3682
+ document.getElementById('memory-retrieval-status').textContent = data.error || 'Error';
3683
+ }
3684
+ });
3685
+ }
3686
+
3687
+ function applyMemoryPreset(kind) {
3688
+ if (kind === 'strict') {
3689
+ document.getElementById('memory-context-topk').value = 2;
3690
+ document.getElementById('memory-min-confidence').value = 0.35;
3691
+ } else {
3692
+ document.getElementById('memory-context-topk').value = 4;
3693
+ document.getElementById('memory-min-confidence').value = 0.2;
3694
+ }
3695
+ document.getElementById('memory-retrieval-status').textContent = 'Preset applied';
3696
+ }
3697
+
3698
+ function loadToolScopeSettings() {
3699
+ api('/settings/tool-scopes').then(function(data) {
3700
+ document.getElementById('tool-scopes-allowed').value = (data.allowed || []).join(',');
3701
+ document.getElementById('tool-scopes-dry-run').value = data.dryRunByDefault ? 'true' : 'false';
3702
+ document.getElementById('tool-scopes-status').textContent = '';
3703
+ });
3704
+ }
3705
+
3706
+ function saveToolScopeSettings() {
3707
+ var allowed = document.getElementById('tool-scopes-allowed').value
3708
+ .split(',')
3709
+ .map(function(x) { return x.trim(); })
3710
+ .filter(Boolean);
3711
+ var dryRunByDefault = document.getElementById('tool-scopes-dry-run').value === 'true';
3712
+ api('/settings/tool-scopes', {
3713
+ method: 'PUT',
3714
+ headers: {'Content-Type':'application/json'},
3715
+ body: JSON.stringify({ allowed: allowed, dryRunByDefault: dryRunByDefault })
3716
+ }).then(function(data) {
3717
+ if (data.ok) {
3718
+ document.getElementById('tool-scopes-status').textContent = 'Saved';
3719
+ toast('Tool safety settings updated');
3720
+ } else {
3721
+ document.getElementById('tool-scopes-status').textContent = data.error || 'Error';
3722
+ }
3723
+ });
3724
+ }
3725
+
3726
+ function applyToolScopePreset(kind) {
3727
+ if (kind === 'safe') {
3728
+ document.getElementById('tool-scopes-allowed').value = 'memory,network_read,filesystem_read,config,scheduling';
3729
+ document.getElementById('tool-scopes-dry-run').value = 'true';
3730
+ } else {
3731
+ document.getElementById('tool-scopes-allowed').value = 'memory,network_read,filesystem_read,filesystem_write,config,scheduling,delegation';
3732
+ document.getElementById('tool-scopes-dry-run').value = 'true';
3733
+ }
3734
+ document.getElementById('tool-scopes-status').textContent = 'Preset applied';
3735
+ }
3594
3736
 
3595
3737
  // --- Smart Routing ---
3596
3738
  function loadSmartRouting() {