clementine-agent 1.0.74 → 1.0.76

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.
@@ -208,16 +208,10 @@ Steps:
208
208
  fields: [
209
209
  {
210
210
  key: 'contact',
211
- label: 'Contact',
211
+ label: 'Phone number or email',
212
+ placeholder: '+15551234567 or someone@example.com',
212
213
  required: true,
213
- help: 'Pick an iMessage contact. Their phone number or email becomes the thread id.',
214
- picker: {
215
- tool: 'mcp__imessage__search_contacts',
216
- intent: 'call search_contacts with query "{{query}}". For each match, output {id: the contact\'s phone number or email handle, label: the display name (or the handle if no name), sublabel: "handle: " + handle}. Return up to 15 results.',
217
- queryArg: 'query',
218
- minQueryLength: 2,
219
- allowCustom: true,
220
- },
214
+ help: 'Paste the contact\'s iMessage handle exactly as it appears in your Messages thread. The iMessage MCP\'s search_contacts reads the Contacts app via AppleScript and is unreliable — we bypass it by letting you paste the handle directly.',
221
215
  },
222
216
  { key: 'limit', label: 'Max messages per run', placeholder: '50', defaultValue: '50' },
223
217
  ],
@@ -2659,7 +2659,13 @@ export async function cmdDashboard(opts) {
2659
2659
  }
2660
2660
  const { query } = await import('@anthropic-ai/claude-agent-sdk');
2661
2661
  const { parseJsonResponse } = await import('../brain/llm-client.js');
2662
+ const { getMcpServersForAgent } = await import('../agent/mcp-bridge.js');
2662
2663
  const { MODELS } = await import('../config.js');
2664
+ // Pass the full MCP server map so the one-shot probe can call
2665
+ // Extension-provided tools (iMessage, figma, supabase, etc.).
2666
+ // Without this the SDK only sees built-in + claude_ai_* tools
2667
+ // and every Extension tool call is silently rejected at runtime.
2668
+ const mcpServers = getMcpServersForAgent();
2663
2669
  // Strict prompt: force JSON-only output. The tool lookup goes through
2664
2670
  // the SDK so claude_ai_* and regular MCP servers work uniformly.
2665
2671
  const stream = query({
@@ -2672,6 +2678,7 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
2672
2678
  maxTurns: 3,
2673
2679
  systemPrompt: 'You are a data enumerator. You call the given tool once, extract the items from its response, and emit a strict JSON array. No commentary.',
2674
2680
  allowedTools: [tool],
2681
+ mcpServers,
2675
2682
  permissionMode: 'bypassPermissions',
2676
2683
  settingSources: [],
2677
2684
  },
@@ -10820,7 +10827,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
10820
10827
  return;
10821
10828
  }
10822
10829
  brainPickerTypeahead[field.key] = {
10823
- timer: setTimeout(function() { brainFireTypeaheadProbe(field, q); }, 400),
10830
+ timer: setTimeout(function() { brainFireTypeaheadProbe(field, q); }, 600),
10824
10831
  };
10825
10832
  };
10826
10833
  return;
@@ -10886,7 +10893,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
10886
10893
  if (!resp.ok) throw new Error(data.error || 'probe failed');
10887
10894
  const items = data.items || [];
10888
10895
  if (!items.length) {
10889
- resultsEl.innerHTML = '<div style="color:#8a5a00;font-size:12px;padding:6px">No matches for "' + escapeHtml(query) + '"' + (data.rawPreview ? ' (' + escapeHtml(data.rawPreview.slice(0, 100)) + ')' : '') + '</div>';
10896
+ // If the raw preview is anything other than an empty array (with
10897
+ // or without a markdown code fence), show it so the user can tell
10898
+ // whether the tool actually ran.
10899
+ const trimmed = (data.rawPreview || '').trim();
10900
+ const isEmptyArray = /^\s*\[\s*\]\s*$/.test(trimmed.replace(/^[^\[]*/, '').replace(/[^\]]*$/, ''));
10901
+ const rawHint = trimmed && !isEmptyArray
10902
+ ? ' <span style="color:var(--muted)">Tool said: ' + escapeHtml(trimmed.slice(0, 140)) + '</span>'
10903
+ : '';
10904
+ resultsEl.innerHTML = '<div style="color:#8a5a00;font-size:12px;padding:6px">No matches for "' + escapeHtml(query) + '". The tool may be limited by macOS permissions or not support this query — use <a href="#" onclick="brainFieldPickerToggleCustom(\\'' + field.key + '\\', \\'\\');return false">type a value directly</a>.' + rawHint + '</div>';
10890
10905
  return;
10891
10906
  }
