@customclaw/composio 0.0.5 → 0.0.7

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/dist/client.js CHANGED
@@ -22,14 +22,33 @@ export class ComposioClient {
22
22
  /**
23
23
  * Get or create a Tool Router session for a user
24
24
  */
25
- async getSession(userId) {
26
- if (this.sessionCache.has(userId)) {
27
- return this.sessionCache.get(userId);
25
+ makeSessionCacheKey(userId, connectedAccounts) {
26
+ if (!connectedAccounts || Object.keys(connectedAccounts).length === 0) {
27
+ return `uid:${userId}`;
28
28
  }
29
- const session = await this.client.toolRouter.create(userId);
30
- this.sessionCache.set(userId, session);
29
+ const normalized = Object.entries(connectedAccounts)
30
+ .map(([toolkit, accountId]) => `${toolkit.toLowerCase()}=${accountId}`)
31
+ .sort()
32
+ .join(",");
33
+ return `uid:${userId}::ca:${normalized}`;
34
+ }
35
+ async getSession(userId, connectedAccounts) {
36
+ const key = this.makeSessionCacheKey(userId, connectedAccounts);
37
+ if (this.sessionCache.has(key)) {
38
+ return this.sessionCache.get(key);
39
+ }
40
+ const session = await this.client.toolRouter.create(userId, connectedAccounts ? { connectedAccounts } : undefined);
41
+ this.sessionCache.set(key, session);
31
42
  return session;
32
43
  }
44
+ clearUserSessionCache(userId) {
45
+ const prefix = `uid:${userId}`;
46
+ for (const key of this.sessionCache.keys()) {
47
+ if (!key.startsWith(prefix))
48
+ continue;
49
+ this.sessionCache.delete(key);
50
+ }
51
+ }
33
52
  /**
34
53
  * Check if a toolkit is allowed based on config
35
54
  */
@@ -111,9 +130,8 @@ export class ComposioClient {
111
130
  /**
112
131
  * Execute a single tool using COMPOSIO_MULTI_EXECUTE_TOOL
113
132
  */
114
- async executeTool(toolSlug, args, userId) {
133
+ async executeTool(toolSlug, args, userId, connectedAccountId) {
115
134
  const uid = this.getUserId(userId);
116
- const session = await this.getSession(uid);
117
135
  const toolkit = toolSlug.split("_")[0]?.toLowerCase() || "";
118
136
  if (!this.isToolkitAllowed(toolkit)) {
119
137
  return {
@@ -121,6 +139,17 @@ export class ComposioClient {
121
139
  error: `Toolkit '${toolkit}' is not allowed by plugin configuration`,
122
140
  };
123
141
  }
142
+ const accountResolution = await this.resolveConnectedAccountForExecution({
143
+ toolkit,
144
+ userId: uid,
145
+ connectedAccountId,
146
+ });
147
+ if ("error" in accountResolution) {
148
+ return { success: false, error: accountResolution.error };
149
+ }
150
+ const session = await this.getSession(uid, accountResolution.connectedAccountId
151
+ ? { [toolkit]: accountResolution.connectedAccountId }
152
+ : undefined);
124
153
  try {
125
154
  const response = await this.executeMetaTool("COMPOSIO_MULTI_EXECUTE_TOOL", {
126
155
  tools: [{ tool_slug: toolSlug, arguments: args }],
@@ -128,6 +157,16 @@ export class ComposioClient {
128
157
  sync_response_to_workbench: false,
129
158
  });
130
159
  if (!response.successful) {
160
+ const recovered = await this.tryExecutionRecovery({
161
+ uid,
162
+ toolSlug,
163
+ args,
164
+ connectedAccountId: accountResolution.connectedAccountId,
165
+ metaError: response.error,
166
+ metaData: response.data,
167
+ });
168
+ if (recovered)
169
+ return recovered;
131
170
  return { success: false, error: response.error || "Execution failed" };
132
171
  }
133
172
  const results = response.data?.results || [];
@@ -137,6 +176,18 @@ export class ComposioClient {
137
176
  }
138
177
  // Response data is nested under result.response
139
178
  const toolResponse = result.response;
179
+ if (!toolResponse.successful) {
180
+ const recovered = await this.tryExecutionRecovery({
181
+ uid,
182
+ toolSlug,
183
+ args,
184
+ connectedAccountId: accountResolution.connectedAccountId,
185
+ metaError: toolResponse.error ?? undefined,
186
+ metaData: response.data,
187
+ });
188
+ if (recovered)
189
+ return recovered;
190
+ }
140
191
  return {
141
192
  success: toolResponse.successful,
142
193
  data: toolResponse.data,
@@ -150,6 +201,159 @@ export class ComposioClient {
150
201
  };
151
202
  }
152
203
  }
204
+ async tryExecutionRecovery(params) {
205
+ const directFallback = await this.tryDirectExecutionFallback(params);
206
+ if (directFallback?.success)
207
+ return directFallback;
208
+ const hintedRetry = await this.tryHintedIdentifierRetry({
209
+ ...params,
210
+ additionalError: directFallback?.error,
211
+ });
212
+ if (hintedRetry)
213
+ return hintedRetry;
214
+ return directFallback;
215
+ }
216
+ async tryDirectExecutionFallback(params) {
217
+ if (!this.shouldFallbackToDirectExecution(params.uid, params.metaError, params.metaData)) {
218
+ return null;
219
+ }
220
+ return this.executeDirectTool(params.toolSlug, params.uid, params.args, params.connectedAccountId);
221
+ }
222
+ async executeDirectTool(toolSlug, userId, args, connectedAccountId) {
223
+ try {
224
+ const response = await this.client.tools.execute(toolSlug, {
225
+ userId,
226
+ connectedAccountId,
227
+ arguments: args,
228
+ dangerouslySkipVersionCheck: true,
229
+ });
230
+ return {
231
+ success: Boolean(response?.successful),
232
+ data: response?.data,
233
+ error: response?.error ?? undefined,
234
+ };
235
+ }
236
+ catch (err) {
237
+ return {
238
+ success: false,
239
+ error: err instanceof Error ? err.message : String(err),
240
+ };
241
+ }
242
+ }
243
+ async tryHintedIdentifierRetry(params) {
244
+ const combined = this.buildCombinedErrorText(params.metaError, params.metaData, params.additionalError);
245
+ if (!this.shouldRetryFromServerHint(combined))
246
+ return null;
247
+ const hint = this.extractServerHintLiteral(combined);
248
+ if (!hint)
249
+ return null;
250
+ const retryArgs = this.buildRetryArgsFromHint(params.args, combined, hint);
251
+ if (!retryArgs)
252
+ return null;
253
+ return this.executeDirectTool(params.toolSlug, params.uid, retryArgs, params.connectedAccountId);
254
+ }
255
+ shouldFallbackToDirectExecution(uid, metaError, metaData) {
256
+ if (uid === "default")
257
+ return false;
258
+ const combined = this.buildCombinedErrorText(metaError, metaData).toLowerCase();
259
+ return combined.includes("no connected account found for entity id default");
260
+ }
261
+ shouldRetryFromServerHint(errorText) {
262
+ const lower = errorText.toLowerCase();
263
+ return (lower.includes("only allowed to access") ||
264
+ lower.includes("allowed to access the"));
265
+ }
266
+ extractServerHintLiteral(errorText) {
267
+ const matches = errorText.match(/`([^`]+)`/);
268
+ if (!matches?.[1])
269
+ return undefined;
270
+ const literal = matches[1].trim();
271
+ if (!literal)
272
+ return undefined;
273
+ if (literal.length > 64)
274
+ return undefined;
275
+ if (/\s/.test(literal))
276
+ return undefined;
277
+ return literal;
278
+ }
279
+ buildRetryArgsFromHint(args, errorText, hint) {
280
+ const stringEntries = Object.entries(args).filter(([, value]) => typeof value === "string");
281
+ if (stringEntries.length === 1) {
282
+ const [field, current] = stringEntries[0];
283
+ if (current === hint)
284
+ return null;
285
+ return { ...args, [field]: hint };
286
+ }
287
+ if (stringEntries.length === 0) {
288
+ const missing = this.extractSingleMissingField(errorText);
289
+ if (!missing)
290
+ return null;
291
+ return { ...args, [missing]: hint };
292
+ }
293
+ return null;
294
+ }
295
+ extractSingleMissingField(errorText) {
296
+ const match = errorText.match(/following fields are missing:\s*\{([^}]+)\}/i);
297
+ const raw = match?.[1];
298
+ if (!raw)
299
+ return undefined;
300
+ const fields = raw
301
+ .split(",")
302
+ .map(part => part.trim().replace(/^['"]|['"]$/g, ""))
303
+ .filter(Boolean);
304
+ return fields.length === 1 ? fields[0] : undefined;
305
+ }
306
+ buildCombinedErrorText(metaError, metaData, additionalError) {
307
+ return [metaError, this.extractNestedMetaError(metaData), additionalError]
308
+ .map(v => String(v || "").trim())
309
+ .filter(Boolean)
310
+ .join("\n");
311
+ }
312
+ extractNestedMetaError(metaData) {
313
+ const results = metaData?.results || [];
314
+ const first = results[0];
315
+ return String(first?.error || "");
316
+ }
317
+ async resolveConnectedAccountForExecution(params) {
318
+ const { toolkit, userId } = params;
319
+ const explicitId = params.connectedAccountId?.trim();
320
+ if (explicitId) {
321
+ try {
322
+ const account = await this.client.connectedAccounts.get(explicitId);
323
+ const accountToolkit = String(account?.toolkit?.slug || "").toLowerCase();
324
+ const accountStatus = String(account?.status || "").toUpperCase();
325
+ if (accountToolkit && accountToolkit !== toolkit) {
326
+ return {
327
+ error: `Connected account '${explicitId}' belongs to toolkit '${accountToolkit}', but tool '${toolkit}' was requested.`,
328
+ };
329
+ }
330
+ if (accountStatus && accountStatus !== "ACTIVE") {
331
+ return {
332
+ error: `Connected account '${explicitId}' is '${accountStatus}', not ACTIVE.`,
333
+ };
334
+ }
335
+ return { connectedAccountId: explicitId };
336
+ }
337
+ catch (err) {
338
+ return {
339
+ error: `Invalid connected_account_id '${explicitId}': ${err instanceof Error ? err.message : String(err)}`,
340
+ };
341
+ }
342
+ }
343
+ const activeAccounts = await this.listConnectedAccounts({
344
+ toolkits: [toolkit],
345
+ userIds: [userId],
346
+ statuses: ["ACTIVE"],
347
+ });
348
+ if (activeAccounts.length <= 1) {
349
+ return { connectedAccountId: activeAccounts[0]?.id };
350
+ }
351
+ const ids = activeAccounts.map(a => a.id).join(", ");
352
+ return {
353
+ error: `Multiple ACTIVE '${toolkit}' accounts found for user_id '${userId}': ${ids}. ` +
354
+ "Please provide connected_account_id to choose one explicitly.",
355
+ };
356
+ }
153
357
  /**
154
358
  * Get connection status for toolkits using session.toolkits()
155
359
  */
@@ -157,42 +361,255 @@ export class ComposioClient {
157
361
  const uid = this.getUserId(userId);
158
362
  const session = await this.getSession(uid);
159
363
  try {
160
- const response = await session.toolkits();
161
- const allToolkits = response.items || [];
162
- const statuses = [];
163
364
  if (toolkits && toolkits.length > 0) {
164
- // Check specific toolkits
165
- for (const toolkit of toolkits) {
166
- if (!this.isToolkitAllowed(toolkit))
167
- continue;
168
- const found = allToolkits.find(t => t.slug.toLowerCase() === toolkit.toLowerCase());
169
- statuses.push({
365
+ const requestedToolkits = toolkits.filter(t => this.isToolkitAllowed(t));
366
+ if (requestedToolkits.length === 0)
367
+ return [];
368
+ const toolkitStateMap = await this.getToolkitStateMap(session, requestedToolkits);
369
+ const activeAccountToolkits = await this.getActiveConnectedAccountToolkits(uid, requestedToolkits);
370
+ return requestedToolkits.map((toolkit) => {
371
+ const key = toolkit.toLowerCase();
372
+ return {
170
373
  toolkit,
171
- connected: found?.connection?.isActive ?? false,
374
+ connected: (toolkitStateMap.get(key) ?? false) || activeAccountToolkits.has(key),
172
375
  userId: uid,
173
- });
174
- }
376
+ };
377
+ });
175
378
  }
176
- else {
177
- // Return all connected toolkits
178
- for (const tk of allToolkits) {
179
- if (!this.isToolkitAllowed(tk.slug))
180
- continue;
181
- if (!tk.connection?.isActive)
182
- continue;
183
- statuses.push({
184
- toolkit: tk.slug,
185
- connected: true,
186
- userId: uid,
187
- });
188
- }
379
+ const toolkitStateMap = await this.getToolkitStateMap(session);
380
+ const activeAccountToolkits = await this.getActiveConnectedAccountToolkits(uid);
381
+ const connected = new Set();
382
+ for (const [slug, isActive] of toolkitStateMap.entries()) {
383
+ if (!isActive)
384
+ continue;
385
+ if (!this.isToolkitAllowed(slug))
386
+ continue;
387
+ connected.add(slug);
388
+ }
389
+ for (const slug of activeAccountToolkits) {
390
+ if (!this.isToolkitAllowed(slug))
391
+ continue;
392
+ connected.add(slug);
189
393
  }
190
- return statuses;
394
+ return Array.from(connected).map((toolkit) => ({
395
+ toolkit,
396
+ connected: true,
397
+ userId: uid,
398
+ }));
191
399
  }
192
400
  catch (err) {
193
401
  throw new Error(`Failed to get connection status: ${err instanceof Error ? err.message : String(err)}`);
194
402
  }
195
403
  }
404
+ async getToolkitStateMap(session, toolkits) {
405
+ const map = new Map();
406
+ let nextCursor;
407
+ const seenCursors = new Set();
408
+ do {
409
+ const response = await session.toolkits({
410
+ nextCursor,
411
+ limit: 100,
412
+ ...(toolkits && toolkits.length > 0 ? { toolkits } : { isConnected: true }),
413
+ });
414
+ const items = response.items || [];
415
+ for (const tk of items) {
416
+ const key = tk.slug.toLowerCase();
417
+ const isActive = tk.connection?.isActive ?? false;
418
+ map.set(key, (map.get(key) ?? false) || isActive);
419
+ }
420
+ nextCursor = response.nextCursor;
421
+ if (!nextCursor)
422
+ break;
423
+ if (seenCursors.has(nextCursor))
424
+ break;
425
+ seenCursors.add(nextCursor);
426
+ } while (true);
427
+ return map;
428
+ }
429
+ async getActiveConnectedAccountToolkits(userId, toolkits) {
430
+ const connected = new Set();
431
+ let cursor;
432
+ const seenCursors = new Set();
433
+ try {
434
+ do {
435
+ const response = await this.client.connectedAccounts.list({
436
+ userIds: [userId],
437
+ statuses: ["ACTIVE"],
438
+ ...(toolkits && toolkits.length > 0 ? { toolkitSlugs: toolkits } : {}),
439
+ limit: 100,
440
+ ...(cursor ? { cursor } : {}),
441
+ });
442
+ const items = (Array.isArray(response)
443
+ ? response
444
+ : response?.items || []);
445
+ for (const item of items) {
446
+ const slug = item.toolkit?.slug;
447
+ if (!slug)
448
+ continue;
449
+ if (item.status && String(item.status).toUpperCase() !== "ACTIVE")
450
+ continue;
451
+ connected.add(slug.toLowerCase());
452
+ }
453
+ cursor = Array.isArray(response)
454
+ ? null
455
+ : (response?.nextCursor ?? null);
456
+ if (!cursor)
457
+ break;
458
+ if (seenCursors.has(cursor))
459
+ break;
460
+ seenCursors.add(cursor);
461
+ } while (true);
462
+ return connected;
463
+ }
464
+ catch {
465
+ // Best-effort fallback: preserve status checks based on session.toolkits only.
466
+ return connected;
467
+ }
468
+ }
469
+ normalizeStatuses(statuses) {
470
+ if (!statuses || statuses.length === 0)
471
+ return undefined;
472
+ const allowed = new Set(["INITIALIZING", "INITIATED", "ACTIVE", "FAILED", "EXPIRED", "INACTIVE"]);
473
+ const normalized = statuses
474
+ .map(s => String(s || "").trim().toUpperCase())
475
+ .filter(s => allowed.has(s));
476
+ return normalized.length > 0 ? Array.from(new Set(normalized)) : undefined;
477
+ }
478
+ /**
479
+ * List connected accounts with optional filters.
480
+ * Uses raw API first to preserve user_id in responses, then falls back to SDK-normalized output.
481
+ */
482
+ async listConnectedAccounts(options) {
483
+ const toolkits = options?.toolkits
484
+ ?.map(t => String(t || "").trim())
485
+ .filter(t => t.length > 0 && this.isToolkitAllowed(t));
486
+ const userIds = options?.userIds
487
+ ?.map(u => String(u || "").trim())
488
+ .filter(Boolean);
489
+ const statuses = this.normalizeStatuses(options?.statuses);
490
+ if (options?.toolkits && (!toolkits || toolkits.length === 0))
491
+ return [];
492
+ try {
493
+ return await this.listConnectedAccountsRaw({
494
+ toolkits,
495
+ userIds,
496
+ statuses,
497
+ });
498
+ }
499
+ catch {
500
+ return this.listConnectedAccountsFallback({
501
+ toolkits,
502
+ userIds,
503
+ statuses,
504
+ });
505
+ }
506
+ }
507
+ /**
508
+ * Find user IDs that have an active connected account for a toolkit.
509
+ */
510
+ async findActiveUserIdsForToolkit(toolkit) {
511
+ if (!this.isToolkitAllowed(toolkit))
512
+ return [];
513
+ const accounts = await this.listConnectedAccounts({
514
+ toolkits: [toolkit],
515
+ statuses: ["ACTIVE"],
516
+ });
517
+ const userIds = new Set();
518
+ for (const account of accounts) {
519
+ if (account.userId)
520
+ userIds.add(account.userId);
521
+ }
522
+ return Array.from(userIds).sort();
523
+ }
524
+ async listConnectedAccountsRaw(options) {
525
+ const accounts = [];
526
+ let cursor;
527
+ const seenCursors = new Set();
528
+ do {
529
+ const response = await this.client.client.connectedAccounts.list({
530
+ ...(options?.toolkits && options.toolkits.length > 0 ? { toolkit_slugs: options.toolkits } : {}),
531
+ ...(options?.userIds && options.userIds.length > 0 ? { user_ids: options.userIds } : {}),
532
+ ...(options?.statuses && options.statuses.length > 0 ? { statuses: options.statuses } : {}),
533
+ limit: 100,
534
+ ...(cursor ? { cursor } : {}),
535
+ });
536
+ const items = (Array.isArray(response)
537
+ ? response
538
+ : response?.items || []);
539
+ for (const item of items) {
540
+ const toolkitSlug = (item.toolkit?.slug || "").toString().toLowerCase();
541
+ if (!toolkitSlug)
542
+ continue;
543
+ if (!this.isToolkitAllowed(toolkitSlug))
544
+ continue;
545
+ accounts.push({
546
+ id: String(item.id || ""),
547
+ toolkit: toolkitSlug,
548
+ userId: typeof item.user_id === "string" ? item.user_id : undefined,
549
+ status: typeof item.status === "string" ? item.status : undefined,
550
+ authConfigId: typeof item.auth_config?.id === "string"
551
+ ? item.auth_config.id
552
+ : undefined,
553
+ isDisabled: typeof item.is_disabled === "boolean" ? item.is_disabled : undefined,
554
+ createdAt: typeof item.created_at === "string" ? item.created_at : undefined,
555
+ updatedAt: typeof item.updated_at === "string" ? item.updated_at : undefined,
556
+ });
557
+ }
558
+ cursor = Array.isArray(response)
559
+ ? null
560
+ : (response?.next_cursor ?? null);
561
+ if (!cursor)
562
+ break;
563
+ if (seenCursors.has(cursor))
564
+ break;
565
+ seenCursors.add(cursor);
566
+ } while (true);
567
+ return accounts;
568
+ }
569
+ async listConnectedAccountsFallback(options) {
570
+ const accounts = [];
571
+ let cursor;
572
+ const seenCursors = new Set();
573
+ do {
574
+ const response = await this.client.connectedAccounts.list({
575
+ ...(options?.toolkits && options.toolkits.length > 0 ? { toolkitSlugs: options.toolkits } : {}),
576
+ ...(options?.userIds && options.userIds.length > 0 ? { userIds: options.userIds } : {}),
577
+ ...(options?.statuses && options.statuses.length > 0 ? { statuses: options.statuses } : {}),
578
+ limit: 100,
579
+ ...(cursor ? { cursor } : {}),
580
+ });
581
+ const items = (Array.isArray(response)
582
+ ? response
583
+ : response?.items || []);
584
+ for (const item of items) {
585
+ const toolkitSlug = (item.toolkit?.slug || "").toString().toLowerCase();
586
+ if (!toolkitSlug)
587
+ continue;
588
+ if (!this.isToolkitAllowed(toolkitSlug))
589
+ continue;
590
+ accounts.push({
591
+ id: String(item.id || ""),
592
+ toolkit: toolkitSlug,
593
+ status: typeof item.status === "string" ? item.status : undefined,
594
+ authConfigId: typeof item.authConfig?.id === "string"
595
+ ? item.authConfig.id
596
+ : undefined,
597
+ isDisabled: typeof item.isDisabled === "boolean" ? item.isDisabled : undefined,
598
+ createdAt: typeof item.createdAt === "string" ? item.createdAt : undefined,
599
+ updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : undefined,
600
+ });
601
+ }
602
+ cursor = Array.isArray(response)
603
+ ? null
604
+ : (response?.nextCursor ?? null);
605
+ if (!cursor)
606
+ break;
607
+ if (seenCursors.has(cursor))
608
+ break;
609
+ seenCursors.add(cursor);
610
+ } while (true);
611
+ return accounts;
612
+ }
196
613
  /**
197
614
  * Create an auth connection for a toolkit using session.authorize()
198
615
  */
@@ -219,11 +636,29 @@ export class ComposioClient {
219
636
  const uid = this.getUserId(userId);
220
637
  try {
221
638
  const session = await this.getSession(uid);
222
- const response = await session.toolkits();
223
- const allToolkits = response.items || [];
224
- return allToolkits
225
- .map(tk => tk.slug)
226
- .filter(slug => this.isToolkitAllowed(slug));
639
+ const seen = new Set();
640
+ let nextCursor;
641
+ const seenCursors = new Set();
642
+ do {
643
+ const response = await session.toolkits({
644
+ nextCursor,
645
+ limit: 100,
646
+ });
647
+ const allToolkits = response.items || [];
648
+ for (const tk of allToolkits) {
649
+ const slug = tk.slug.toLowerCase();
650
+ if (!this.isToolkitAllowed(slug))
651
+ continue;
652
+ seen.add(slug);
653
+ }
654
+ nextCursor = response.nextCursor;
655
+ if (!nextCursor)
656
+ break;
657
+ if (seenCursors.has(nextCursor))
658
+ break;
659
+ seenCursors.add(nextCursor);
660
+ } while (true);
661
+ return Array.from(seen);
227
662
  }
228
663
  catch (err) {
229
664
  const errObj = err;
@@ -240,17 +675,25 @@ export class ComposioClient {
240
675
  async disconnectToolkit(toolkit, userId) {
241
676
  const uid = this.getUserId(userId);
242
677
  try {
243
- const response = await this.client.connectedAccounts.list({ userId: uid });
244
- const connections = (Array.isArray(response)
245
- ? response
246
- : response?.items || []);
247
- const conn = connections.find(c => c.toolkit?.slug?.toLowerCase() === toolkit.toLowerCase());
248
- if (!conn) {
678
+ const activeAccounts = await this.listConnectedAccounts({
679
+ toolkits: [toolkit],
680
+ userIds: [uid],
681
+ statuses: ["ACTIVE"],
682
+ });
683
+ if (activeAccounts.length === 0) {
249
684
  return { success: false, error: `No connection found for toolkit '${toolkit}'` };
250
685
  }
251
- await this.client.connectedAccounts.delete({ connectedAccountId: conn.id });
686
+ if (activeAccounts.length > 1) {
687
+ const ids = activeAccounts.map(a => a.id).join(", ");
688
+ return {
689
+ success: false,
690
+ error: `Multiple ACTIVE '${toolkit}' accounts found for user_id '${uid}': ${ids}. ` +
691
+ "Use the dashboard to disconnect a specific account.",
692
+ };
693
+ }
694
+ await this.client.connectedAccounts.delete({ connectedAccountId: activeAccounts[0].id });
252
695
  // Clear session cache to refresh connection status
253
- this.sessionCache.delete(uid);
696
+ this.clearUserSessionCache(uid);
254
697
  return { success: true };
255
698
  }
256
699
  catch (err) {