clementine-agent 1.10.4 → 1.11.0

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.
@@ -61,7 +61,7 @@ export declare class ComposioNeedsAuthConfigError extends Error {
61
61
  readonly underlying: string;
62
62
  constructor(slug: string, underlying: string);
63
63
  }
64
- export declare function authorizeToolkit(slug: string, opts?: {
64
+ export declare function authorizeToolkit(slug: string, _opts?: {
65
65
  callbackUrl?: string;
66
66
  alias?: string;
67
67
  }): Promise<{
@@ -98,7 +98,11 @@ export async function getPreferredUserId() {
98
98
  // calls have to pass *some* user_id, and we want it to match whatever the
99
99
  // user already has if possible. detectPreferredUserId() picks the user_id
100
100
  // with the most existing connections, falling back to this constant.
101
- const DEFAULT_NEW_CONNECTION_USER_ID = 'default';
101
+ // Empty string matches Composio's web-UI default user_id (verified by
102
+ // probing — connections created via dashboard.composio.dev land under '').
103
+ // Using the same value here keeps Clementine-authorized connections in the
104
+ // same bucket as anything the user already has from the web UI.
105
+ const DEFAULT_NEW_CONNECTION_USER_ID = '';
102
106
  export function clementineUserId() {
103
107
  return readComposioEnv('COMPOSIO_USER_ID') || DEFAULT_NEW_CONNECTION_USER_ID;
104
108
  }
@@ -108,26 +112,37 @@ async function detectPreferredUserId(composio) {
108
112
  const explicit = readComposioEnv('COMPOSIO_USER_ID');
109
113
  if (explicit)
110
114
  return explicit;
111
- if (detectedPreferredUserId)
115
+ if (detectedPreferredUserId !== null)
112
116
  return detectedPreferredUserId;
117
+ // The list response doesn't expose userId on records (verified empirically),
118
+ // so we can't read it from the data. Instead, probe each candidate user_id
119
+ // with a filtered list and pick whichever returns connections. This matches
120
+ // however the connections were originally created — including empty string,
121
+ // which is what Composio's web UI uses by default for new accounts.
122
+ const candidates = ['', 'default', 'user', 'me'];
123
+ let totalUnscoped = 0;
113
124
  try {
114
- const resp = await composio.connectedAccounts.list({ limit: 100 });
115
- const counts = new Map();
116
- for (const it of resp.items) {
117
- const uid = it.userId ?? it.user_id;
118
- if (typeof uid === 'string' && uid.length > 0) {
119
- counts.set(uid, (counts.get(uid) ?? 0) + 1);
125
+ const all = await composio.connectedAccounts.list({ limit: 1 });
126
+ totalUnscoped = all.items.length;
127
+ }
128
+ catch { /* non-fatal */ }
129
+ if (totalUnscoped > 0) {
130
+ for (const uid of candidates) {
131
+ try {
132
+ const resp = await composio.connectedAccounts.list({ userIds: [uid], limit: 1 });
133
+ if (resp.items.length > 0) {
134
+ logger.info({ userId: uid }, 'Detected Composio user_id via probe');
135
+ detectedPreferredUserId = uid;
136
+ return uid;
137
+ }
120
138
  }
139
+ catch { /* try next */ }
121
140
  }
122
- if (counts.size > 0) {
123
- const top = [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
124
- detectedPreferredUserId = top;
125
- return top;
126
- }
127
- }
128
- catch (err) {
129
- logger.debug({ err }, 'detectPreferredUserId failed — using default');
130
141
  }
142
+ // No connections yet, or none of the common user_ids match. Fall back to
143
+ // empty string, which is what Composio's web UI uses by default — keeps
144
+ // future Clementine-authorized connections in the same bucket as anything
145
+ // the user creates in Composio's dashboard.
131
146
  detectedPreferredUserId = DEFAULT_NEW_CONNECTION_USER_ID;
132
147
  return DEFAULT_NEW_CONNECTION_USER_ID;
133
148
  }
@@ -388,66 +403,34 @@ export class ComposioNeedsAuthConfigError extends Error {
388
403
  this.name = 'ComposioNeedsAuthConfigError';
389
404
  }
390
405
  }
391
- export async function authorizeToolkit(slug, opts) {
406
+ export async function authorizeToolkit(slug,
407
+ // Reserved for future per-call overrides (callbackUrl / alias). Currently
408
+ // unused because composio.toolkits.authorize doesn't surface those options
409
+ // — alias is set later via patch, callback uses Composio's hosted page.
410
+ _opts) {
392
411
  const composio = getComposio();
393
412
  if (!composio)
394
413
  throw new Error('COMPOSIO_API_KEY not set');
395
- // 1. Find or create an auth config. session.authorize() doesn't auto-create
396
- // so we have to pass authConfigId explicitly to connectedAccounts.initiate.
397
- let authConfigId;
398
- let existingConfig;
399
- try {
400
- existingConfig = (await composio.authConfigs.list({ toolkit: slug })).items[0];
401
- }
402
- catch (err) {
403
- logger.error({ err, slug, step: 'authConfigs.list' }, 'Composio authorize failed');
404
- throw err;
405
- }
406
- if (existingConfig) {
407
- authConfigId = existingConfig.id;
408
- logger.debug({ slug, authConfigId }, 'Reusing existing auth config');
409
- }
410
- else {
411
- try {
412
- const created = await composio.authConfigs.create(slug, {
413
- type: 'use_composio_managed_auth',
414
- name: `${displayNameFor(slug)} Auth Config`,
415
- });
416
- authConfigId = created.id;
417
- logger.debug({ slug, authConfigId }, 'Created managed auth config');
418
- }
419
- catch (err) {
420
- const status = err?.status;
421
- logger.warn({ err, slug, status, step: 'authConfigs.create' }, 'authConfigs.create rejected — likely needs BYO setup');
422
- // 400 → Composio doesn't host a managed OAuth app for this toolkit.
423
- // 401/403 → key lacks permission to create managed auth configs (common
424
- // for Google services where Composio's plan tier requires you to
425
- // register your own Google OAuth project). Either way, the user fix
426
- // is the same: set it up once in Composio's auth-configs dashboard.
427
- if (status === 400 || status === 401 || status === 403) {
428
- throw new ComposioNeedsAuthConfigError(slug, String(err));
429
- }
430
- throw err;
431
- }
432
- }
433
- // 2. Initiate the connection. allowMultiple if there's already an active
434
- // connection so we add another account instead of replacing.
435
- const existing = (await listConnectedToolkits()).filter(c => c.slug === slug && c.status === 'ACTIVE');
436
- // Reuse whichever user_id already owns connections in this account, so a
437
- // freshly authorized Gmail lands next to the existing Outlook (etc.) under
438
- // the same user_id. Falls back to env override or "default".
414
+ // Use the SDK's blessed one-liner — handles auth config discovery
415
+ // (reuses existing managed config if present) AND new-config creation
416
+ // automatically. Source: README + composio-B75SFMkx.d.cts. Replaces our
417
+ // older two-step `authConfigs.list/create + connectedAccounts.initiate`
418
+ // flow which tripped 401s on plans that don't allow programmatic
419
+ // authConfigs.create even when a managed app exists.
439
420
  const userId = await detectPreferredUserId(composio);
440
421
  try {
441
- const conn = await composio.connectedAccounts.initiate(userId, authConfigId, {
442
- ...(existing.length > 0 ? { allowMultiple: true } : {}),
443
- ...(opts?.callbackUrl ? { callbackUrl: opts.callbackUrl } : {}),
444
- ...(opts?.alias ? { alias: opts.alias } : {}),
445
- });
446
- logger.debug({ slug, userId, connectionId: conn.id }, 'Initiated connection');
422
+ const conn = await composio.toolkits.authorize(userId, slug);
423
+ logger.info({ slug, userId, connectionId: conn.id }, 'Composio authorize OK');
447
424
  return { redirectUrl: conn.redirectUrl ?? null, connectionId: conn.id };
448
425
  }
449
426
  catch (err) {
450
- logger.error({ err, slug, userId, authConfigId, step: 'connectedAccounts.initiate' }, 'Composio initiate failed');
427
+ const status = err?.status;
428
+ logger.error({ err, slug, userId, status, step: 'toolkits.authorize' }, 'Composio authorize failed');
429
+ // Translate the documented "no managed auth available" error codes into
430
+ // the friendly BYO-setup banner the dashboard already renders.
431
+ if (status === 400 || status === 401 || status === 403) {
432
+ throw new ComposioNeedsAuthConfigError(slug, String(err));
433
+ }
451
434
  throw err;
452
435
  }
453
436
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.10.4",
3
+ "version": "1.11.0",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",