colana 1.0.0-beta.76 → 1.0.0-beta.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colana",
3
- "version": "1.0.0-beta.76",
3
+ "version": "1.0.0-beta.77",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
package/public/app.js CHANGED
@@ -325,8 +325,8 @@ function renderPersonalStrip() {
325
325
  const preview = document.getElementById('personal-preview');
326
326
  const launchBtn = document.getElementById('personal-launch-btn');
327
327
 
328
- // Discovery state: not installed or not configured
329
- const needsSetup = !pa.installed || !pa.configured;
328
+ // Discovery state: not installed AND not configured (truly needs setup)
329
+ const needsSetup = !pa.installed && !pa.configured;
330
330
  strip.classList.toggle('needs-setup', needsSetup && pa.status !== 'running' && pa.status !== 'waiting');
331
331
 
332
332
  // Status dot
@@ -342,7 +342,7 @@ function renderPersonalStrip() {
342
342
 
343
343
  // Preview text
344
344
  if (preview) {
345
- if (!pa.installed) {
345
+ if (!pa.installed && !pa.configured) {
346
346
  preview.textContent = 'Personal AI — Set up';
347
347
  } else if (!pa.chatEnabled) {
348
348
  preview.textContent = 'Personal AI — Add API key';
@@ -660,8 +660,8 @@ async function openPersonalPanel() {
660
660
  const clearChatBtn = document.getElementById('personal-panel-clear-chat');
661
661
  const composer = document.getElementById('personal-panel-composer');
662
662
 
663
- // --- Inline Setup Mode: installed but no API key ---
664
- if (pa.installed && !pa.chatEnabled) {
663
+ // --- Inline Setup Mode: installed/configured but no API key ---
664
+ if ((pa.installed || pa.configured) && !pa.chatEnabled) {
665
665
  if (modeToggle) modeToggle.style.display = 'none';
666
666
  if (clearChatBtn) clearChatBtn.style.display = 'none';
667
667
  if (composer) composer.classList.add('hidden');
@@ -830,7 +830,7 @@ async function launchPersonalAgent() {
830
830
  // shows "Add API key" with a "Set up" button that opens the panel with an
831
831
  // inline key input card. Don't spawn (would hit preflight GATEWAY_AUTH_MISSING)
832
832
  // and don't auto-open anything — let the user initiate via the strip/button.
833
- if (state.personalAgent.installed && !state.personalAgent.chatEnabled) {
833
+ if ((state.personalAgent.installed || state.personalAgent.configured) && !state.personalAgent.chatEnabled) {
834
834
  return;
835
835
  }
836
836
  // Guard: don't launch if another launch call is already in-flight
@@ -1287,9 +1287,10 @@ function renderPersonalPanelSetup() {
1287
1287
  closePersonalPanel();
1288
1288
  openSettingsModal();
1289
1289
  setTimeout(() => {
1290
- const section = document.getElementById('settings-personal-agents');
1290
+ const section = document.getElementById('settings-personal-api-keys')
1291
+ || document.getElementById('settings-personal-agents');
1291
1292
  if (section) section.scrollIntoView({ behavior: 'smooth', block: 'center' });
1292
- }, 300);
1293
+ }, 400);
1293
1294
  };
1294
1295
  settingsLink.appendChild(settingsAnchor);
1295
1296
  card.appendChild(settingsLink);
@@ -1561,20 +1562,17 @@ async function sendPersonalChatMessage() {
1561
1562
  addKeyBtn.textContent = 'Add API Key';
1562
1563
  addKeyBtn.addEventListener('click', async (e) => {
1563
1564
  e.preventDefault();
1564
- resetModelConfigReady();
1565
1565
  openSettingsModal();
1566
- await _modelConfigReady;
1567
- // Scroll to the key input (Quick Setup on fresh install, or auth row otherwise)
1568
- const keyInput = document.getElementById('openclaw-auth-key-input')
1569
- || document.querySelector('.openclaw-quick-setup input[type="password"]');
1570
- const modelSection = document.getElementById('openclaw-model-config')
1571
- || document.getElementById('settings-openclaw-model');
1572
- if (keyInput) {
1573
- keyInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
1574
- setTimeout(() => keyInput.focus(), 300);
1575
- } else if (modelSection) {
1576
- modelSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
1577
- }
1566
+ // Scroll to the dedicated API Keys section
1567
+ setTimeout(() => {
1568
+ const apiKeysSection = document.getElementById('settings-personal-api-keys');
1569
+ if (apiKeysSection) {
1570
+ apiKeysSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
1571
+ // Focus the first key input for quick entry
1572
+ const firstInput = apiKeysSection.querySelector('.personal-api-key-input');
1573
+ if (firstInput) setTimeout(() => firstInput.focus(), 300);
1574
+ }
1575
+ }, 400);
1578
1576
  });
1579
1577
  assistantBubble.appendChild(addKeyBtn);
1580
1578
 
@@ -7221,6 +7219,9 @@ async function openSettingsModal() {
7221
7219
  // Integration Hub status (non-blocking)
7222
7220
  renderIntegrationsGrid();
7223
7221
 
7222
+ // Personal Agent API keys (non-blocking)
7223
+ renderPersonalApiKeys();
7224
+
7224
7225
  // OpenClaw model config (non-blocking)
7225
7226
  loadOpenClawModelConfig();
7226
7227
  } catch (error) {
@@ -7283,6 +7284,145 @@ async function installCliAgent(provider, btn) {
7283
7284
  }
7284
7285
  }
7285
7286
 
7287
+ // =========================================================================
7288
+ // Personal Agent API Key Management (Settings)
7289
+ // =========================================================================
7290
+
7291
+ /**
7292
+ * Render the API Keys section in Settings > Personal AI Agents.
7293
+ * Always visible — shows provider key status and input for adding/updating keys.
7294
+ * Uses the same /openclaw/models/auth endpoint as the inline panel setup.
7295
+ */
7296
+ async function renderPersonalApiKeys() {
7297
+ const container = document.getElementById('settings-personal-api-keys');
7298
+ if (!container) return;
7299
+
7300
+ const providers = [
7301
+ { id: 'anthropic', label: 'Anthropic', envVar: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-...', defaultModel: 'anthropic/claude-sonnet-4-20250514' },
7302
+ { id: 'openai', label: 'OpenAI', envVar: 'OPENAI_API_KEY', placeholder: 'sk-...', defaultModel: 'openai/gpt-4o' },
7303
+ { id: 'google', label: 'Google', envVar: 'GOOGLE_API_KEY', placeholder: 'AIza...', defaultModel: 'google/gemini-2.0-flash' },
7304
+ { id: 'groq', label: 'Groq', envVar: 'GROQ_API_KEY', placeholder: 'gsk_...', defaultModel: 'groq/llama-3.3-70b-versatile' },
7305
+ { id: 'xai', label: 'xAI', envVar: 'XAI_API_KEY', placeholder: 'xai-...', defaultModel: 'xai/grok-3-mini' },
7306
+ { id: 'mistral', label: 'Mistral', envVar: 'MISTRAL_API_KEY', placeholder: 'Paste key...', defaultModel: 'mistral/mistral-large-latest' },
7307
+ ];
7308
+
7309
+ // Fetch current auth status to show which providers have keys configured
7310
+ let authProviders = null;
7311
+ try {
7312
+ const data = await api('/openclaw/models/status');
7313
+ authProviders = data.authProviders;
7314
+ } catch { /* best-effort */ }
7315
+
7316
+ container.textContent = '';
7317
+
7318
+ const grid = document.createElement('div');
7319
+ grid.className = 'personal-api-keys-grid';
7320
+
7321
+ for (const p of providers) {
7322
+ // Check if this provider has a key configured (from authProviders or env)
7323
+ const hasKey = authProviders
7324
+ ? (authProviders[p.id]?.authenticated === true || authProviders[p.id]?.status === 'authenticated')
7325
+ : false;
7326
+
7327
+ const row = document.createElement('div');
7328
+ row.className = 'personal-api-key-row';
7329
+
7330
+ const infoDiv = document.createElement('div');
7331
+ infoDiv.className = 'personal-api-key-info';
7332
+
7333
+ const nameSpan = document.createElement('span');
7334
+ nameSpan.className = 'personal-api-key-name';
7335
+ nameSpan.textContent = p.label;
7336
+ infoDiv.appendChild(nameSpan);
7337
+
7338
+ const statusSpan = document.createElement('span');
7339
+ statusSpan.className = 'personal-api-key-status';
7340
+ if (hasKey) {
7341
+ statusSpan.classList.add('configured');
7342
+ statusSpan.textContent = 'Configured';
7343
+ } else {
7344
+ statusSpan.textContent = 'Not configured';
7345
+ }
7346
+ infoDiv.appendChild(statusSpan);
7347
+
7348
+ const actionDiv = document.createElement('div');
7349
+ actionDiv.className = 'personal-api-key-action';
7350
+
7351
+ const keyInput = document.createElement('input');
7352
+ keyInput.type = 'password';
7353
+ keyInput.className = 'personal-api-key-input';
7354
+ keyInput.placeholder = hasKey ? 'Enter new key to update...' : p.placeholder;
7355
+
7356
+ const saveBtn = document.createElement('button');
7357
+ saveBtn.type = 'button';
7358
+ saveBtn.className = 'btn btn-sm btn-primary personal-api-key-save';
7359
+ saveBtn.textContent = hasKey ? 'Update' : 'Save';
7360
+
7361
+ saveBtn.onclick = async () => {
7362
+ const keyValue = keyInput.value.trim();
7363
+ if (!keyValue) { showToast('Enter an API key', 'error'); return; }
7364
+ saveBtn.disabled = true;
7365
+ saveBtn.textContent = 'Saving...';
7366
+
7367
+ try {
7368
+ await api('/openclaw/models/auth', {
7369
+ method: 'POST',
7370
+ body: JSON.stringify({ provider: p.id, apiKey: keyValue }),
7371
+ });
7372
+
7373
+ // Auto-set a default model if this is the first key being added
7374
+ if (!hasKey && p.defaultModel) {
7375
+ saveBtn.textContent = 'Setting model...';
7376
+ try {
7377
+ await api('/openclaw/models/set', {
7378
+ method: 'POST',
7379
+ body: JSON.stringify({ model: p.defaultModel }),
7380
+ });
7381
+ } catch {
7382
+ // Model set failed — non-fatal, user can set manually
7383
+ }
7384
+ }
7385
+
7386
+ showToast(`${p.label} API key saved`, 'success');
7387
+ keyInput.value = '';
7388
+
7389
+ // Refresh the section and model config after gateway picks up the key
7390
+ setTimeout(() => {
7391
+ renderPersonalApiKeys();
7392
+ loadOpenClawModelConfig();
7393
+ loadPersonalAgentStatus();
7394
+ }, 2500);
7395
+ } catch (err) {
7396
+ showToast('Failed to save: ' + err.message, 'error');
7397
+ } finally {
7398
+ saveBtn.disabled = false;
7399
+ saveBtn.textContent = hasKey ? 'Update' : 'Save';
7400
+ }
7401
+ };
7402
+
7403
+ // Submit on Enter key
7404
+ keyInput.addEventListener('keydown', (e) => {
7405
+ if (e.key === 'Enter') { e.preventDefault(); saveBtn.click(); }
7406
+ });
7407
+
7408
+ actionDiv.appendChild(keyInput);
7409
+ actionDiv.appendChild(saveBtn);
7410
+
7411
+ row.appendChild(infoDiv);
7412
+ row.appendChild(actionDiv);
7413
+ grid.appendChild(row);
7414
+ }
7415
+
7416
+ container.appendChild(grid);
7417
+
7418
+ // Hint text
7419
+ const hint = document.createElement('p');
7420
+ hint.className = 'settings-hint';
7421
+ hint.style.cssText = 'margin-top: 8px; font-size: 0.78rem;';
7422
+ hint.textContent = 'Keys are encrypted and stored locally. Only one provider key is needed to get started.';
7423
+ container.appendChild(hint);
7424
+ }
7425
+
7286
7426
  // =========================================================================
7287
7427
  // OpenClaw Model Configuration
7288
7428
  // =========================================================================
@@ -7902,7 +8042,7 @@ async function loadPersonalAgentStatus() {
7902
8042
  const hint = document.createElement('p');
7903
8043
  hint.className = 'settings-hint personal-auth-hint';
7904
8044
  hint.style.cssText = 'margin-top: 6px; color: var(--warning, #e8a838); font-size: 0.82rem;';
7905
- hint.textContent = 'Configure a model API key in Model Configuration below to enable chat.';
8045
+ hint.textContent = 'Add an API key in the API Keys section below to enable chat.';
7906
8046
  container.appendChild(hint);
7907
8047
  }
7908
8048
 
@@ -10547,10 +10687,10 @@ function initEventListeners() {
10547
10687
  const pa = state.personalAgent;
10548
10688
  if (pa.panelOpen) {
10549
10689
  closePersonalPanel();
10550
- } else if (pa.installed && !pa.chatEnabled) {
10551
- // Installed but no API key — open panel with inline setup
10690
+ } else if ((pa.installed || pa.configured) && !pa.chatEnabled) {
10691
+ // Installed/configured but no API key — open panel with inline setup
10552
10692
  openPersonalPanel();
10553
- } else if (pa.sessionId) {
10693
+ } else if (pa.sessionId || pa.chatEnabled) {
10554
10694
  openPersonalPanel();
10555
10695
  }
10556
10696
  });
@@ -10561,8 +10701,8 @@ function initEventListeners() {
10561
10701
  personalLaunchBtn.addEventListener('click', (e) => {
10562
10702
  e.stopPropagation();
10563
10703
  const pa = state.personalAgent;
10564
- if (!pa.installed) {
10565
- // Not installed — open Settings scrolled to Personal AI Agents section
10704
+ if (!pa.installed && !pa.configured) {
10705
+ // Not installed and not configured — open Settings scrolled to Personal AI Agents section
10566
10706
  openSettingsModal();
10567
10707
  setTimeout(() => {
10568
10708
  const section = document.getElementById('settings-personal-agents');
@@ -10571,7 +10711,7 @@ function initEventListeners() {
10571
10711
  return;
10572
10712
  }
10573
10713
  if (!pa.chatEnabled) {
10574
- // Installed but no API key — open panel with inline setup
10714
+ // Installed/configured but no API key — open panel with inline setup
10575
10715
  openPersonalPanel();
10576
10716
  return;
10577
10717
  }
package/public/index.html CHANGED
@@ -910,6 +910,12 @@
910
910
  <!-- Populated dynamically by app.js — personal agents only -->
911
911
  </div>
912
912
 
913
+ <div class="settings-section-title">API Keys</div>
914
+ <p class="settings-hint" style="margin-bottom: 10px;">Add or update API keys to connect your Personal Agent to AI providers.</p>
915
+ <div id="settings-personal-api-keys" class="personal-api-keys-section">
916
+ <!-- Populated dynamically by app.js -->
917
+ </div>
918
+
913
919
  <div class="settings-section-title">Model Configuration</div>
914
920
  <p class="settings-hint" style="margin-bottom: 10px;">Configure the default AI model for your Personal Agent.</p>
915
921
  <div id="settings-openclaw-model" class="openclaw-model-config">
package/public/styles.css CHANGED
@@ -7751,6 +7751,91 @@ select.form-input option {
7751
7751
  background: var(--bg-secondary);
7752
7752
  }
7753
7753
 
7754
+ /* --- Settings: Personal Agent API Keys --- */
7755
+ .personal-api-keys-section {
7756
+ margin: 8px 0;
7757
+ }
7758
+
7759
+ .personal-api-keys-grid {
7760
+ display: flex;
7761
+ flex-direction: column;
7762
+ gap: 6px;
7763
+ }
7764
+
7765
+ .personal-api-key-row {
7766
+ display: flex;
7767
+ align-items: center;
7768
+ justify-content: space-between;
7769
+ gap: 12px;
7770
+ padding: 8px 12px;
7771
+ background: var(--bg-secondary, #1a1a2e);
7772
+ border: 1px solid var(--border-color, #2a2a4a);
7773
+ border-radius: 8px;
7774
+ transition: border-color 0.15s ease;
7775
+ }
7776
+
7777
+ .personal-api-key-row:hover {
7778
+ border-color: var(--border-hover, #3a3a5a);
7779
+ }
7780
+
7781
+ .personal-api-key-info {
7782
+ display: flex;
7783
+ flex-direction: column;
7784
+ gap: 2px;
7785
+ min-width: 100px;
7786
+ flex-shrink: 0;
7787
+ }
7788
+
7789
+ .personal-api-key-name {
7790
+ font-size: 0.88rem;
7791
+ font-weight: 600;
7792
+ color: var(--text-primary, #e0e0e0);
7793
+ }
7794
+
7795
+ .personal-api-key-status {
7796
+ font-size: 0.75rem;
7797
+ color: var(--text-muted, #888);
7798
+ }
7799
+
7800
+ .personal-api-key-status.configured {
7801
+ color: var(--success, #4caf50);
7802
+ }
7803
+
7804
+ .personal-api-key-action {
7805
+ display: flex;
7806
+ align-items: center;
7807
+ gap: 6px;
7808
+ flex: 1;
7809
+ min-width: 0;
7810
+ }
7811
+
7812
+ .personal-api-key-input {
7813
+ flex: 1;
7814
+ min-width: 0;
7815
+ padding: 6px 10px;
7816
+ border-radius: 6px;
7817
+ border: 1px solid var(--border-color, #2a2a4a);
7818
+ background: var(--bg-primary, #0d0d1a);
7819
+ color: var(--text-primary, #e0e0e0);
7820
+ font-size: 0.82rem;
7821
+ font-family: inherit;
7822
+ transition: border-color 0.15s ease;
7823
+ }
7824
+
7825
+ .personal-api-key-input:focus {
7826
+ outline: none;
7827
+ border-color: var(--accent, #7c4dff);
7828
+ }
7829
+
7830
+ .personal-api-key-input::placeholder {
7831
+ color: var(--text-muted, #666);
7832
+ }
7833
+
7834
+ .personal-api-key-save {
7835
+ flex-shrink: 0;
7836
+ white-space: nowrap;
7837
+ }
7838
+
7754
7839
  /* --- Settings: OpenClaw Model Config --- */
7755
7840
  .openclaw-model-config {
7756
7841
  margin: 8px 0;