10892
10907
  resultsEl.innerHTML = items.map(function(it) {
@@ -12365,14 +12380,24 @@ async function checkAnthropicAuth() {
12365
12380
  var btn = document.getElementById('gs-auth-btn');
12366
12381
  if (d.authenticated && card) {
12367
12382
  card.className = 'gs-card gs-done';
12368
- if (desc) desc.textContent = 'Connected as ' + d.email;
12383
+ // Email is null when auth comes from the Claude Code keychain (OAuth
12384
+ // login via clementine-login or claude-login that we do not have read
12385
+ // access to). Avoid rendering "Connected as null" — looks broken and
12386
+ // makes users re-click login, which hits an SDK bug in claudeAuthenticate.
12387
+ var label = d.email
12388
+ ? 'Connected as ' + d.email
12389
+ : 'Connected via ' + (d.apiKeySource === 'keychain' ? 'Claude Code keychain' : d.apiKeySource || 'API');
12390
+ if (desc) desc.textContent = label;
12369
12391
  if (btn) { btn.textContent = 'Connected'; btn.disabled = true; }
12370
12392
  }
12371
12393
  // Also update settings auth indicator
12372
12394
  var settingsAuth = document.getElementById('settings-auth-status');
12373
12395
  if (settingsAuth) {
12396
+ var sLabel = d.authenticated
12397
+ ? (d.email ? 'Connected as ' + d.email : 'Connected via ' + (d.apiKeySource || 'API'))
12398
+ : '';
12374
12399
  settingsAuth.innerHTML = d.authenticated
12375
- ? '<span style="color:var(--green)">Connected as ' + d.email + '</span>'
12400
+ ? '<span style="color:var(--green)">' + sLabel + '</span>'
12376
12401
  : '<span style="color:var(--text-muted)">Not connected</span>';
12377
12402
  }
12378
12403
  return d;
@@ -12382,6 +12407,23 @@ async function checkAnthropicAuth() {
12382
12407
  async function startAnthropicOAuth() {
12383
12408
  var btn = document.getElementById('gs-auth-btn');
12384
12409
  var desc = document.getElementById('gs-auth-desc');
12410
+ // Precheck: if the user is already authenticated (env var or keychain),
12411
+ // don't trigger the SDK OAuth flow — which has an upstream bug where
12412
+ // claudeAuthenticate sends a malformed Messages API request with
12413
+ // cache_control on an empty text block (HTTP 400).
12414
+ try {
12415
+ var statusResp = await apiFetch('/api/auth/anthropic/status');
12416
+ var statusData = await statusResp.json();
12417
+ if (statusData && statusData.authenticated) {
12418
+ var card = document.getElementById('gs-step-auth');
12419
+ if (card) card.className = 'gs-card gs-done';
12420
+ if (desc) desc.textContent = statusData.email
12421
+ ? 'Connected as ' + statusData.email
12422
+ : 'Already connected via ' + (statusData.apiKeySource || 'API') + ' — no login needed';
12423
+ if (btn) { btn.textContent = 'Connected'; btn.disabled = true; }
12424
+ return;
12425
+ }
12426
+ } catch (_) { /* fall through to real login */ }
12385
12427
  if (btn) { btn.textContent = 'Opening login...'; btn.disabled = true; }
12386
12428
  try {
12387
12429
  var r = await apiFetch('/api/auth/anthropic/login', { method: 'POST' });
@@ -12402,7 +12444,15 @@ async function startAnthropicOAuth() {
12402
12444
  throw new Error(wd.error || 'Login did not complete');
12403
12445
  }
12404
12446
  } catch (err) {
12405
- if (desc) desc.textContent = 'Login failed: ' + err.message;
12447
+ // Special case: the SDK's claudeAuthenticate has a known bug where it
12448
+ // sends malformed API payloads ("cache_control cannot be set for empty
12449
+ // text blocks"). If that fires, point the user at the CLI login path.
12450
+ var msg = err && err.message ? err.message : String(err);
12451
+ if (/cache_control.+empty text blocks/i.test(msg)) {
12452
+ if (desc) desc.textContent = 'In-browser login hit a known SDK bug. Run "clementine login" in your terminal instead, or check if you are already logged in — the daemon only needs to start once.';
12453
+ } else {
12454
+ if (desc) desc.textContent = 'Login failed: ' + msg;
12455
+ }
12406
12456
  if (btn) { btn.textContent = 'Retry Login'; btn.disabled = false; }
12407
12457
  }
12408
12458
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.0.74",
3
+ "version": "1.0.76",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",