clementine-agent 1.12.0 → 1.12.1

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.
@@ -4406,8 +4406,16 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
4406
4406
  // This replaces the hardcoded CURATED_TOOLKITS — slug typos are now
4407
4407
  // impossible because we render directly from Composio's data, and
4408
4408
  // newly-added services appear automatically.
4409
- const [catalog, connected, configured] = await Promise.all([
4410
- c.listAllToolkits(),
4409
+ let catalog = [];
4410
+ let catalogError = null;
4411
+ try {
4412
+ catalog = await c.listAllToolkits();
4413
+ }
4414
+ catch (err) {
4415
+ catalogError = err?.message ?? String(err);
4416
+ console.error('[composio] catalog fetch failed:', catalogError);
4417
+ }
4418
+ const [connected, configured] = await Promise.all([
4411
4419
  c.listConnectedToolkits(),
4412
4420
  c.listToolkitSlugsWithAuthConfig(),
4413
4421
  ]);
@@ -4475,6 +4483,7 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
4475
4483
  toolkits,
4476
4484
  featured: featured.map(t => t.slug),
4477
4485
  totalCount: toolkits.length,
4486
+ catalogError,
4478
4487
  });
4479
4488
  }
4480
4489
  catch (err) {
@@ -25794,7 +25803,18 @@ async function refreshComposioConnections() {
25794
25803
  }
25795
25804
  var toolkits = d.toolkits || [];
25796
25805
  if (toolkits.length === 0) {
25797
- container.innerHTML = '<div class="empty-state" style="padding:16px">No toolkits available. Check that your Composio API key is valid.</div>';
25806
+ // Distinguish "catalog fetch failed" from "user has nothing connected".
25807
+ // Surfacing d.catalogError tells us if Composio rejected the catalog
25808
+ // call (different permission than connectedAccounts.list, etc.).
25809
+ var msg = d.catalogError
25810
+ ? 'Could not load toolkits from Composio: <code style="color:var(--red);font-size:11px">' + esc(d.catalogError) + '</code>'
25811
+ : 'Composio returned an empty toolkit catalog. This is unusual — try refreshing.';
25812
+ container.innerHTML =
25813
+ '<div style="padding:16px">' +
25814
+ '<div style="margin-bottom:12px;font-size:13px">' + msg + '</div>' +
25815
+ '<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px">If your existing connections (e.g. Outlook) work via the agent, your API key is valid — the catalog endpoint may require a different permission tier or there\\'s a transient API issue.</div>' +
25816
+ '<button class="btn btn-sm btn-primary" onclick="refreshComposioConnections()">Retry</button>' +
25817
+ '</div>';
25798
25818
  return;
25799
25819
  }
25800
25820
  // Stash full catalog in a closure so the search input can re-render
@@ -79,8 +79,8 @@ export interface CatalogToolkit {
79
79
  * static CURATED_TOOLKITS array as the source of truth for the dashboard
80
80
  * Connections panel — slug typos are now impossible because we render from
81
81
  * Composio's own data, and new services appear automatically as they're
82
- * added. Cached at module level for 1 hour; resetComposioClient() clears
83
- * the cache when the API key changes.
82
+ * added. Cached at module level for 1 hour on success; failures don't
83
+ * cache (so the next request retries instead of returning stale empty).
84
84
  */
85
85
  export declare function listAllToolkits(): Promise<CatalogToolkit[]>;
86
86
  export declare function listToolkitMeta(): Promise<Map<string, ToolkitMeta>>;
@@ -342,57 +342,88 @@ const CATALOG_TTL_MS = 60 * 60 * 1000; // 1h — catalog drifts very slowly
342
342
  * static CURATED_TOOLKITS array as the source of truth for the dashboard
343
343
  * Connections panel — slug typos are now impossible because we render from
344
344
  * Composio's own data, and new services appear automatically as they're
345
- * added. Cached at module level for 1 hour; resetComposioClient() clears
346
- * the cache when the API key changes.
345
+ * added. Cached at module level for 1 hour on success; failures don't
346
+ * cache (so the next request retries instead of returning stale empty).
347
347
  */
348
348
  export async function listAllToolkits() {
349
349
  const now = Date.now();
350
- if (catalogCache && now - catalogCache.at < CATALOG_TTL_MS) {
350
+ if (catalogCache && catalogCache.data.length > 0 && now - catalogCache.at < CATALOG_TTL_MS) {
351
351
  return catalogCache.data;
352
352
  }
353
353
  const composio = getComposio();
354
354
  if (!composio)
355
355
  return [];
356
- const out = [];
357
- let cursor;
358
- // Bounded loop 30 pages × 500 items = 15K ceiling, far more than any
359
- // realistic catalog. Composio currently returns ~1.5K items across 3
360
- // pages, but the cap makes a runaway impossible.
361
- for (let page = 0; page < 30; page++) {
356
+ // Try the raw client first — supports cursor pagination so we get the full
357
+ // 1000+ catalog. If that errors (some API keys / plans restrict it), fall
358
+ // back to the high-level wrapper which returns up to 500 in one shot.
359
+ let result = [];
360
+ let lastError = null;
361
+ try {
362
+ result = await fetchCatalogViaRawClient(composio);
363
+ }
364
+ catch (err) {
365
+ lastError = err;
366
+ logger.warn({ err }, 'Raw-client toolkit list failed — falling back to high-level wrapper');
367
+ }
368
+ if (result.length === 0) {
362
369
  try {
363
- const rawClient = composio.client;
364
- const resp = await rawClient.toolkits.list({
365
- limit: 500,
366
- ...(cursor ? { cursor } : {}),
367
- });
368
- const items = (resp?.items ?? []);
369
- for (const it of items) {
370
- const managed = (it.composioManagedAuthSchemes ?? it.composio_managed_auth_schemes ?? []);
371
- const schemes = (it.authSchemes ?? it.auth_schemes ?? []);
372
- const noAuth = it.noAuth ?? it.no_auth ?? false;
373
- out.push({
374
- slug: it.slug,
375
- name: it.name,
376
- logoUrl: it.meta?.logo,
377
- description: it.meta?.description,
378
- toolsCount: it.meta?.toolsCount ?? it.meta?.tools_count,
379
- authMode: noAuth ? 'none' : (managed.length > 0 ? 'managed' : (schemes.length > 0 ? 'byo' : 'none')),
380
- categories: it.meta?.categories ?? [],
381
- });
382
- }
383
- cursor = resp?.next_cursor ?? resp?.nextCursor;
384
- if (!cursor || items.length === 0)
385
- break;
370
+ result = await fetchCatalogViaWrapper(composio);
386
371
  }
387
372
  catch (err) {
388
- logger.warn({ err, page }, 'listAllToolkits page failed — stopping pagination');
389
- break;
373
+ lastError = err;
374
+ logger.error({ err }, 'High-level toolkit list also failed');
390
375
  }
391
376
  }
392
- catalogCache = { at: now, data: out };
393
- logger.info({ count: out.length }, 'Composio catalog fetched');
377
+ if (result.length === 0) {
378
+ // Surface whichever error we hit so the dashboard can render something
379
+ // actionable instead of "no toolkits available".
380
+ const detail = lastError?.message ?? 'Unknown error';
381
+ throw new Error(`Composio catalog fetch failed: ${detail}`);
382
+ }
383
+ catalogCache = { at: now, data: result };
384
+ logger.info({ count: result.length }, 'Composio catalog fetched');
385
+ return result;
386
+ }
387
+ function normalizeCatalogItem(it) {
388
+ const managed = (it.composioManagedAuthSchemes ?? it.composio_managed_auth_schemes ?? []);
389
+ const schemes = (it.authSchemes ?? it.auth_schemes ?? []);
390
+ const noAuth = it.noAuth ?? it.no_auth ?? false;
391
+ return {
392
+ slug: it.slug,
393
+ name: it.name,
394
+ logoUrl: it.meta?.logo,
395
+ description: it.meta?.description,
396
+ toolsCount: it.meta?.toolsCount ?? it.meta?.tools_count,
397
+ authMode: noAuth ? 'none' : (managed.length > 0 ? 'managed' : (schemes.length > 0 ? 'byo' : 'none')),
398
+ categories: it.meta?.categories ?? [],
399
+ };
400
+ }
401
+ async function fetchCatalogViaRawClient(composio) {
402
+ const out = [];
403
+ let cursor;
404
+ // Bounded loop — 30 pages × 500 items = 15K ceiling.
405
+ for (let page = 0; page < 30; page++) {
406
+ const rawClient = composio.client;
407
+ const resp = await rawClient.toolkits.list({
408
+ limit: 500,
409
+ ...(cursor ? { cursor } : {}),
410
+ });
411
+ const items = (resp?.items ?? []);
412
+ for (const it of items)
413
+ out.push(normalizeCatalogItem(it));
414
+ cursor = resp?.next_cursor ?? resp?.nextCursor;
415
+ if (!cursor || items.length === 0)
416
+ break;
417
+ }
394
418
  return out;
395
419
  }
420
+ async function fetchCatalogViaWrapper(composio) {
421
+ // High-level wrapper returns an array (up to limit). No cursor support
422
+ // through this path, so we cap at the documented max. Better than zero.
423
+ const resp = await composio.toolkits.get({ limit: 500 });
424
+ const items = (Array.isArray(resp) ? resp : (resp?.items ?? []));
425
+ return items.map(normalizeCatalogItem);
426
+ }
396
427
  async function fetchAllToolkitMeta() {
397
428
  const composio = getComposio();
398
429
  if (!composio)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",