zubo 0.1.19 → 0.1.21
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 +2 -2
- package/package.json +1 -1
- package/site/docs/agents.html +2 -2
- package/site/docs/api.html +2 -2
- package/site/docs/cli.html +7 -2
- package/site/docs/config.html +92 -0
- package/site/docs/index.html +8 -6
- package/site/docs/integrations.html +3 -3
- package/site/docs/marketplace.html +9 -9
- package/site/docs/security.html +4 -4
- package/site/docs/skills.html +1 -1
- package/site/docs/webhooks.html +17 -0
- package/site/index.html +4 -4
- package/site/install.sh +11 -5
- package/src/agent/compaction.ts +20 -4
- package/src/agent/history.ts +7 -2
- package/src/agent/loop.ts +50 -18
- package/src/agent/prompts.ts +2 -0
- package/src/agent/session.ts +69 -2
- package/src/agent/summarizer.ts +223 -0
- package/src/channels/dashboard.html.ts +98 -56
- package/src/channels/telegram.ts +10 -1
- package/src/channels/webchat.ts +40 -8
- package/src/llm/claude-code.ts +1 -2
- package/src/llm/codex.ts +3 -3
- package/src/llm/factory.ts +81 -2
- package/src/llm/failover.ts +59 -4
- package/src/llm/smart-router.ts +14 -6
- package/src/memory/knowledge-graph.ts +1 -1
- package/src/memory/vector-index.ts +1 -1
- package/src/scheduler/visual-workflows.ts +1 -1
- package/src/setup-web.html.ts +1371 -0
- package/src/setup-web.ts +165 -0
- package/src/setup.ts +266 -15
- package/src/start.ts +12 -2
- package/src/tools/builtin/config-update.ts +18 -1
- package/src/tools/executor.ts +2 -2
- package/src/tools/mcp-registry.ts +12 -6
- package/src/tools/permissions.ts +2 -2
|
@@ -928,7 +928,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
928
928
|
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg></span> Dashboard
|
|
929
929
|
</a>
|
|
930
930
|
<a href="#memory" onclick="showPanel('memory')">
|
|
931
|
-
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8.5 2 6 4.5 6 7c0 1.5.5 2.8 1.4 3.8C6.5 12 6 13.5 6 15c0 3.5 2.5 7 6 7s6-3.5 6-7c0-1.5-.5-3-1.4-4.2C17.5 9.8 18 8.5 18 7c0-2.5-2.5-5-6-5z"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span>
|
|
931
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8.5 2 6 4.5 6 7c0 1.5.5 2.8 1.4 3.8C6.5 12 6 13.5 6 15c0 3.5 2.5 7 6 7s6-3.5 6-7c0-1.5-.5-3-1.4-4.2C17.5 9.8 18 8.5 18 7c0-2.5-2.5-5-6-5z"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span> Knowledge
|
|
932
932
|
</a>
|
|
933
933
|
<a href="#skills" onclick="showPanel('skills')">
|
|
934
934
|
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg></span> Skills
|
|
@@ -940,7 +940,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
940
940
|
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 16.98h1.67c2.49 0 4.5-2.01 4.5-4.49 0-2.48-2.01-4.49-4.5-4.49h-.16C19.17 5.35 16.68 3 13.67 3 11.23 3 9.14 4.56 8.34 6.78c-.3-.05-.6-.08-.91-.08-2.76 0-5 2.24-5 5s2.24 5 5 5h1.48"/><polyline points="12 13 12 21"/><polyline points="9 18 12 21 15 18"/></svg></span> Webhooks
|
|
941
941
|
</a>
|
|
942
942
|
<a href="#mcp" onclick="showPanel('mcp')">
|
|
943
|
-
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/><circle cx="8" cy="12" r="1"/><circle cx="16" cy="12" r="1"/></svg></span>
|
|
943
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/><circle cx="8" cy="12" r="1"/><circle cx="16" cy="12" r="1"/></svg></span> Extensions
|
|
944
944
|
</a>
|
|
945
945
|
<div class="sidebar-divider"></div>
|
|
946
946
|
<div class="sidebar-section">Settings</div>
|
|
@@ -989,11 +989,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
989
989
|
<div class="suggestion-chips-group">
|
|
990
990
|
<div class="chip-row">
|
|
991
991
|
<button class="suggestion-chip" onclick="useSuggestion(this)">What can you do?</button>
|
|
992
|
-
<button class="suggestion-chip" onclick="useSuggestion(this)">
|
|
992
|
+
<button class="suggestion-chip" onclick="useSuggestion(this)">Set a reminder for tomorrow</button>
|
|
993
993
|
</div>
|
|
994
994
|
<div class="chip-row">
|
|
995
|
-
<button class="suggestion-chip" onclick="useSuggestion(this)">
|
|
996
|
-
<button class="suggestion-chip" onclick="useSuggestion(this)">
|
|
995
|
+
<button class="suggestion-chip" onclick="useSuggestion(this)">Help me write an email</button>
|
|
996
|
+
<button class="suggestion-chip" onclick="useSuggestion(this)">Explain something to me</button>
|
|
997
997
|
</div>
|
|
998
998
|
</div>
|
|
999
999
|
</div>
|
|
@@ -1099,6 +1099,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1099
1099
|
<!-- MEMORY PANEL -->
|
|
1100
1100
|
<div id="panel-memory" class="panel">
|
|
1101
1101
|
<div class="panel-body">
|
|
1102
|
+
<div style="margin-bottom:20px;">
|
|
1103
|
+
<p class="settings-desc" style="margin-bottom:0;">Everything Zubo remembers about you and your conversations. Edit the persistent memory file directly, or search through individual memory chunks below.</p>
|
|
1104
|
+
</div>
|
|
1102
1105
|
<div class="editor-wrap">
|
|
1103
1106
|
<div class="editor-toolbar">
|
|
1104
1107
|
<button class="btn btn-primary" onclick="saveMemory()">Save MEMORY.md</button>
|
|
@@ -1122,6 +1125,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1122
1125
|
<!-- SKILLS PANEL (with Browse/Registry tab) -->
|
|
1123
1126
|
<div id="panel-skills" class="panel">
|
|
1124
1127
|
<div class="panel-body">
|
|
1128
|
+
<div style="margin-bottom:16px;">
|
|
1129
|
+
<p class="settings-desc" style="margin-bottom:0;">Skills are custom capabilities you can add to Zubo. Browse the community registry to install new ones, or build your own in TypeScript.</p>
|
|
1130
|
+
</div>
|
|
1125
1131
|
<div class="tab-bar" id="skills-tabs">
|
|
1126
1132
|
<button class="tab active" onclick="switchTab('skills','installed')">Installed</button>
|
|
1127
1133
|
<button class="tab" onclick="switchTab('skills','browse')">Browse Registry</button>
|
|
@@ -1206,6 +1212,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1206
1212
|
<!-- WORKFLOWS PANEL -->
|
|
1207
1213
|
<div id="panel-workflows" class="panel">
|
|
1208
1214
|
<div class="panel-body">
|
|
1215
|
+
<div style="margin-bottom:16px;">
|
|
1216
|
+
<p class="settings-desc" style="margin-bottom:0;">Automate multi-step tasks by chaining actions together. Use pre-built recipes, ask Zubo to create one in chat, or build visually with drag-and-drop.</p>
|
|
1217
|
+
</div>
|
|
1209
1218
|
<div class="tab-bar" id="workflows-tabs">
|
|
1210
1219
|
<button class="tab active" onclick="switchTab('workflows','recipes')">Recipes</button>
|
|
1211
1220
|
<button class="tab" onclick="switchTab('workflows','custom')">Custom Workflows</button>
|
|
@@ -1305,6 +1314,9 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1305
1314
|
<!-- MCP MARKETPLACE PANEL -->
|
|
1306
1315
|
<div id="panel-mcp" class="panel">
|
|
1307
1316
|
<div class="panel-body">
|
|
1317
|
+
<div style="margin-bottom:16px;">
|
|
1318
|
+
<p class="settings-desc" style="margin-bottom:0;">Extensions use the <strong>MCP (Model Context Protocol)</strong> standard to give Zubo new tools — like accessing files, databases, GitHub, and more. Browse the marketplace or add your own.</p>
|
|
1319
|
+
</div>
|
|
1308
1320
|
<div class="tab-bar" id="mcp-tabs">
|
|
1309
1321
|
<button class="tab active" onclick="switchTab('mcp','installed')">Installed</button>
|
|
1310
1322
|
<button class="tab" onclick="switchTab('mcp','marketplace')">Marketplace</button>
|
|
@@ -1314,22 +1326,22 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1314
1326
|
<div id="mcp-installed-list" style="display:flex;flex-direction:column;gap:12px;"></div>
|
|
1315
1327
|
<div id="mcp-installed-empty" class="empty-state-card" style="display:none;">
|
|
1316
1328
|
<div class="empty-icon"><svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/></svg></div>
|
|
1317
|
-
<h4>No
|
|
1318
|
-
<p>Browse the marketplace to add powerful
|
|
1329
|
+
<h4>No extensions installed</h4>
|
|
1330
|
+
<p>Browse the marketplace to add powerful extensions.</p>
|
|
1319
1331
|
<button class="btn btn-primary" onclick="switchTab('mcp','marketplace')">Browse Marketplace</button>
|
|
1320
1332
|
</div>
|
|
1321
1333
|
</div>
|
|
1322
1334
|
|
|
1323
1335
|
<div class="tab-content" id="mcp-tab-marketplace">
|
|
1324
1336
|
<div class="search-bar">
|
|
1325
|
-
<input id="mcp-marketplace-search" type="text" placeholder="Search
|
|
1337
|
+
<input id="mcp-marketplace-search" type="text" placeholder="Search extensions (e.g. filesystem, github, database...)" onkeydown="if(event.key==='Enter')searchMcpMarketplace()">
|
|
1326
1338
|
<button class="btn btn-primary" onclick="searchMcpMarketplace()">Search</button>
|
|
1327
1339
|
</div>
|
|
1328
1340
|
<div class="mcp-marketplace-grid" id="mcp-marketplace-results"></div>
|
|
1329
1341
|
<div id="mcp-marketplace-empty" class="empty-state-card">
|
|
1330
1342
|
<div class="empty-icon"><svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
|
|
1331
|
-
<h4>Discover
|
|
1332
|
-
<p>Search the
|
|
1343
|
+
<h4>Discover Extensions</h4>
|
|
1344
|
+
<p>Search the marketplace to find and install extensions.</p>
|
|
1333
1345
|
</div>
|
|
1334
1346
|
</div>
|
|
1335
1347
|
</div>
|
|
@@ -1398,12 +1410,12 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1398
1410
|
<button class="tab active" onclick="switchTab('settings','general')">General</button>
|
|
1399
1411
|
<button class="tab" onclick="switchTab('settings','providers')">Providers</button>
|
|
1400
1412
|
<button class="tab" onclick="switchTab('settings','channels')">Channels</button>
|
|
1401
|
-
<button class="tab" onclick="switchTab('settings','mcp')">
|
|
1402
|
-
<button class="tab" onclick="switchTab('settings','routing')">
|
|
1413
|
+
<button class="tab" onclick="switchTab('settings','mcp')">Extensions</button>
|
|
1414
|
+
<button class="tab" onclick="switchTab('settings','routing')">Cost Savings</button>
|
|
1403
1415
|
<button class="tab" onclick="switchTab('settings','data')">Data</button>
|
|
1404
|
-
<button class="tab" onclick="switchTab('settings','secrets')">
|
|
1405
|
-
<button class="tab" onclick="switchTab('settings','system')">
|
|
1406
|
-
<button class="tab" onclick="switchTab('settings','cron')">
|
|
1416
|
+
<button class="tab" onclick="switchTab('settings','secrets')">API Keys</button>
|
|
1417
|
+
<button class="tab" onclick="switchTab('settings','system')">Personality</button>
|
|
1418
|
+
<button class="tab" onclick="switchTab('settings','cron')">Scheduled Tasks</button>
|
|
1407
1419
|
<button class="tab" onclick="switchTab('settings','logs')">Logs</button>
|
|
1408
1420
|
<button class="tab" onclick="switchTab('settings','privacy')">Privacy</button>
|
|
1409
1421
|
<button class="tab" onclick="switchTab('settings','budget')">Budget</button>
|
|
@@ -1413,11 +1425,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1413
1425
|
<!-- General Tab -->
|
|
1414
1426
|
<div class="tab-content active" id="settings-tab-general">
|
|
1415
1427
|
<div class="settings-section">
|
|
1416
|
-
<h3 class="settings-title" data-tooltip="
|
|
1417
|
-
<p class="settings-desc">
|
|
1428
|
+
<h3 class="settings-title" data-tooltip="Choose which AI powers your agent">AI Model</h3>
|
|
1429
|
+
<p class="settings-desc">Choose which AI service and model your agent uses for conversations.</p>
|
|
1418
1430
|
<div class="settings-grid">
|
|
1419
1431
|
<div class="settings-field">
|
|
1420
|
-
<label class="settings-label" data-tooltip="
|
|
1432
|
+
<label class="settings-label" data-tooltip="AI service (e.g. Anthropic, OpenAI)" for="settings-provider">Provider</label>
|
|
1421
1433
|
<select id="settings-provider" class="settings-select" onchange="onProviderChange()"></select>
|
|
1422
1434
|
</div>
|
|
1423
1435
|
<div class="settings-field">
|
|
@@ -1433,11 +1445,11 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1433
1445
|
</div>
|
|
1434
1446
|
|
|
1435
1447
|
<div class="settings-section">
|
|
1436
|
-
<h3 class="settings-title" data-tooltip="
|
|
1437
|
-
<p class="settings-desc">How often
|
|
1448
|
+
<h3 class="settings-title" data-tooltip="How often Zubo checks for tasks">Background Check Interval</h3>
|
|
1449
|
+
<p class="settings-desc">How often Zubo checks for reminders, scheduled tasks, and updates. Default: every 30 minutes.</p>
|
|
1438
1450
|
<div class="settings-grid">
|
|
1439
1451
|
<div class="settings-field">
|
|
1440
|
-
<label class="settings-label" data-tooltip="Minutes between
|
|
1452
|
+
<label class="settings-label" data-tooltip="Minutes between checks" for="settings-heartbeat">Interval (minutes)</label>
|
|
1441
1453
|
<input id="settings-heartbeat" type="number" class="settings-input" min="1" max="1440" step="1" placeholder="30">
|
|
1442
1454
|
</div>
|
|
1443
1455
|
</div>
|
|
@@ -1456,8 +1468,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1456
1468
|
<!-- Providers Tab -->
|
|
1457
1469
|
<div class="tab-content" id="settings-tab-providers">
|
|
1458
1470
|
<div class="settings-section">
|
|
1459
|
-
<h3 class="settings-title">
|
|
1460
|
-
<p class="settings-desc">Configure AI
|
|
1471
|
+
<h3 class="settings-title">AI Providers</h3>
|
|
1472
|
+
<p class="settings-desc">Configure AI services. Set one as active for your agent to use.</p>
|
|
1461
1473
|
<div id="providers-list" style="display:flex;flex-direction:column;gap:12px;"></div>
|
|
1462
1474
|
<div style="margin-top:20px;">
|
|
1463
1475
|
<button class="btn btn-primary" onclick="showAddProviderForm()">Add Provider</button>
|
|
@@ -1468,22 +1480,23 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1468
1480
|
<div class="settings-field">
|
|
1469
1481
|
<label class="settings-label" for="new-provider-name">Provider</label>
|
|
1470
1482
|
<select id="new-provider-name" class="settings-select" onchange="onNewProviderSelect()">
|
|
1471
|
-
<option value="">-- Select --</option>
|
|
1472
|
-
<option value="anthropic">Anthropic</option>
|
|
1473
|
-
<option value="openai">OpenAI</option>
|
|
1474
|
-
<option value="groq">Groq</option>
|
|
1475
|
-
<option value="together">Together</option>
|
|
1476
|
-
<option value="openrouter">OpenRouter</option>
|
|
1483
|
+
<option value="">-- Select a provider --</option>
|
|
1484
|
+
<option value="anthropic">Anthropic (Claude)</option>
|
|
1485
|
+
<option value="openai">OpenAI (GPT)</option>
|
|
1486
|
+
<option value="groq">Groq (fast, free tier)</option>
|
|
1487
|
+
<option value="together">Together AI</option>
|
|
1488
|
+
<option value="openrouter">OpenRouter (many models)</option>
|
|
1477
1489
|
<option value="deepseek">DeepSeek</option>
|
|
1478
1490
|
<option value="xai">xAI (Grok)</option>
|
|
1479
|
-
<option value="ollama">Ollama (
|
|
1480
|
-
<option value="lmstudio">LM Studio (
|
|
1481
|
-
<option value="custom">Custom (
|
|
1491
|
+
<option value="ollama">Ollama (free, runs locally)</option>
|
|
1492
|
+
<option value="lmstudio">LM Studio (free, runs locally)</option>
|
|
1493
|
+
<option value="custom">Custom (advanced)</option>
|
|
1482
1494
|
</select>
|
|
1483
1495
|
</div>
|
|
1484
1496
|
<div class="settings-field">
|
|
1485
1497
|
<label class="settings-label" for="new-provider-key">API Key</label>
|
|
1486
1498
|
<input id="new-provider-key" type="password" class="settings-input" placeholder="sk-...">
|
|
1499
|
+
<span id="provider-key-help" class="settings-hint" style="font-size:11px;color:var(--text-faint);margin-top:4px;display:none;"></span>
|
|
1487
1500
|
</div>
|
|
1488
1501
|
<div class="settings-field">
|
|
1489
1502
|
<label class="settings-label" for="new-provider-model">Model</label>
|
|
@@ -1532,14 +1545,14 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1532
1545
|
<!-- MCP Tab -->
|
|
1533
1546
|
<div class="tab-content" id="settings-tab-mcp">
|
|
1534
1547
|
<div class="settings-section">
|
|
1535
|
-
<h3 class="settings-title">
|
|
1536
|
-
<p class="settings-desc">
|
|
1548
|
+
<h3 class="settings-title">Extensions</h3>
|
|
1549
|
+
<p class="settings-desc">Extensions give Zubo new abilities like accessing files, databases, and APIs. Install from the marketplace or add manually.</p>
|
|
1537
1550
|
<div id="mcp-servers-list" style="display:flex;flex-direction:column;gap:12px;"></div>
|
|
1538
1551
|
<div style="margin-top:20px;">
|
|
1539
|
-
<button class="btn btn-primary" onclick="showAddMcpForm()">Add
|
|
1552
|
+
<button class="btn btn-primary" onclick="showAddMcpForm()">Add Extension</button>
|
|
1540
1553
|
</div>
|
|
1541
1554
|
<div id="mcp-add-form" style="display:none;margin-top:16px;padding:16px;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);">
|
|
1542
|
-
<h4 style="margin-bottom:12px;font-family:var(--display);font-weight:600;">Add
|
|
1555
|
+
<h4 style="margin-bottom:12px;font-family:var(--display);font-weight:600;">Add Extension</h4>
|
|
1543
1556
|
<div class="settings-grid">
|
|
1544
1557
|
<div class="settings-field">
|
|
1545
1558
|
<label class="settings-label" for="mcp-name">Name</label>
|
|
@@ -1570,8 +1583,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1570
1583
|
<!-- Routing Tab -->
|
|
1571
1584
|
<div class="tab-content" id="settings-tab-routing">
|
|
1572
1585
|
<div class="settings-section">
|
|
1573
|
-
<h3 class="settings-title" data-tooltip="
|
|
1574
|
-
<p class="settings-desc">
|
|
1586
|
+
<h3 class="settings-title" data-tooltip="Use a cheaper AI for simple questions">Smart Cost Savings</h3>
|
|
1587
|
+
<p class="settings-desc">Use a cheaper, faster AI for simple questions and your main AI for complex ones. Saves money without sacrificing quality.</p>
|
|
1575
1588
|
<div class="settings-grid">
|
|
1576
1589
|
<div class="settings-field">
|
|
1577
1590
|
<label class="settings-label" for="sr-enabled">Enabled</label>
|
|
@@ -1615,8 +1628,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1615
1628
|
<!-- Secrets Tab -->
|
|
1616
1629
|
<div class="tab-content" id="settings-tab-secrets">
|
|
1617
1630
|
<div class="settings-section" style="max-width:700px;">
|
|
1618
|
-
<h3 class="settings-title">
|
|
1619
|
-
<p class="settings-desc">Manage API keys
|
|
1631
|
+
<h3 class="settings-title">API Keys & Credentials</h3>
|
|
1632
|
+
<p class="settings-desc">Manage API keys for integrations. Your keys are encrypted and stored securely on your device — Zubo never sends them anywhere.</p>
|
|
1620
1633
|
<div style="display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;">
|
|
1621
1634
|
<button class="btn btn-primary" onclick="showAddSecretForm()">Add Secret</button>
|
|
1622
1635
|
<button class="btn btn-ghost" onclick="loadSecrets()">Refresh</button>
|
|
@@ -1648,6 +1661,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1648
1661
|
|
|
1649
1662
|
<!-- System Prompt Tab -->
|
|
1650
1663
|
<div class="tab-content" id="settings-tab-system">
|
|
1664
|
+
<p class="settings-desc" style="margin-bottom:12px;">Customize your agent's personality and instructions. This controls how Zubo thinks and responds. Be careful — incorrect changes may cause errors.</p>
|
|
1651
1665
|
<div class="editor-wrap" style="min-height:calc(100vh - 220px);">
|
|
1652
1666
|
<div class="editor-toolbar">
|
|
1653
1667
|
<button class="btn btn-primary" onclick="saveSystem()">Save</button>
|
|
@@ -1728,7 +1742,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1728
1742
|
<div class="cards" id="budget-summary-cards"></div>
|
|
1729
1743
|
<div class="settings-section" style="margin-top:24px;">
|
|
1730
1744
|
<h3 class="settings-title">Budget Limits</h3>
|
|
1731
|
-
<p class="settings-desc">Set spending limits to control costs.
|
|
1745
|
+
<p class="settings-desc">Set spending limits to control costs. Zubo will stop responding when limits are reached — you can raise them anytime. Typical usage: ~100 messages/day costs $0.50–$2.00 depending on your AI model.</p>
|
|
1732
1746
|
<div class="settings-grid">
|
|
1733
1747
|
<div class="settings-field">
|
|
1734
1748
|
<label class="settings-label" for="budget-daily">Daily Limit (USD)</label>
|
|
@@ -1907,7 +1921,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1907
1921
|
<script>
|
|
1908
1922
|
// --- Panel routing ---
|
|
1909
1923
|
var panelNames = ['agent','history','dashboard','memory','skills','workflows','webhooks','mcp','integrations','settings'];
|
|
1910
|
-
var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'
|
|
1924
|
+
var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'Knowledge', skills:'Skills', workflows:'Workflows', webhooks:'Webhooks', mcp:'Extensions', integrations:'Integrations', settings:'Settings' };
|
|
1911
1925
|
|
|
1912
1926
|
// Legacy panel name mapping (old names -> new names + tab)
|
|
1913
1927
|
var legacyPanelMap = {
|
|
@@ -2002,6 +2016,8 @@ function switchTab(panelId, tabName) {
|
|
|
2002
2016
|
if (tabName === 'marketplace') {
|
|
2003
2017
|
var s = document.getElementById('mcp-marketplace-search');
|
|
2004
2018
|
if (s) s.focus();
|
|
2019
|
+
var grid = document.getElementById('mcp-marketplace-results');
|
|
2020
|
+
if (grid && !grid.hasChildNodes()) searchMcpMarketplace();
|
|
2005
2021
|
}
|
|
2006
2022
|
}
|
|
2007
2023
|
if (panelId === 'settings' && tabName === 'digests') loadDigestConfig();
|
|
@@ -3788,10 +3804,28 @@ function onNewProviderSelect() {
|
|
|
3788
3804
|
document.getElementById('new-provider-url-field').style.display = showUrl ? '' : 'none';
|
|
3789
3805
|
// Local providers don't need API key
|
|
3790
3806
|
var keyInput = document.getElementById('new-provider-key');
|
|
3807
|
+
var helpEl = document.getElementById('provider-key-help');
|
|
3791
3808
|
if (name === 'ollama' || name === 'lmstudio') {
|
|
3792
|
-
keyInput.placeholder = '
|
|
3809
|
+
keyInput.placeholder = 'Not needed for local models';
|
|
3810
|
+
helpEl.style.display = 'none';
|
|
3793
3811
|
} else {
|
|
3794
3812
|
keyInput.placeholder = 'sk-...';
|
|
3813
|
+
// Show where to get the API key
|
|
3814
|
+
var keyLinks = {
|
|
3815
|
+
anthropic: 'console.anthropic.com/settings/keys',
|
|
3816
|
+
openai: 'platform.openai.com/api-keys',
|
|
3817
|
+
groq: 'console.groq.com/keys',
|
|
3818
|
+
together: 'api.together.xyz/settings/api-keys',
|
|
3819
|
+
openrouter: 'openrouter.ai/keys',
|
|
3820
|
+
deepseek: 'platform.deepseek.com/api_keys',
|
|
3821
|
+
xai: 'console.x.ai'
|
|
3822
|
+
};
|
|
3823
|
+
if (keyLinks[name]) {
|
|
3824
|
+
helpEl.textContent = 'Get your key at ' + keyLinks[name];
|
|
3825
|
+
helpEl.style.display = 'block';
|
|
3826
|
+
} else {
|
|
3827
|
+
helpEl.style.display = 'none';
|
|
3828
|
+
}
|
|
3795
3829
|
}
|
|
3796
3830
|
}
|
|
3797
3831
|
|
|
@@ -3855,7 +3889,7 @@ function loadMcpServers() {
|
|
|
3855
3889
|
if (!servers.length) {
|
|
3856
3890
|
var empty = document.createElement('div');
|
|
3857
3891
|
empty.className = 'empty-state';
|
|
3858
|
-
empty.textContent = 'No
|
|
3892
|
+
empty.textContent = 'No extensions configured. Add one to extend Zubo with new tools.';
|
|
3859
3893
|
list.appendChild(empty);
|
|
3860
3894
|
return;
|
|
3861
3895
|
}
|
|
@@ -3919,7 +3953,7 @@ function restartMcpServer(name) {
|
|
|
3919
3953
|
}
|
|
3920
3954
|
|
|
3921
3955
|
function removeMcpServer(name) {
|
|
3922
|
-
if (!confirm('Remove
|
|
3956
|
+
if (!confirm('Remove extension "' + name + '"? This will disconnect it.')) return;
|
|
3923
3957
|
api('/mcp/servers/' + encodeURIComponent(name), { method: 'DELETE' }).then(function(data) {
|
|
3924
3958
|
if (data.ok) {
|
|
3925
3959
|
toast(name + ' removed');
|
|
@@ -4361,19 +4395,19 @@ function renderCmdResults(query) {
|
|
|
4361
4395
|
var subTabs = [
|
|
4362
4396
|
{ title: 'Analytics', action: function() { showPanel('dashboard'); switchTab('dashboard','analytics'); } },
|
|
4363
4397
|
{ title: 'Performance', action: function() { showPanel('dashboard'); switchTab('dashboard','performance'); } },
|
|
4364
|
-
{ title: '
|
|
4365
|
-
{ title: '
|
|
4398
|
+
{ title: 'Personality', action: function() { showPanel('settings'); switchTab('settings','system'); } },
|
|
4399
|
+
{ title: 'Scheduled Tasks', action: function() { showPanel('settings'); switchTab('settings','cron'); } },
|
|
4366
4400
|
{ title: 'Logs', action: function() { showPanel('settings'); switchTab('settings','logs'); } },
|
|
4367
4401
|
{ title: 'Privacy & Data', action: function() { showPanel('settings'); switchTab('settings','privacy'); } },
|
|
4368
4402
|
{ title: 'Budget', action: function() { showPanel('settings'); switchTab('settings','budget'); } },
|
|
4369
|
-
{ title: '
|
|
4403
|
+
{ title: 'API Keys', action: function() { showPanel('settings'); switchTab('settings','secrets'); } },
|
|
4370
4404
|
{ title: 'Channels', action: function() { showPanel('settings'); switchTab('settings','channels'); } },
|
|
4371
4405
|
{ title: 'Providers', action: function() { showPanel('settings'); switchTab('settings','providers'); } },
|
|
4372
|
-
{ title: '
|
|
4373
|
-
{ title: '
|
|
4406
|
+
{ title: 'Extensions', action: function() { showPanel('settings'); switchTab('settings','mcp'); } },
|
|
4407
|
+
{ title: 'Cost Savings', action: function() { showPanel('settings'); switchTab('settings','routing'); } },
|
|
4374
4408
|
{ title: 'Browse Registry', action: function() { showPanel('skills'); switchTab('skills','browse'); } },
|
|
4375
4409
|
{ title: 'Visual Builder', action: function() { showPanel('workflows'); switchTab('workflows','visual'); } },
|
|
4376
|
-
{ title: '
|
|
4410
|
+
{ title: 'Extensions Marketplace', action: function() { showPanel('mcp'); switchTab('mcp','marketplace'); } },
|
|
4377
4411
|
{ title: 'Email Digests', action: function() { showPanel('settings'); switchTab('settings','digests'); } },
|
|
4378
4412
|
];
|
|
4379
4413
|
subTabs.forEach(function(st) { items.push({ name: st.title.toLowerCase(), title: st.title, action: st.action }); });
|
|
@@ -4514,7 +4548,7 @@ function clearChatMessages() {
|
|
|
4514
4548
|
subtext.textContent = 'Ask me anything, or try a suggestion below';
|
|
4515
4549
|
var chipsGroup = document.createElement('div');
|
|
4516
4550
|
chipsGroup.className = 'suggestion-chips-group';
|
|
4517
|
-
var rows = [['What can you do?','
|
|
4551
|
+
var rows = [['What can you do?','Set a reminder for tomorrow'],['Help me write an email','Explain something to me']];
|
|
4518
4552
|
rows.forEach(function(rowLabels) {
|
|
4519
4553
|
var row = document.createElement('div');
|
|
4520
4554
|
row.className = 'chip-row';
|
|
@@ -4580,8 +4614,8 @@ function loadConversationStats() {
|
|
|
4580
4614
|
var stats = [
|
|
4581
4615
|
{ label: 'Conversations', value: data.totalConversations || 0 },
|
|
4582
4616
|
{ label: 'Messages', value: data.totalMessages || 0 },
|
|
4583
|
-
{ label: 'Channels', value:
|
|
4584
|
-
{ label: 'Recent (24h)', value: data.recentActivity || 0 },
|
|
4617
|
+
{ label: 'Channels', value: (data.messagesByChannel || []).length },
|
|
4618
|
+
{ label: 'Recent (24h)', value: (data.recentActivity || []).reduce(function(s, r) { return s + (r.count || 0); }, 0) },
|
|
4585
4619
|
];
|
|
4586
4620
|
stats.forEach(function(s) {
|
|
4587
4621
|
var card = document.createElement('div');
|
|
@@ -4823,6 +4857,13 @@ function loadWebhooks() {
|
|
|
4823
4857
|
urlEl.setAttribute('data-tooltip', 'Click to copy');
|
|
4824
4858
|
urlEl.onclick = function() { copyToClipboard(whUrl); };
|
|
4825
4859
|
card.appendChild(urlEl);
|
|
4860
|
+
// Localhost warning
|
|
4861
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
4862
|
+
var warn = document.createElement('div');
|
|
4863
|
+
warn.style.cssText = 'font-size:11px;color:var(--yellow,#f5a623);margin-top:4px;padding:6px 8px;background:rgba(245,166,35,0.08);border-radius:4px;line-height:1.4;';
|
|
4864
|
+
warn.textContent = 'This URL uses localhost and won\\'t be reachable by external services. Use a tunnel (ngrok, cloudflared) or deploy to a public server to receive webhooks.';
|
|
4865
|
+
card.appendChild(warn);
|
|
4866
|
+
}
|
|
4826
4867
|
// Stats
|
|
4827
4868
|
var stats = document.createElement('div');
|
|
4828
4869
|
stats.className = 'webhook-card-stats';
|
|
@@ -5024,9 +5065,10 @@ function searchMcpMarketplace() {
|
|
|
5024
5065
|
card.appendChild(desc);
|
|
5025
5066
|
var acts = document.createElement('div');
|
|
5026
5067
|
acts.className = 'mcp-server-card-actions';
|
|
5027
|
-
|
|
5068
|
+
var repoUrl = typeof s.repository === 'string' ? s.repository : (s.repository && s.repository.url ? s.repository.url : '');
|
|
5069
|
+
if (repoUrl) {
|
|
5028
5070
|
var link = document.createElement('a');
|
|
5029
|
-
link.href =
|
|
5071
|
+
link.href = repoUrl;
|
|
5030
5072
|
link.target = '_blank';
|
|
5031
5073
|
link.style.cssText = 'font-size:12px;color:var(--text-muted);';
|
|
5032
5074
|
link.textContent = 'View Repo';
|
package/src/channels/telegram.ts
CHANGED
|
@@ -68,8 +68,17 @@ export function createTelegramAdapter(
|
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
bot.catch((err) => {
|
|
71
|
+
bot.catch(async (err) => {
|
|
72
72
|
logger.error("Telegram bot error", { error: err.message });
|
|
73
|
+
// Try to notify the user that something went wrong
|
|
74
|
+
try {
|
|
75
|
+
const ctx = err.ctx;
|
|
76
|
+
if (ctx?.chat?.id) {
|
|
77
|
+
await ctx.reply("Sorry, something went wrong. Please try again.").catch(() => {});
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Can't reply — don't crash
|
|
81
|
+
}
|
|
73
82
|
});
|
|
74
83
|
|
|
75
84
|
return {
|
package/src/channels/webchat.ts
CHANGED
|
@@ -17,6 +17,24 @@ function escapeHtml(s: string): string {
|
|
|
17
17
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Convert raw error messages to user-friendly messages. Prevents leaking internal details. */
|
|
21
|
+
function friendlyError(err: any): string {
|
|
22
|
+
const msg = err?.message ?? String(err);
|
|
23
|
+
if (msg.includes("401") || msg.includes("Unauthorized") || msg.includes("invalid"))
|
|
24
|
+
return "Authentication failed. Check your API key in Settings > API Keys.";
|
|
25
|
+
if (msg.includes("429") || msg.includes("rate limit"))
|
|
26
|
+
return "Too many messages too quickly. Wait a moment and try again.";
|
|
27
|
+
if (msg.includes("404") || msg.includes("not found"))
|
|
28
|
+
return "The AI model wasn't found. Check your model name in Settings > AI Model.";
|
|
29
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused"))
|
|
30
|
+
return "Can't reach the AI service. Check your internet connection or try again.";
|
|
31
|
+
if (msg.includes("timed out") || msg.includes("timeout"))
|
|
32
|
+
return "The request took too long. The AI service may be busy — try again in a moment.";
|
|
33
|
+
if (msg.includes("context") || msg.includes("too long"))
|
|
34
|
+
return "Your message is too long. Try splitting it into shorter questions.";
|
|
35
|
+
return "Something went wrong. Try again, or check Settings if this keeps happening.";
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
/** Add security headers to all HTTP responses */
|
|
21
39
|
function addSecurityHeaders(res: Response): Response {
|
|
22
40
|
const headers = new Headers(res.headers);
|
|
@@ -509,7 +527,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
|
|
|
509
527
|
const { loadConfig } = await import("../config/loader");
|
|
510
528
|
const { createProvider } = await import("../llm/factory");
|
|
511
529
|
const config = await loadConfig();
|
|
512
|
-
const llm = createProvider(config);
|
|
530
|
+
const llm = await createProvider(config);
|
|
513
531
|
const res = await llm.chat({
|
|
514
532
|
system: "You are a test.",
|
|
515
533
|
messages: [{ role: "user", content: [{ type: "text", text: "Say OK" }] }],
|
|
@@ -1204,7 +1222,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
|
|
|
1204
1222
|
const { loadConfig } = await import("../config/loader");
|
|
1205
1223
|
const { createProvider } = await import("../llm/factory");
|
|
1206
1224
|
const config = await loadConfig();
|
|
1207
|
-
const webhookLlm = createProvider(config);
|
|
1225
|
+
const webhookLlm = await createProvider(config);
|
|
1208
1226
|
const { agentLoop } = await import("../agent/loop");
|
|
1209
1227
|
const sessionKey = `webhook:${webhookName}`;
|
|
1210
1228
|
const result = await agentLoop(webhookLlm, sessionKey, message);
|
|
@@ -1736,7 +1754,7 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
|
|
|
1736
1754
|
const router = (globalThis as any).__zuboRouter;
|
|
1737
1755
|
if (router) {
|
|
1738
1756
|
const freshConfig = await loadConfig();
|
|
1739
|
-
const newLlm = createProvider(freshConfig);
|
|
1757
|
+
const newLlm = await createProvider(freshConfig);
|
|
1740
1758
|
router.setLlm(newLlm);
|
|
1741
1759
|
}
|
|
1742
1760
|
|
|
@@ -1948,9 +1966,9 @@ async function handleDashboardApi(url: URL, req: Request): Promise<Response | nu
|
|
|
1948
1966
|
}
|
|
1949
1967
|
|
|
1950
1968
|
// GET /api/dashboard/conversations/:id/messages — get messages for a conversation
|
|
1951
|
-
const convMsgMatch = path.match(/^\/conversations\/([
|
|
1969
|
+
const convMsgMatch = path.match(/^\/conversations\/([^/]+)\/messages$/);
|
|
1952
1970
|
if (convMsgMatch && req.method === "GET") {
|
|
1953
|
-
const threadId = convMsgMatch[1];
|
|
1971
|
+
const threadId = decodeURIComponent(convMsgMatch[1]);
|
|
1954
1972
|
try {
|
|
1955
1973
|
const db = getDb();
|
|
1956
1974
|
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "50", 10), 200);
|
|
@@ -2494,9 +2512,23 @@ export function createWebChatAdapter(
|
|
|
2494
2512
|
db.run(`CREATE TABLE IF NOT EXISTS threads (
|
|
2495
2513
|
id TEXT PRIMARY KEY,
|
|
2496
2514
|
title TEXT NOT NULL DEFAULT 'New conversation',
|
|
2515
|
+
channel TEXT DEFAULT 'webchat',
|
|
2516
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
2517
|
+
summary TEXT,
|
|
2497
2518
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2498
2519
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2499
2520
|
)`);
|
|
2521
|
+
// Migrate existing tables missing new columns
|
|
2522
|
+
try { db.run("ALTER TABLE threads ADD COLUMN channel TEXT DEFAULT 'webchat'"); } catch {}
|
|
2523
|
+
try { db.run("ALTER TABLE threads ADD COLUMN message_count INTEGER NOT NULL DEFAULT 0"); } catch {}
|
|
2524
|
+
try { db.run("ALTER TABLE threads ADD COLUMN summary TEXT"); } catch {}
|
|
2525
|
+
// Backfill threads from conversation_messages for pre-existing data
|
|
2526
|
+
try {
|
|
2527
|
+
db.run(`INSERT OR IGNORE INTO threads (id, title, channel, message_count, created_at, updated_at)
|
|
2528
|
+
SELECT thread_id, thread_id, COALESCE(channel, 'webchat'),
|
|
2529
|
+
COUNT(*), MIN(timestamp), MAX(timestamp)
|
|
2530
|
+
FROM conversation_messages GROUP BY thread_id`);
|
|
2531
|
+
} catch {}
|
|
2500
2532
|
} catch (err: any) {
|
|
2501
2533
|
logger.warn("Failed to create threads table", { error: (err as Error).message });
|
|
2502
2534
|
}
|
|
@@ -2877,7 +2909,7 @@ async function handleRequest(
|
|
|
2877
2909
|
return Response.json({ reply });
|
|
2878
2910
|
} catch (err: any) {
|
|
2879
2911
|
return Response.json(
|
|
2880
|
-
{ error: err
|
|
2912
|
+
{ error: friendlyError(err) },
|
|
2881
2913
|
{ status: 500 }
|
|
2882
2914
|
);
|
|
2883
2915
|
}
|
|
@@ -2934,7 +2966,7 @@ async function handleRequest(
|
|
|
2934
2966
|
send("done", { reply });
|
|
2935
2967
|
close();
|
|
2936
2968
|
}).catch((err) => {
|
|
2937
|
-
send("error", { error: err
|
|
2969
|
+
send("error", { error: friendlyError(err) });
|
|
2938
2970
|
close();
|
|
2939
2971
|
});
|
|
2940
2972
|
return;
|
|
@@ -2949,7 +2981,7 @@ async function handleRequest(
|
|
|
2949
2981
|
send("done", { reply });
|
|
2950
2982
|
close();
|
|
2951
2983
|
}).catch((err) => {
|
|
2952
|
-
send("error", { error: err
|
|
2984
|
+
send("error", { error: friendlyError(err) });
|
|
2953
2985
|
close();
|
|
2954
2986
|
});
|
|
2955
2987
|
},
|
package/src/llm/claude-code.ts
CHANGED
|
@@ -11,7 +11,7 @@ export class ClaudeCodeProvider implements LlmProvider {
|
|
|
11
11
|
model: string;
|
|
12
12
|
contextWindow = 200_000;
|
|
13
13
|
|
|
14
|
-
constructor(model: string = "
|
|
14
|
+
constructor(model: string = "default") {
|
|
15
15
|
this.model = model;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -55,7 +55,6 @@ export class ClaudeCodeProvider implements LlmProvider {
|
|
|
55
55
|
const prompt = parts.join("\n\n");
|
|
56
56
|
|
|
57
57
|
const args = ["claude", "-p", prompt, "--output-format", "json"];
|
|
58
|
-
if (this.model) args.push("--model", this.model);
|
|
59
58
|
|
|
60
59
|
try {
|
|
61
60
|
const proc = Bun.spawn(args, {
|
package/src/llm/codex.ts
CHANGED
|
@@ -11,7 +11,7 @@ export class CodexProvider implements LlmProvider {
|
|
|
11
11
|
model: string;
|
|
12
12
|
contextWindow = 200_000;
|
|
13
13
|
|
|
14
|
-
constructor(model: string = "
|
|
14
|
+
constructor(model: string = "default") {
|
|
15
15
|
this.model = model;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -51,8 +51,8 @@ export class CodexProvider implements LlmProvider {
|
|
|
51
51
|
|
|
52
52
|
const prompt = parts.join("\n\n");
|
|
53
53
|
|
|
54
|
-
const args = ["codex", "
|
|
55
|
-
|
|
54
|
+
const args = ["codex", "exec"];
|
|
55
|
+
args.push(prompt);
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
58
|
const proc = Bun.spawn(args, {
|